Skip to main content

agentic_evolve_core/composition/
composer.rs

1//! PatternComposer — composes multiple patterns into a single output.
2
3use std::collections::HashMap;
4
5use crate::types::error::{EvolveError, EvolveResult};
6use crate::types::pattern::Pattern;
7
8/// Result of composing patterns.
9#[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/// Composes multiple patterns together.
18#[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}