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(
102 events
103 .iter()
104 .rev()
105 .take(self.sequence_window)
106 .map(|e| e.tool_name.as_str()),
107 );
108
109 if recent_tools.is_empty() {
110 return None;
111 }
112
113 let last_tool = recent_tools[0];
114 let mut predecessors = HashMap::<&str, usize>::new();
115
116 for w in recent_tools.windows(2) {
117 if w[0] == last_tool {
118 *predecessors.entry(w[1]).or_default() += 1;
119 }
120 }
121
122 predecessors
123 .into_iter()
124 .max_by_key(|(_, count)| *count)
125 .map(|(tool, _)| tool.to_owned())
126 }
127
128 pub fn summary(&self) -> ExecutionSummary {
134 let events = self.events.read();
135 if events.is_empty() {
136 return ExecutionSummary::default();
137 }
138
139 let total = events.len();
140 let mut successful = 0usize;
141 let mut quality_sum = 0.0f32;
142 let mut duration_sum = 0u64;
143 let mut unique_tools: hashbrown::HashSet<&str> =
145 hashbrown::HashSet::with_capacity(total.min(16));
146
147 for e in events.iter() {
148 if e.success {
149 successful += 1;
150 }
151 quality_sum += e.quality_score;
152 duration_sum += e.duration_ms;
153 unique_tools.insert(e.tool_name.as_str());
154 }
155
156 ExecutionSummary {
157 total_executions: total,
158 successful_executions: successful,
159 success_rate: successful as f32 / total as f32,
160 average_quality: quality_sum / total as f32,
161 average_duration_ms: duration_sum / total as u64,
162 unique_tools: unique_tools.len(),
163 current_pattern: self.detect_pattern_with_guard(&events),
164 }
165 }
166
167 fn detect_pattern_with_guard(&self, events: &VecDeque<ExecutionEvent>) -> DetectedPattern {
169 let len = events.len();
170 if len < 2 {
171 return DetectedPattern::Single;
172 }
173
174 let mut recent = SmallVec::<[&ExecutionEvent; 32]>::new();
175 recent.extend(events.iter().rev().take(self.sequence_window));
176
177 detect_in_sequence(&recent)
178 }
179}
180
181fn detect_in_sequence(events: &[&ExecutionEvent]) -> DetectedPattern {
188 let len = events.len();
189 if len == 0 {
190 return DetectedPattern::Single;
191 }
192
193 let first = events[0];
194
195 let mut qualities = SmallVec::<[f32; 32]>::with_capacity(len);
200 let mut same_tool = true;
201 let mut same_args = true;
202
203 for e in events {
204 qualities.push(e.quality_score);
205 if e.tool_name != first.tool_name {
206 same_tool = false;
207 }
208 if e.arguments != first.arguments {
209 same_args = false;
210 }
211 }
212
213 if same_tool && same_args {
215 return match len {
216 1 | 2 => DetectedPattern::ExactRepeat,
217 _ => DetectedPattern::Loop,
218 };
219 }
220
221 if len >= 3 {
223 let is_refinement = qualities.windows(2).all(|w| w[0] > w[1] + 0.05);
226 if is_refinement {
227 return DetectedPattern::Refinement;
228 }
229
230 let is_degradation = qualities.windows(2).all(|w| w[0] < w[1] - 0.05);
231 if is_degradation {
232 return DetectedPattern::Degradation;
233 }
234 }
235
236 if same_tool
238 && len >= 3
239 && events
240 .windows(2)
241 .all(|w| jaro_winkler_similarity(&w[0].arguments, &w[1].arguments) > 0.85)
242 {
243 return DetectedPattern::NearLoop;
244 }
245
246 if !same_tool && len >= 3 {
248 let n = len as f32;
250 let avg = qualities.iter().sum::<f32>() / n;
251 let mad = qualities.iter().map(|q| (q - avg).abs()).sum::<f32>() / n;
252
253 if mad < 0.15 {
254 return DetectedPattern::Convergence;
255 }
256 return DetectedPattern::Exploration;
257 }
258
259 if !same_tool {
260 return DetectedPattern::Exploration;
261 }
262
263 DetectedPattern::Single
264}
265
266#[derive(Clone, Debug)]
268pub struct ExecutionSummary {
269 pub total_executions: usize,
270 pub successful_executions: usize,
271 pub success_rate: f32,
272 pub average_quality: f32,
273 pub average_duration_ms: u64,
274 pub unique_tools: usize,
275 pub current_pattern: DetectedPattern,
276}
277
278impl Default for ExecutionSummary {
279 fn default() -> Self {
280 Self {
281 total_executions: 0,
282 successful_executions: 0,
283 success_rate: 0.0,
284 average_quality: 0.0,
285 average_duration_ms: 0,
286 unique_tools: 0,
287 current_pattern: DetectedPattern::Single,
288 }
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 fn make_event(tool: &str, args: &str, quality: f32) -> ExecutionEvent {
297 ExecutionEvent {
298 tool_name: tool.to_owned(),
299 arguments: args.to_owned(),
300 success: true,
301 quality_score: quality,
302 duration_ms: 100,
303 timestamp: 0,
304 }
305 }
306
307 #[test]
308 fn test_pattern_single() {
309 let engine = PatternEngine::new(100, 10);
310 engine.record(make_event("grep", "pattern:test", 0.8));
311 assert_eq!(engine.detect_pattern(), DetectedPattern::Single);
312 }
313
314 #[test]
315 fn test_pattern_loop() {
316 let engine = PatternEngine::new(100, 10);
317 for _ in 0..3 {
318 engine.record(make_event("grep", "pattern:test", 0.5));
319 }
320 assert_eq!(engine.detect_pattern(), DetectedPattern::Loop);
321 }
322
323 #[test]
324 fn test_pattern_refinement() {
325 let engine = PatternEngine::new(100, 10);
326 for (i, &quality) in [0.3f32, 0.5, 0.8].iter().enumerate() {
327 engine.record(ExecutionEvent {
328 tool_name: "grep".to_owned(),
329 arguments: format!("pattern:{i}"),
330 success: true,
331 quality_score: quality,
332 duration_ms: 100,
333 timestamp: i as u64,
334 });
335 }
336 assert_eq!(engine.detect_pattern(), DetectedPattern::Refinement);
337 }
338
339 #[test]
340 fn test_pattern_degradation() {
341 let engine = PatternEngine::new(100, 10);
342 for (i, &quality) in [0.9f32, 0.6, 0.2].iter().enumerate() {
343 engine.record(ExecutionEvent {
344 tool_name: "grep".to_owned(),
345 arguments: format!("pattern:{i}"),
346 success: true,
347 quality_score: quality,
348 duration_ms: 100,
349 timestamp: i as u64,
350 });
351 }
352 assert_eq!(engine.detect_pattern(), DetectedPattern::Degradation);
353 }
354
355 #[test]
356 fn test_predict_next_tool() {
357 let engine = PatternEngine::new(100, 10);
358 for (i, &tool) in ["grep", "read", "grep", "read"].iter().enumerate() {
359 engine.record(ExecutionEvent {
360 tool_name: tool.to_owned(),
361 arguments: "arg".to_owned(),
362 success: true,
363 quality_score: 0.8,
364 duration_ms: 100,
365 timestamp: i as u64,
366 });
367 }
368 assert_eq!(engine.predict_next_tool(), Some("grep".to_owned()));
369 }
370
371 #[test]
372 fn test_execution_summary() {
373 let engine = PatternEngine::new(100, 10);
374 for i in 0..5u64 {
375 engine.record(ExecutionEvent {
376 tool_name: "grep".to_owned(),
377 arguments: "arg".to_owned(),
378 success: i != 2,
379 quality_score: 0.8,
380 duration_ms: 100,
381 timestamp: i,
382 });
383 }
384
385 let summary = engine.summary();
386 assert_eq!(summary.total_executions, 5);
387 assert_eq!(summary.successful_executions, 4);
388 assert_eq!(summary.success_rate, 0.8);
389 assert_eq!(summary.unique_tools, 1);
390 }
391}