1use crate::tools::improvement_algorithms::jaro_winkler_similarity;
7use hashbrown::HashMap;
8use parking_lot::RwLock;
9use smallvec::SmallVec;
10use std::collections::VecDeque;
11use std::sync::Arc;
12
13#[derive(Clone, Debug)]
15pub struct ExecutionEvent {
16 pub tool_name: String,
17 pub arguments: String,
18 pub success: bool,
19 pub quality_score: f32, pub duration_ms: u64,
21 pub timestamp: u64,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum DetectedPattern {
27 Single,
29 ExactRepeat,
31 Loop,
33 NearLoop,
35 Refinement,
37 Degradation,
39 Convergence,
41 Exploration,
43}
44
45pub struct PatternEngine {
47 max_history: usize,
48 sequence_window: usize,
49 events: Arc<RwLock<VecDeque<ExecutionEvent>>>,
50}
51
52impl PatternEngine {
53 pub fn new(max_history: usize, sequence_window: usize) -> Self {
55 Self {
56 max_history,
57 sequence_window,
58 events: Arc::new(RwLock::new(VecDeque::with_capacity(max_history))),
59 }
60 }
61
62 pub fn record(&self, event: ExecutionEvent) {
64 let mut events = self.events.write();
65
66 if events.len() >= self.max_history {
67 events.pop_front();
68 }
69
70 events.push_back(event);
71 }
72
73 pub fn detect_pattern(&self) -> DetectedPattern {
75 let events = self.events.read();
76
77 let len = events.len();
78 if len < 2 {
79 return DetectedPattern::Single;
80 }
81
82 let mut recent = SmallVec::<[&ExecutionEvent; 32]>::new();
85 recent.extend(events.iter().rev().take(self.sequence_window));
86
87 detect_in_sequence(&recent)
88 }
89
90 pub fn predict_next_tool(&self) -> Option<String> {
92 let events = self.events.read();
93
94 let len = events.len();
95 if len < 2 {
96 return None;
97 }
98
99 let mut recent_tools = SmallVec::<[&str; 32]>::new();
101 recent_tools.extend(events.iter().rev().take(self.sequence_window).map(|e| e.tool_name.as_str()));
102
103 if recent_tools.is_empty() {
104 return None;
105 }
106
107 let last_tool = recent_tools[0];
108 let mut predecessors = HashMap::<&str, usize>::new();
109
110 for w in recent_tools.windows(2) {
111 if w[0] == last_tool {
112 *predecessors.entry(w[1]).or_default() += 1;
113 }
114 }
115
116 predecessors
117 .into_iter()
118 .max_by_key(|(_, count)| *count)
119 .map(|(tool, _)| tool.to_owned())
120 }
121
122 pub fn summary(&self) -> ExecutionSummary {
128 let events = self.events.read();
129 if events.is_empty() {
130 return ExecutionSummary::default();
131 }
132
133 let total = events.len();
134 let mut successful = 0usize;
135 let mut quality_sum = 0.0f32;
136 let mut duration_sum = 0u64;
137 let mut unique_tools: hashbrown::HashSet<&str> = hashbrown::HashSet::with_capacity(total.min(16));
139
140 for e in events.iter() {
141 if e.success {
142 successful += 1;
143 }
144 quality_sum += e.quality_score;
145 duration_sum += e.duration_ms;
146 unique_tools.insert(e.tool_name.as_str());
147 }
148
149 ExecutionSummary {
150 total_executions: total,
151 successful_executions: successful,
152 success_rate: successful as f32 / total as f32,
153 average_quality: quality_sum / total as f32,
154 average_duration_ms: duration_sum / total as u64,
155 unique_tools: unique_tools.len(),
156 current_pattern: self.detect_pattern_with_guard(&events),
157 }
158 }
159
160 fn detect_pattern_with_guard(&self, events: &VecDeque<ExecutionEvent>) -> DetectedPattern {
162 let len = events.len();
163 if len < 2 {
164 return DetectedPattern::Single;
165 }
166
167 let mut recent = SmallVec::<[&ExecutionEvent; 32]>::new();
168 recent.extend(events.iter().rev().take(self.sequence_window));
169
170 detect_in_sequence(&recent)
171 }
172}
173
174fn detect_in_sequence(events: &[&ExecutionEvent]) -> DetectedPattern {
181 let len = events.len();
182 if len == 0 {
183 return DetectedPattern::Single;
184 }
185
186 let first = events[0];
187
188 let mut qualities = SmallVec::<[f32; 32]>::with_capacity(len);
193 let mut same_tool = true;
194 let mut same_args = true;
195
196 for e in events {
197 qualities.push(e.quality_score);
198 if e.tool_name != first.tool_name {
199 same_tool = false;
200 }
201 if e.arguments != first.arguments {
202 same_args = false;
203 }
204 }
205
206 if same_tool && same_args {
208 return match len {
209 1 | 2 => DetectedPattern::ExactRepeat,
210 _ => DetectedPattern::Loop,
211 };
212 }
213
214 if len >= 3 {
216 let is_refinement = qualities.windows(2).all(|w| w[0] > w[1] + 0.05);
219 if is_refinement {
220 return DetectedPattern::Refinement;
221 }
222
223 let is_degradation = qualities.windows(2).all(|w| w[0] < w[1] - 0.05);
224 if is_degradation {
225 return DetectedPattern::Degradation;
226 }
227 }
228
229 if same_tool
231 && len >= 3
232 && events
233 .windows(2)
234 .all(|w| jaro_winkler_similarity(&w[0].arguments, &w[1].arguments) > 0.85)
235 {
236 return DetectedPattern::NearLoop;
237 }
238
239 if !same_tool && len >= 3 {
241 let n = len as f32;
243 let avg = qualities.iter().sum::<f32>() / n;
244 let mad = qualities.iter().map(|q| (q - avg).abs()).sum::<f32>() / n;
245
246 if mad < 0.15 {
247 return DetectedPattern::Convergence;
248 }
249 return DetectedPattern::Exploration;
250 }
251
252 if !same_tool {
253 return DetectedPattern::Exploration;
254 }
255
256 DetectedPattern::Single
257}
258
259#[derive(Clone, Debug)]
261pub struct ExecutionSummary {
262 pub total_executions: usize,
263 pub successful_executions: usize,
264 pub success_rate: f32,
265 pub average_quality: f32,
266 pub average_duration_ms: u64,
267 pub unique_tools: usize,
268 pub current_pattern: DetectedPattern,
269}
270
271impl Default for ExecutionSummary {
272 fn default() -> Self {
273 Self {
274 total_executions: 0,
275 successful_executions: 0,
276 success_rate: 0.0,
277 average_quality: 0.0,
278 average_duration_ms: 0,
279 unique_tools: 0,
280 current_pattern: DetectedPattern::Single,
281 }
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 fn make_event(tool: &str, args: &str, quality: f32) -> ExecutionEvent {
290 ExecutionEvent {
291 tool_name: tool.to_owned(),
292 arguments: args.to_owned(),
293 success: true,
294 quality_score: quality,
295 duration_ms: 100,
296 timestamp: 0,
297 }
298 }
299
300 #[test]
301 fn test_pattern_single() {
302 let engine = PatternEngine::new(100, 10);
303 engine.record(make_event("grep", "pattern:test", 0.8));
304 assert_eq!(engine.detect_pattern(), DetectedPattern::Single);
305 }
306
307 #[test]
308 fn test_pattern_loop() {
309 let engine = PatternEngine::new(100, 10);
310 for _ in 0..3 {
311 engine.record(make_event("grep", "pattern:test", 0.5));
312 }
313 assert_eq!(engine.detect_pattern(), DetectedPattern::Loop);
314 }
315
316 #[test]
317 fn test_pattern_refinement() {
318 let engine = PatternEngine::new(100, 10);
319 for (i, &quality) in [0.3f32, 0.5, 0.8].iter().enumerate() {
320 engine.record(ExecutionEvent {
321 tool_name: "grep".to_owned(),
322 arguments: format!("pattern:{i}"),
323 success: true,
324 quality_score: quality,
325 duration_ms: 100,
326 timestamp: i as u64,
327 });
328 }
329 assert_eq!(engine.detect_pattern(), DetectedPattern::Refinement);
330 }
331
332 #[test]
333 fn test_pattern_degradation() {
334 let engine = PatternEngine::new(100, 10);
335 for (i, &quality) in [0.9f32, 0.6, 0.2].iter().enumerate() {
336 engine.record(ExecutionEvent {
337 tool_name: "grep".to_owned(),
338 arguments: format!("pattern:{i}"),
339 success: true,
340 quality_score: quality,
341 duration_ms: 100,
342 timestamp: i as u64,
343 });
344 }
345 assert_eq!(engine.detect_pattern(), DetectedPattern::Degradation);
346 }
347
348 #[test]
349 fn test_predict_next_tool() {
350 let engine = PatternEngine::new(100, 10);
351 for (i, &tool) in ["grep", "read", "grep", "read"].iter().enumerate() {
352 engine.record(ExecutionEvent {
353 tool_name: tool.to_owned(),
354 arguments: "arg".to_owned(),
355 success: true,
356 quality_score: 0.8,
357 duration_ms: 100,
358 timestamp: i as u64,
359 });
360 }
361 assert_eq!(engine.predict_next_tool(), Some("grep".to_owned()));
362 }
363
364 #[test]
365 fn test_execution_summary() {
366 let engine = PatternEngine::new(100, 10);
367 for i in 0..5u64 {
368 engine.record(ExecutionEvent {
369 tool_name: "grep".to_owned(),
370 arguments: "arg".to_owned(),
371 success: i != 2,
372 quality_score: 0.8,
373 duration_ms: 100,
374 timestamp: i,
375 });
376 }
377
378 let summary = engine.summary();
379 assert_eq!(summary.total_executions, 5);
380 assert_eq!(summary.successful_executions, 4);
381 assert!((summary.success_rate - 0.8).abs() < f32::EPSILON);
382 assert_eq!(summary.unique_tools, 1);
383 }
384}