use crate::tools::improvement_algorithms::jaro_winkler_similarity;
use hashbrown::HashMap;
use parking_lot::RwLock;
use smallvec::SmallVec;
use std::collections::VecDeque;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct ExecutionEvent {
pub tool_name: String,
pub arguments: String,
pub success: bool,
pub quality_score: f32, pub duration_ms: u64,
pub timestamp: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DetectedPattern {
Single,
ExactRepeat,
Loop,
NearLoop,
Refinement,
Degradation,
Convergence,
Exploration,
}
pub struct PatternEngine {
max_history: usize,
sequence_window: usize,
events: Arc<RwLock<VecDeque<ExecutionEvent>>>,
}
impl PatternEngine {
pub fn new(max_history: usize, sequence_window: usize) -> Self {
Self {
max_history,
sequence_window,
events: Arc::new(RwLock::new(VecDeque::with_capacity(max_history))),
}
}
pub fn record(&self, event: ExecutionEvent) {
let mut events = self.events.write();
if events.len() >= self.max_history {
events.pop_front();
}
events.push_back(event);
}
pub fn detect_pattern(&self) -> DetectedPattern {
let events = self.events.read();
let len = events.len();
if len < 2 {
return DetectedPattern::Single;
}
let mut recent = SmallVec::<[&ExecutionEvent; 32]>::new();
recent.extend(events.iter().rev().take(self.sequence_window));
detect_in_sequence(&recent)
}
pub fn predict_next_tool(&self) -> Option<String> {
let events = self.events.read();
let len = events.len();
if len < 2 {
return None;
}
let mut recent_tools = SmallVec::<[&str; 32]>::new();
recent_tools.extend(
events
.iter()
.rev()
.take(self.sequence_window)
.map(|e| e.tool_name.as_str()),
);
if recent_tools.is_empty() {
return None;
}
let last_tool = recent_tools[0];
let mut predecessors = HashMap::<&str, usize>::new();
for w in recent_tools.windows(2) {
if w[0] == last_tool {
*predecessors.entry(w[1]).or_default() += 1;
}
}
predecessors
.into_iter()
.max_by_key(|(_, count)| *count)
.map(|(tool, _)| tool.to_owned())
}
pub fn summary(&self) -> ExecutionSummary {
let events = self.events.read();
if events.is_empty() {
return ExecutionSummary::default();
}
let total = events.len();
let mut successful = 0usize;
let mut quality_sum = 0.0f32;
let mut duration_sum = 0u64;
let mut unique_tools: hashbrown::HashSet<&str> =
hashbrown::HashSet::with_capacity(total.min(16));
for e in events.iter() {
if e.success {
successful += 1;
}
quality_sum += e.quality_score;
duration_sum += e.duration_ms;
unique_tools.insert(e.tool_name.as_str());
}
ExecutionSummary {
total_executions: total,
successful_executions: successful,
success_rate: successful as f32 / total as f32,
average_quality: quality_sum / total as f32,
average_duration_ms: duration_sum / total as u64,
unique_tools: unique_tools.len(),
current_pattern: self.detect_pattern_with_guard(&events),
}
}
fn detect_pattern_with_guard(&self, events: &VecDeque<ExecutionEvent>) -> DetectedPattern {
let len = events.len();
if len < 2 {
return DetectedPattern::Single;
}
let mut recent = SmallVec::<[&ExecutionEvent; 32]>::new();
recent.extend(events.iter().rev().take(self.sequence_window));
detect_in_sequence(&recent)
}
}
fn detect_in_sequence(events: &[&ExecutionEvent]) -> DetectedPattern {
let len = events.len();
if len == 0 {
return DetectedPattern::Single;
}
let first = events[0];
let mut qualities = SmallVec::<[f32; 32]>::with_capacity(len);
let mut same_tool = true;
let mut same_args = true;
for e in events {
qualities.push(e.quality_score);
if e.tool_name != first.tool_name {
same_tool = false;
}
if e.arguments != first.arguments {
same_args = false;
}
}
if same_tool && same_args {
return match len {
1 | 2 => DetectedPattern::ExactRepeat,
_ => DetectedPattern::Loop,
};
}
if len >= 3 {
let is_refinement = qualities.windows(2).all(|w| w[0] > w[1] + 0.05);
if is_refinement {
return DetectedPattern::Refinement;
}
let is_degradation = qualities.windows(2).all(|w| w[0] < w[1] - 0.05);
if is_degradation {
return DetectedPattern::Degradation;
}
}
if same_tool
&& len >= 3
&& events
.windows(2)
.all(|w| jaro_winkler_similarity(&w[0].arguments, &w[1].arguments) > 0.85)
{
return DetectedPattern::NearLoop;
}
if !same_tool && len >= 3 {
let n = len as f32;
let avg = qualities.iter().sum::<f32>() / n;
let mad = qualities.iter().map(|q| (q - avg).abs()).sum::<f32>() / n;
if mad < 0.15 {
return DetectedPattern::Convergence;
}
return DetectedPattern::Exploration;
}
if !same_tool {
return DetectedPattern::Exploration;
}
DetectedPattern::Single
}
#[derive(Clone, Debug)]
pub struct ExecutionSummary {
pub total_executions: usize,
pub successful_executions: usize,
pub success_rate: f32,
pub average_quality: f32,
pub average_duration_ms: u64,
pub unique_tools: usize,
pub current_pattern: DetectedPattern,
}
impl Default for ExecutionSummary {
fn default() -> Self {
Self {
total_executions: 0,
successful_executions: 0,
success_rate: 0.0,
average_quality: 0.0,
average_duration_ms: 0,
unique_tools: 0,
current_pattern: DetectedPattern::Single,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_event(tool: &str, args: &str, quality: f32) -> ExecutionEvent {
ExecutionEvent {
tool_name: tool.to_owned(),
arguments: args.to_owned(),
success: true,
quality_score: quality,
duration_ms: 100,
timestamp: 0,
}
}
#[test]
fn test_pattern_single() {
let engine = PatternEngine::new(100, 10);
engine.record(make_event("grep", "pattern:test", 0.8));
assert_eq!(engine.detect_pattern(), DetectedPattern::Single);
}
#[test]
fn test_pattern_loop() {
let engine = PatternEngine::new(100, 10);
for _ in 0..3 {
engine.record(make_event("grep", "pattern:test", 0.5));
}
assert_eq!(engine.detect_pattern(), DetectedPattern::Loop);
}
#[test]
fn test_pattern_refinement() {
let engine = PatternEngine::new(100, 10);
for (i, &quality) in [0.3f32, 0.5, 0.8].iter().enumerate() {
engine.record(ExecutionEvent {
tool_name: "grep".to_owned(),
arguments: format!("pattern:{i}"),
success: true,
quality_score: quality,
duration_ms: 100,
timestamp: i as u64,
});
}
assert_eq!(engine.detect_pattern(), DetectedPattern::Refinement);
}
#[test]
fn test_pattern_degradation() {
let engine = PatternEngine::new(100, 10);
for (i, &quality) in [0.9f32, 0.6, 0.2].iter().enumerate() {
engine.record(ExecutionEvent {
tool_name: "grep".to_owned(),
arguments: format!("pattern:{i}"),
success: true,
quality_score: quality,
duration_ms: 100,
timestamp: i as u64,
});
}
assert_eq!(engine.detect_pattern(), DetectedPattern::Degradation);
}
#[test]
fn test_predict_next_tool() {
let engine = PatternEngine::new(100, 10);
for (i, &tool) in ["grep", "read", "grep", "read"].iter().enumerate() {
engine.record(ExecutionEvent {
tool_name: tool.to_owned(),
arguments: "arg".to_owned(),
success: true,
quality_score: 0.8,
duration_ms: 100,
timestamp: i as u64,
});
}
assert_eq!(engine.predict_next_tool(), Some("grep".to_owned()));
}
#[test]
fn test_execution_summary() {
let engine = PatternEngine::new(100, 10);
for i in 0..5u64 {
engine.record(ExecutionEvent {
tool_name: "grep".to_owned(),
arguments: "arg".to_owned(),
success: i != 2,
quality_score: 0.8,
duration_ms: 100,
timestamp: i,
});
}
let summary = engine.summary();
assert_eq!(summary.total_executions, 5);
assert_eq!(summary.successful_executions, 4);
assert_eq!(summary.success_rate, 0.8);
assert_eq!(summary.unique_tools, 1);
}
}