agentic_evolve_core/composition/
composer.rs1use std::collections::HashMap;
4
5use crate::types::error::{EvolveError, EvolveResult};
6use crate::types::pattern::Pattern;
7
8#[derive(Debug, Clone, serde::Serialize)]
10pub struct CompositionResult {
11 pub code: String,
12 pub patterns_used: Vec<String>,
13 pub coverage: f64,
14 pub gaps: Vec<String>,
15}
16
17#[derive(Debug, Default)]
19pub struct PatternComposer;
20
21impl PatternComposer {
22 pub fn new() -> Self {
23 Self
24 }
25
26 pub fn compose(
27 &self,
28 patterns: &[&Pattern],
29 bindings: &HashMap<String, String>,
30 order: Option<&[usize]>,
31 ) -> EvolveResult<CompositionResult> {
32 if patterns.is_empty() {
33 return Err(EvolveError::CompositionError(
34 "No patterns to compose".to_string(),
35 ));
36 }
37
38 let ordered: Vec<&&Pattern> = match order {
39 Some(indices) => indices.iter().filter_map(|&i| patterns.get(i)).collect(),
40 None => patterns.iter().collect(),
41 };
42
43 let mut code_parts = Vec::new();
44 let mut patterns_used = Vec::new();
45 let mut total_placeholders = 0;
46 let mut bound_placeholders = 0;
47
48 for pattern in &ordered {
49 let mut rendered = pattern.template.clone();
50 for (key, value) in bindings {
51 let placeholder = format!("{{{{{key}}}}}");
52 if rendered.contains(&placeholder) {
53 rendered = rendered.replace(&placeholder, value);
54 bound_placeholders += 1;
55 }
56 }
57 total_placeholders += count_placeholders(&rendered) + bound_placeholders;
58 code_parts.push(rendered);
59 patterns_used.push(pattern.id.as_str().to_string());
60 }
61
62 let code = code_parts.join("\n\n");
63 let gaps = find_unbound_placeholders(&code);
64 let coverage = if total_placeholders == 0 {
65 1.0
66 } else {
67 bound_placeholders as f64 / total_placeholders as f64
68 };
69
70 Ok(CompositionResult {
71 code,
72 patterns_used,
73 coverage,
74 gaps,
75 })
76 }
77}
78
79fn count_placeholders(template: &str) -> usize {
80 let re = regex::Regex::new(r"\{\{\w+\}\}").unwrap();
81 re.find_iter(template).count()
82}
83
84fn find_unbound_placeholders(code: &str) -> Vec<String> {
85 let re = regex::Regex::new(r"\{\{(\w+)\}\}").unwrap();
86 re.captures_iter(code)
87 .map(|cap| cap[1].to_string())
88 .collect()
89}