agentic_evolve_core/matching/
composite.rs1use crate::types::error::EvolveResult;
4use crate::types::match_result::{MatchContext, MatchResult, MatchScore};
5use crate::types::pattern::{FunctionSignature, Pattern};
6
7use super::{ContextMatcher, FuzzyMatcher, SemanticMatcher, SignatureMatcher};
8
9#[derive(Debug)]
11pub struct CompositeMatcher {
12 signature_matcher: SignatureMatcher,
13 context_matcher: ContextMatcher,
14 #[allow(dead_code)]
15 semantic_matcher: SemanticMatcher,
16 #[allow(dead_code)]
17 fuzzy_matcher: FuzzyMatcher,
18 weights: MatchWeights,
19}
20
21#[derive(Debug, Clone)]
23pub struct MatchWeights {
24 pub signature: f64,
25 pub context: f64,
26 pub semantic: f64,
27 pub fuzzy: f64,
28}
29
30impl Default for MatchWeights {
31 fn default() -> Self {
32 Self {
33 signature: 0.4,
34 context: 0.2,
35 semantic: 0.25,
36 fuzzy: 0.15,
37 }
38 }
39}
40
41impl CompositeMatcher {
42 pub fn new() -> Self {
43 Self {
44 signature_matcher: SignatureMatcher::new(),
45 context_matcher: ContextMatcher::new(),
46 semantic_matcher: SemanticMatcher::new(),
47 fuzzy_matcher: FuzzyMatcher::default(),
48 weights: MatchWeights::default(),
49 }
50 }
51
52 pub fn with_weights(weights: MatchWeights) -> Self {
53 Self {
54 signature_matcher: SignatureMatcher::new(),
55 context_matcher: ContextMatcher::new(),
56 semantic_matcher: SemanticMatcher::new(),
57 fuzzy_matcher: FuzzyMatcher::default(),
58 weights,
59 }
60 }
61
62 pub fn find_matches(
63 &self,
64 signature: &FunctionSignature,
65 patterns: &[&Pattern],
66 context: &MatchContext,
67 limit: usize,
68 ) -> EvolveResult<Vec<MatchResult>> {
69 let mut combined_scores: std::collections::HashMap<String, (f64, f64, f64, f64, &Pattern)> =
70 std::collections::HashMap::new();
71
72 for pattern in patterns {
74 let sig_score = self.signature_matcher.score_match(pattern, signature);
75 let ctx_score = self.context_matcher.score_context(pattern, context);
76 let sem_score = {
77 let tokens = tokenize_camel_snake(&signature.name);
78 let pat_tokens = tokenize_camel_snake(&pattern.name);
79 token_overlap(&tokens, &pat_tokens)
80 };
81 let confidence_factor = pattern.confidence;
82
83 combined_scores.insert(
84 pattern.id.as_str().to_string(),
85 (sig_score, ctx_score, sem_score, confidence_factor, pattern),
86 );
87 }
88
89 let mut results: Vec<MatchResult> = combined_scores
90 .into_iter()
91 .map(|(_id, (sig, ctx, sem, conf, pattern))| {
92 let combined = sig * self.weights.signature
93 + ctx * self.weights.context
94 + sem * self.weights.semantic
95 + conf * self.weights.fuzzy;
96 MatchResult {
97 pattern_id: pattern.id.clone(),
98 pattern: pattern.clone(),
99 score: MatchScore::new(sig, ctx, sem, combined),
100 suggested_bindings: std::collections::HashMap::new(),
101 }
102 })
103 .filter(|r| r.score.combined > 0.1)
104 .collect();
105
106 results.sort_by(|a, b| {
107 b.score
108 .combined
109 .partial_cmp(&a.score.combined)
110 .unwrap_or(std::cmp::Ordering::Equal)
111 });
112 results.truncate(limit);
113 Ok(results)
114 }
115}
116
117impl Default for CompositeMatcher {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123fn tokenize_camel_snake(name: &str) -> Vec<String> {
124 let mut tokens = Vec::new();
125 let mut current = String::new();
126 for ch in name.chars() {
127 if ch == '_' || ch == '-' || ch == ' ' {
128 if !current.is_empty() {
129 tokens.push(current.to_lowercase());
130 current.clear();
131 }
132 } else if ch.is_uppercase() && !current.is_empty() {
133 tokens.push(current.to_lowercase());
134 current.clear();
135 current.push(ch);
136 } else {
137 current.push(ch);
138 }
139 }
140 if !current.is_empty() {
141 tokens.push(current.to_lowercase());
142 }
143 tokens
144}
145
146fn token_overlap(a: &[String], b: &[String]) -> f64 {
147 if a.is_empty() && b.is_empty() {
148 return 1.0;
149 }
150 let max_len = a.len().max(b.len());
151 if max_len == 0 {
152 return 0.0;
153 }
154 let matches = a.iter().filter(|t| b.contains(t)).count();
155 matches as f64 / max_len as f64
156}