Skip to main content

codetether_agent/rlm/oracle/
templates.rs

1//! Oracle-friendly query templates for high-golden-rate trace generation.
2//!
3//! These templates are designed to produce verifiable queries that the oracles
4//! can deterministically validate. Each template specifies:
5//! - The query text
6//! - The expected oracle kind (grep, ast, or semantic)
7//! - Example parameters
8//!
9//! # Usage
10//!
11//! ```ignore
12//! use codetether_agent::rlm::oracle::templates::{QueryTemplate, TemplateKind};
13//!
14//! // Generate a grep template query
15//! let template = QueryTemplate::find_all_pattern("async fn", "src/main.rs");
16//! println!("{}", template.render()); // "Find all occurrences of async fn in src/main.rs"
17//!
18//! // Get all templates of a certain kind
19//! let grep_templates = QueryTemplate::all()
20//!     .filter(|t| t.kind == TemplateKind::Grep);
21//! ```
22
23use serde::{Deserialize, Serialize};
24
25/// The kind of oracle this template is designed for.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum TemplateKind {
28    /// Grep/pattern-match oracle
29    Grep,
30    /// Tree-sitter/AST oracle
31    Ast,
32    /// Semantic (unverifiable) - included for completeness
33    Semantic,
34}
35
36impl TemplateKind {
37    /// Convert to the FinalPayload kind string.
38    pub fn as_payload_kind(&self) -> &'static str {
39        match self {
40            TemplateKind::Grep => "grep",
41            TemplateKind::Ast => "ast",
42            TemplateKind::Semantic => "semantic",
43        }
44    }
45}
46
47/// A query template for oracle-friendly trace generation.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct QueryTemplate {
50    /// Unique template identifier
51    pub id: &'static str,
52    /// Human-readable template description
53    pub description: &'static str,
54    /// The template string with {PLACEHOLDERS}
55    pub template: &'static str,
56    /// Parameters this template expects
57    #[serde(skip)]
58    pub params: &'static [Param],
59    /// Expected oracle kind
60    pub kind: TemplateKind,
61    /// Example parameter values
62    #[serde(skip)]
63    pub example: &'static [&'static str],
64}
65
66/// A parameter in a query template.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct Param {
69    /// Parameter name (used as placeholder)
70    pub name: &'static str,
71    /// Description of what value to provide
72    pub description: &'static str,
73    /// Example value
74    pub example: &'static str,
75}
76
77impl QueryTemplate {
78    /// Render this template with the given parameter values.
79    pub fn render(&self, values: &[&str]) -> String {
80        let mut result = self.template.to_string();
81        
82        for (i, value) in values.iter().enumerate() {
83            if i < self.params.len() {
84                let placeholder = format!("{{{}}}", self.params[i].name);
85                result = result.replace(&placeholder, value);
86            }
87        }
88        
89        result
90    }
91
92    /// Get all defined templates.
93    pub fn all() -> &'static [QueryTemplate] {
94        TEMPLATES
95    }
96
97    /// Get templates by kind.
98    pub fn by_kind(kind: TemplateKind) -> impl Iterator<Item = &'static QueryTemplate> {
99        TEMPLATES.iter().filter(move |t| t.kind == kind)
100    }
101
102    /// Find a template by ID.
103    pub fn find(id: &str) -> Option<&'static QueryTemplate> {
104        TEMPLATES.iter().find(|t| t.id == id)
105    }
106
107    /// Generate a random example query (for testing).
108    #[allow(dead_code)]
109    pub fn random_example(&self) -> String {
110        self.render(self.example)
111    }
112}
113
114// Define all templates
115const TEMPLATES: &[QueryTemplate] = &[
116    // ==================== GREP TEMPLATES ====================
117    QueryTemplate {
118        id: "grep_find_occurrences",
119        description: "Find all occurrences of a string pattern in a file",
120        template: "Find all occurrences of {PATTERN} in {FILE}",
121        params: &[
122            Param {
123                name: "PATTERN",
124                description: "String or regex pattern to search for",
125                example: "async fn",
126            },
127            Param {
128                name: "FILE",
129                description: "File path to search in",
130                example: "src/main.rs",
131            },
132        ],
133        kind: TemplateKind::Grep,
134        example: &["async fn", "src/main.rs"],
135    },
136    QueryTemplate {
137        id: "grep_find_regex",
138        description: "Find all lines matching a regex pattern in a file",
139        template: "Find all lines matching {REGEX} in {FILE}",
140        params: &[
141            Param {
142                name: "REGEX",
143                description: "Regular expression pattern",
144                example: r"\bpub\s+fn\b",
145            },
146            Param {
147                name: "FILE",
148                description: "File path to search in",
149                example: "src/lib.rs",
150            },
151        ],
152        kind: TemplateKind::Grep,
153        example: &[r"\bpub\s+fn\b", "src/lib.rs"],
154    },
155    QueryTemplate {
156        id: "grep_count_occurrences",
157        description: "Count occurrences of a pattern in a file",
158        template: "Count occurrences of {PATTERN} in {FILE}",
159        params: &[
160            Param {
161                name: "PATTERN",
162                description: "Pattern to count",
163                example: "TODO",
164            },
165            Param {
166                name: "FILE",
167                description: "File path",
168                example: "src/main.rs",
169            },
170        ],
171        kind: TemplateKind::Grep,
172        example: &["TODO", "src/main.rs"],
173    },
174    QueryTemplate {
175        id: "grep_find_functions",
176        description: "Find all function definitions matching a pattern",
177        template: "Find all {VISIBILITY} functions matching {PATTERN} in {FILE}",
178        params: &[
179            Param {
180                name: "VISIBILITY",
181                description: "public, private, or all (leave empty)",
182                example: "public",
183            },
184            Param {
185                name: "PATTERN",
186                description: "Pattern in function definition",
187                example: "async fn",
188            },
189            Param {
190                name: "FILE",
191                description: "File path",
192                example: "src/main.rs",
193            },
194        ],
195        kind: TemplateKind::Grep,
196        example: &["public", "async fn", "src/main.rs"],
197    },
198    QueryTemplate {
199        id: "grep_find_structs",
200        description: "Find all struct definitions",
201        template: "Find all structs in {FILE}",
202        params: &[
203            Param {
204                name: "FILE",
205                description: "File path",
206                example: "src/main.rs",
207            },
208        ],
209        kind: TemplateKind::Grep,
210        example: &["src/main.rs"],
211    },
212    QueryTemplate {
213        id: "grep_find_enums",
214        description: "Find all enum definitions",
215        template: "Find all enums in {FILE}",
216        params: &[
217            Param {
218                name: "FILE",
219                description: "File path",
220                example: "src/main.rs",
221            },
222        ],
223        kind: TemplateKind::Grep,
224        example: &["src/main.rs"],
225    },
226    QueryTemplate {
227        id: "grep_find_traits",
228        description: "Find all trait definitions",
229        template: "Find all traits in {FILE}",
230        params: &[
231            Param {
232                name: "FILE",
233                description: "File path",
234                example: "src/main.rs",
235            },
236        ],
237        kind: TemplateKind::Grep,
238        example: &["src/main.rs"],
239    },
240    QueryTemplate {
241        id: "grep_find_impls",
242        description: "Find all impl blocks",
243        template: "Find all impl blocks in {FILE}",
244        params: &[
245            Param {
246                name: "FILE",
247                description: "File path",
248                example: "src/main.rs",
249            },
250        ],
251        kind: TemplateKind::Grep,
252        example: &["src/main.rs"],
253    },
254    QueryTemplate {
255        id: "grep_find_imports",
256        description: "Find all use/import statements",
257        template: "Find all imports in {FILE}",
258        params: &[
259            Param {
260                name: "FILE",
261                description: "File path",
262                example: "src/main.rs",
263            },
264        ],
265        kind: TemplateKind::Grep,
266        example: &["src/main.rs"],
267    },
268    QueryTemplate {
269        id: "grep_find_tests",
270        description: "Find all test functions",
271        template: "Find all test functions in {FILE}",
272        params: &[
273            Param {
274                name: "FILE",
275                description: "File path",
276                example: "src/main.rs",
277            },
278        ],
279        kind: TemplateKind::Grep,
280        example: &["src/main.rs"],
281    },
282    QueryTemplate {
283        id: "grep_find_error_handling",
284        description: "Find error handling patterns (Result, ?, unwrap)",
285        template: "Find all error handling patterns in {FILE}",
286        params: &[
287            Param {
288                name: "FILE",
289                description: "File path",
290                example: "src/main.rs",
291            },
292        ],
293        kind: TemplateKind::Grep,
294        example: &["src/main.rs"],
295    },
296    QueryTemplate {
297        id: "grep_find_macros",
298        description: "Find macro invocations",
299        template: "Find all macro calls in {FILE}",
300        params: &[
301            Param {
302                name: "FILE",
303                description: "File path",
304                example: "src/main.rs",
305            },
306        ],
307        kind: TemplateKind::Grep,
308        example: &["src/main.rs"],
309    },
310    QueryTemplate {
311        id: "grep_find_comments",
312        description: "Find comments in code",
313        template: "Find all comments in {FILE}",
314        params: &[
315            Param {
316                name: "FILE",
317                description: "File path",
318                example: "src/main.rs",
319            },
320        ],
321        kind: TemplateKind::Grep,
322        example: &["src/main.rs"],
323    },
324    QueryTemplate {
325        id: "grep_find_todos",
326        description: "Find TODO/FIXME comments",
327        template: "Find all TODO comments in {FILE}",
328        params: &[
329            Param {
330                name: "FILE",
331                description: "File path",
332                example: "src/main.rs",
333            },
334        ],
335        kind: TemplateKind::Grep,
336        example: &["src/main.rs"],
337    },
338    QueryTemplate {
339        id: "grep_find_string_literals",
340        description: "Find string literals",
341        template: "Find all string literals in {FILE}",
342        params: &[
343            Param {
344                name: "FILE",
345                description: "File path",
346                example: "src/main.rs",
347            },
348        ],
349        kind: TemplateKind::Grep,
350        example: &["src/main.rs"],
351    },
352
353    // ==================== AST TEMPLATES ====================
354    QueryTemplate {
355        id: "ast_list_functions",
356        description: "List all function names in a file",
357        template: "List all function names in {FILE}",
358        params: &[
359            Param {
360                name: "FILE",
361                description: "File path",
362                example: "src/main.rs",
363            },
364        ],
365        kind: TemplateKind::Ast,
366        example: &["src/main.rs"],
367    },
368    QueryTemplate {
369        id: "ast_list_structs",
370        description: "List all structs and their fields",
371        template: "List all structs and fields in {FILE}",
372        params: &[
373            Param {
374                name: "FILE",
375                description: "File path",
376                example: "src/main.rs",
377            },
378        ],
379        kind: TemplateKind::Ast,
380        example: &["src/main.rs"],
381    },
382    QueryTemplate {
383        id: "ast_list_enums",
384        description: "List all enums and their variants",
385        template: "List all enums and variants in {FILE}",
386        params: &[
387            Param {
388                name: "FILE",
389                description: "File path",
390                example: "src/main.rs",
391            },
392        ],
393        kind: TemplateKind::Ast,
394        example: &["src/main.rs"],
395    },
396    QueryTemplate {
397        id: "ast_list_impls",
398        description: "List all impl blocks for a type",
399        template: "List all impl blocks for {TYPE} in {FILE}",
400        params: &[
401            Param {
402                name: "TYPE",
403                description: "Type name to find impls for",
404                example: "MyStruct",
405            },
406            Param {
407                name: "FILE",
408                description: "File path",
409                example: "src/main.rs",
410            },
411        ],
412        kind: TemplateKind::Ast,
413        example: &["MyStruct", "src/main.rs"],
414    },
415    QueryTemplate {
416        id: "ast_function_signatures",
417        description: "Get function signatures (name, params, return type)",
418        template: "List all function signatures in {FILE}",
419        params: &[
420            Param {
421                name: "FILE",
422                description: "File path",
423                example: "src/main.rs",
424            },
425        ],
426        kind: TemplateKind::Ast,
427        example: &["src/main.rs"],
428    },
429    QueryTemplate {
430        id: "ast_pub_functions",
431        description: "List public function signatures",
432        template: "List all public function signatures in {FILE}",
433        params: &[
434            Param {
435                name: "FILE",
436                description: "File path",
437                example: "src/main.rs",
438            },
439        ],
440        kind: TemplateKind::Ast,
441        example: &["src/main.rs"],
442    },
443    QueryTemplate {
444        id: "ast_async_functions",
445        description: "List async function signatures",
446        template: "List all async function signatures in {FILE}",
447        params: &[
448            Param {
449                name: "FILE",
450                description: "File path",
451                example: "src/main.rs",
452            },
453        ],
454        kind: TemplateKind::Ast,
455        example: &["src/main.rs"],
456    },
457    QueryTemplate {
458        id: "ast_struct_fields",
459        description: "Get struct field names and types",
460        template: "What fields does {STRUCT} have in {FILE}?",
461        params: &[
462            Param {
463                name: "STRUCT",
464                description: "Struct name",
465                example: "Config",
466            },
467            Param {
468                name: "FILE",
469                description: "File path",
470                example: "src/main.rs",
471            },
472        ],
473        kind: TemplateKind::Ast,
474        example: &["Config", "src/main.rs"],
475    },
476    QueryTemplate {
477        id: "ast_enum_variants",
478        description: "Get enum variant names",
479        template: "What variants does {ENUM} have in {FILE}?",
480        params: &[
481            Param {
482                name: "ENUM",
483                description: "Enum name",
484                example: "Status",
485            },
486            Param {
487                name: "FILE",
488                description: "File path",
489                example: "src/main.rs",
490            },
491        ],
492        kind: TemplateKind::Ast,
493        example: &["Status", "src/main.rs"],
494    },
495    QueryTemplate {
496        id: "ast_trait_impls",
497        description: "Find trait implementations",
498        template: "What traits does {TYPE} implement in {FILE}?",
499        params: &[
500            Param {
501                name: "TYPE",
502                description: "Type name",
503                example: "MyStruct",
504            },
505            Param {
506                name: "FILE",
507                description: "File path",
508                example: "src/main.rs",
509            },
510        ],
511        kind: TemplateKind::Ast,
512        example: &["MyStruct", "src/main.rs"],
513    },
514    QueryTemplate {
515        id: "ast_error_handling_count",
516        description: "Count error handling patterns",
517        template: "Count the error handling patterns (Result, ?, unwrap, expect) in {FILE}",
518        params: &[
519            Param {
520                name: "FILE",
521                description: "File path",
522                example: "src/main.rs",
523            },
524        ],
525        kind: TemplateKind::Ast,
526        example: &["src/main.rs"],
527    },
528    QueryTemplate {
529        id: "ast_method_names",
530        description: "List all method names in impl blocks",
531        template: "List all methods defined in impl blocks in {FILE}",
532        params: &[
533            Param {
534                name: "FILE",
535                description: "File path",
536                example: "src/main.rs",
537            },
538        ],
539        kind: TemplateKind::Ast,
540        example: &["src/main.rs"],
541    },
542];
543
544/// A pre-generated query ready for use.
545#[derive(Debug, Clone)]
546pub struct GeneratedQuery {
547    /// The rendered query text
548    pub query: String,
549    /// Template ID used
550    pub template_id: &'static str,
551    /// Expected oracle kind
552    pub kind: TemplateKind,
553    /// File the query targets
554    pub file: String,
555}
556
557impl GeneratedQuery {
558    /// Render a template with custom values.
559    pub fn render(template: &'static QueryTemplate, values: &[&str]) -> Self {
560        let file = values.last().unwrap_or(&"").to_string();
561        
562        Self {
563            query: template.render(values),
564            template_id: template.id,
565            kind: template.kind,
566            file,
567        }
568    }
569}
570
571/// Iterator over templates of a specific kind.
572pub struct TemplateIter {
573    index: usize,
574    kind: Option<TemplateKind>,
575}
576
577impl Iterator for TemplateIter {
578    type Item = &'static QueryTemplate;
579
580    fn next(&mut self) -> Option<Self::Item> {
581        while self.index < TEMPLATES.len() {
582            let template = &TEMPLATES[self.index];
583            self.index += 1;
584            
585            if let Some(kind) = self.kind {
586                if template.kind == kind {
587                    return Some(template);
588                }
589            } else {
590                return Some(template);
591            }
592        }
593        None
594    }
595}
596
597impl QueryTemplate {
598    /// Iterate over all templates.
599    pub fn iter() -> TemplateIter {
600        TemplateIter { index: 0, kind: None }
601    }
602
603    /// Iterate over templates of a specific kind.
604    pub fn iter_kind(kind: TemplateKind) -> TemplateIter {
605        TemplateIter { index: 0, kind: Some(kind) }
606    }
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612
613    #[test]
614    fn render_simple_template() {
615        let template = QueryTemplate::find("grep_find_occurrences").unwrap();
616        let rendered = template.render(&["fn", "src/main.rs"]);
617        assert_eq!(rendered, "Find all occurrences of fn in src/main.rs");
618    }
619
620    #[test]
621    fn find_template_by_id() {
622        let template = QueryTemplate::find("ast_list_functions").unwrap();
623        assert_eq!(template.kind, TemplateKind::Ast);
624    }
625
626    #[test]
627    fn filter_templates_by_kind() {
628        let grep_count = QueryTemplate::iter_kind(TemplateKind::Grep).count();
629        let ast_count = QueryTemplate::iter_kind(TemplateKind::Ast).count();
630        
631        assert!(grep_count > 0);
632        assert!(ast_count > 0);
633    }
634
635    #[test]
636    fn template_has_examples() {
637        for template in QueryTemplate::all() {
638            assert!(!template.example.is_empty(), "Template {} has no examples", template.id);
639        }
640    }
641
642    #[test]
643    fn generated_query_has_fields() {
644        let template = QueryTemplate::find("grep_find_occurrences").unwrap();
645        let r#gen = GeneratedQuery::render(template, &["async fn", "src/lib.rs"]);
646        
647        assert_eq!(gen.query, "Find all occurrences of async fn in src/lib.rs");
648        assert_eq!(gen.template_id, "grep_find_occurrences");
649        assert_eq!(gen.kind, TemplateKind::Grep);
650        assert_eq!(gen.file, "src/lib.rs");
651    }
652
653    #[test]
654    fn template_kind_to_payload_kind() {
655        assert_eq!(TemplateKind::Grep.as_payload_kind(), "grep");
656        assert_eq!(TemplateKind::Ast.as_payload_kind(), "ast");
657        assert_eq!(TemplateKind::Semantic.as_payload_kind(), "semantic");
658    }
659}