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: &[Param {
203            name: "FILE",
204            description: "File path",
205            example: "src/main.rs",
206        }],
207        kind: TemplateKind::Grep,
208        example: &["src/main.rs"],
209    },
210    QueryTemplate {
211        id: "grep_find_enums",
212        description: "Find all enum definitions",
213        template: "Find all enums in {FILE}",
214        params: &[Param {
215            name: "FILE",
216            description: "File path",
217            example: "src/main.rs",
218        }],
219        kind: TemplateKind::Grep,
220        example: &["src/main.rs"],
221    },
222    QueryTemplate {
223        id: "grep_find_traits",
224        description: "Find all trait definitions",
225        template: "Find all traits in {FILE}",
226        params: &[Param {
227            name: "FILE",
228            description: "File path",
229            example: "src/main.rs",
230        }],
231        kind: TemplateKind::Grep,
232        example: &["src/main.rs"],
233    },
234    QueryTemplate {
235        id: "grep_find_impls",
236        description: "Find all impl blocks",
237        template: "Find all impl blocks in {FILE}",
238        params: &[Param {
239            name: "FILE",
240            description: "File path",
241            example: "src/main.rs",
242        }],
243        kind: TemplateKind::Grep,
244        example: &["src/main.rs"],
245    },
246    QueryTemplate {
247        id: "grep_find_imports",
248        description: "Find all use/import statements",
249        template: "Find all imports in {FILE}",
250        params: &[Param {
251            name: "FILE",
252            description: "File path",
253            example: "src/main.rs",
254        }],
255        kind: TemplateKind::Grep,
256        example: &["src/main.rs"],
257    },
258    QueryTemplate {
259        id: "grep_find_tests",
260        description: "Find all test functions",
261        template: "Find all test functions in {FILE}",
262        params: &[Param {
263            name: "FILE",
264            description: "File path",
265            example: "src/main.rs",
266        }],
267        kind: TemplateKind::Grep,
268        example: &["src/main.rs"],
269    },
270    QueryTemplate {
271        id: "grep_find_error_handling",
272        description: "Find error handling patterns (Result, ?, unwrap)",
273        template: "Find all error handling patterns in {FILE}",
274        params: &[Param {
275            name: "FILE",
276            description: "File path",
277            example: "src/main.rs",
278        }],
279        kind: TemplateKind::Grep,
280        example: &["src/main.rs"],
281    },
282    QueryTemplate {
283        id: "grep_find_macros",
284        description: "Find macro invocations",
285        template: "Find all macro calls in {FILE}",
286        params: &[Param {
287            name: "FILE",
288            description: "File path",
289            example: "src/main.rs",
290        }],
291        kind: TemplateKind::Grep,
292        example: &["src/main.rs"],
293    },
294    QueryTemplate {
295        id: "grep_find_comments",
296        description: "Find comments in code",
297        template: "Find all comments in {FILE}",
298        params: &[Param {
299            name: "FILE",
300            description: "File path",
301            example: "src/main.rs",
302        }],
303        kind: TemplateKind::Grep,
304        example: &["src/main.rs"],
305    },
306    QueryTemplate {
307        id: "grep_find_todos",
308        description: "Find TODO/FIXME comments",
309        template: "Find all TODO comments in {FILE}",
310        params: &[Param {
311            name: "FILE",
312            description: "File path",
313            example: "src/main.rs",
314        }],
315        kind: TemplateKind::Grep,
316        example: &["src/main.rs"],
317    },
318    QueryTemplate {
319        id: "grep_find_string_literals",
320        description: "Find string literals",
321        template: "Find all string literals in {FILE}",
322        params: &[Param {
323            name: "FILE",
324            description: "File path",
325            example: "src/main.rs",
326        }],
327        kind: TemplateKind::Grep,
328        example: &["src/main.rs"],
329    },
330    // ==================== AST TEMPLATES ====================
331    QueryTemplate {
332        id: "ast_list_functions",
333        description: "List all function names in a file",
334        template: "List all function names in {FILE}",
335        params: &[Param {
336            name: "FILE",
337            description: "File path",
338            example: "src/main.rs",
339        }],
340        kind: TemplateKind::Ast,
341        example: &["src/main.rs"],
342    },
343    QueryTemplate {
344        id: "ast_list_structs",
345        description: "List all structs and their fields",
346        template: "List all structs and fields in {FILE}",
347        params: &[Param {
348            name: "FILE",
349            description: "File path",
350            example: "src/main.rs",
351        }],
352        kind: TemplateKind::Ast,
353        example: &["src/main.rs"],
354    },
355    QueryTemplate {
356        id: "ast_list_enums",
357        description: "List all enums and their variants",
358        template: "List all enums and variants in {FILE}",
359        params: &[Param {
360            name: "FILE",
361            description: "File path",
362            example: "src/main.rs",
363        }],
364        kind: TemplateKind::Ast,
365        example: &["src/main.rs"],
366    },
367    QueryTemplate {
368        id: "ast_list_impls",
369        description: "List all impl blocks for a type",
370        template: "List all impl blocks for {TYPE} in {FILE}",
371        params: &[
372            Param {
373                name: "TYPE",
374                description: "Type name to find impls for",
375                example: "MyStruct",
376            },
377            Param {
378                name: "FILE",
379                description: "File path",
380                example: "src/main.rs",
381            },
382        ],
383        kind: TemplateKind::Ast,
384        example: &["MyStruct", "src/main.rs"],
385    },
386    QueryTemplate {
387        id: "ast_function_signatures",
388        description: "Get function signatures (name, params, return type)",
389        template: "List all function signatures in {FILE}",
390        params: &[Param {
391            name: "FILE",
392            description: "File path",
393            example: "src/main.rs",
394        }],
395        kind: TemplateKind::Ast,
396        example: &["src/main.rs"],
397    },
398    QueryTemplate {
399        id: "ast_pub_functions",
400        description: "List public function signatures",
401        template: "List all public function signatures in {FILE}",
402        params: &[Param {
403            name: "FILE",
404            description: "File path",
405            example: "src/main.rs",
406        }],
407        kind: TemplateKind::Ast,
408        example: &["src/main.rs"],
409    },
410    QueryTemplate {
411        id: "ast_async_functions",
412        description: "List async function signatures",
413        template: "List all async function signatures in {FILE}",
414        params: &[Param {
415            name: "FILE",
416            description: "File path",
417            example: "src/main.rs",
418        }],
419        kind: TemplateKind::Ast,
420        example: &["src/main.rs"],
421    },
422    QueryTemplate {
423        id: "ast_struct_fields",
424        description: "Get struct field names and types",
425        template: "What fields does {STRUCT} have in {FILE}?",
426        params: &[
427            Param {
428                name: "STRUCT",
429                description: "Struct name",
430                example: "Config",
431            },
432            Param {
433                name: "FILE",
434                description: "File path",
435                example: "src/main.rs",
436            },
437        ],
438        kind: TemplateKind::Ast,
439        example: &["Config", "src/main.rs"],
440    },
441    QueryTemplate {
442        id: "ast_enum_variants",
443        description: "Get enum variant names",
444        template: "What variants does {ENUM} have in {FILE}?",
445        params: &[
446            Param {
447                name: "ENUM",
448                description: "Enum name",
449                example: "Status",
450            },
451            Param {
452                name: "FILE",
453                description: "File path",
454                example: "src/main.rs",
455            },
456        ],
457        kind: TemplateKind::Ast,
458        example: &["Status", "src/main.rs"],
459    },
460    QueryTemplate {
461        id: "ast_trait_impls",
462        description: "Find trait implementations",
463        template: "What traits does {TYPE} implement in {FILE}?",
464        params: &[
465            Param {
466                name: "TYPE",
467                description: "Type name",
468                example: "MyStruct",
469            },
470            Param {
471                name: "FILE",
472                description: "File path",
473                example: "src/main.rs",
474            },
475        ],
476        kind: TemplateKind::Ast,
477        example: &["MyStruct", "src/main.rs"],
478    },
479    QueryTemplate {
480        id: "ast_error_handling_count",
481        description: "Count error handling patterns",
482        template: "Count the error handling patterns (Result, ?, unwrap, expect) in {FILE}",
483        params: &[Param {
484            name: "FILE",
485            description: "File path",
486            example: "src/main.rs",
487        }],
488        kind: TemplateKind::Ast,
489        example: &["src/main.rs"],
490    },
491    QueryTemplate {
492        id: "ast_method_names",
493        description: "List all method names in impl blocks",
494        template: "List all methods defined in impl blocks in {FILE}",
495        params: &[Param {
496            name: "FILE",
497            description: "File path",
498            example: "src/main.rs",
499        }],
500        kind: TemplateKind::Ast,
501        example: &["src/main.rs"],
502    },
503];
504
505/// A pre-generated query ready for use.
506#[derive(Debug, Clone)]
507pub struct GeneratedQuery {
508    /// The rendered query text
509    pub query: String,
510    /// Template ID used
511    pub template_id: &'static str,
512    /// Expected oracle kind
513    pub kind: TemplateKind,
514    /// File the query targets
515    pub file: String,
516}
517
518impl GeneratedQuery {
519    /// Render a template with custom values.
520    pub fn render(template: &'static QueryTemplate, values: &[&str]) -> Self {
521        let file = values.last().unwrap_or(&"").to_string();
522
523        Self {
524            query: template.render(values),
525            template_id: template.id,
526            kind: template.kind,
527            file,
528        }
529    }
530}
531
532/// Iterator over templates of a specific kind.
533pub struct TemplateIter {
534    index: usize,
535    kind: Option<TemplateKind>,
536}
537
538impl Iterator for TemplateIter {
539    type Item = &'static QueryTemplate;
540
541    fn next(&mut self) -> Option<Self::Item> {
542        while self.index < TEMPLATES.len() {
543            let template = &TEMPLATES[self.index];
544            self.index += 1;
545
546            if let Some(kind) = self.kind {
547                if template.kind == kind {
548                    return Some(template);
549                }
550            } else {
551                return Some(template);
552            }
553        }
554        None
555    }
556}
557
558impl QueryTemplate {
559    /// Iterate over all templates.
560    pub fn iter() -> TemplateIter {
561        TemplateIter {
562            index: 0,
563            kind: None,
564        }
565    }
566
567    /// Iterate over templates of a specific kind.
568    pub fn iter_kind(kind: TemplateKind) -> TemplateIter {
569        TemplateIter {
570            index: 0,
571            kind: Some(kind),
572        }
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579
580    #[test]
581    fn render_simple_template() {
582        let template = QueryTemplate::find("grep_find_occurrences").unwrap();
583        let rendered = template.render(&["fn", "src/main.rs"]);
584        assert_eq!(rendered, "Find all occurrences of fn in src/main.rs");
585    }
586
587    #[test]
588    fn find_template_by_id() {
589        let template = QueryTemplate::find("ast_list_functions").unwrap();
590        assert_eq!(template.kind, TemplateKind::Ast);
591    }
592
593    #[test]
594    fn filter_templates_by_kind() {
595        let grep_count = QueryTemplate::iter_kind(TemplateKind::Grep).count();
596        let ast_count = QueryTemplate::iter_kind(TemplateKind::Ast).count();
597
598        assert!(grep_count > 0);
599        assert!(ast_count > 0);
600    }
601
602    #[test]
603    fn template_has_examples() {
604        for template in QueryTemplate::all() {
605            assert!(
606                !template.example.is_empty(),
607                "Template {} has no examples",
608                template.id
609            );
610        }
611    }
612
613    #[test]
614    fn generated_query_has_fields() {
615        let template = QueryTemplate::find("grep_find_occurrences").unwrap();
616        let r#gen = GeneratedQuery::render(template, &["async fn", "src/lib.rs"]);
617
618        assert_eq!(
619            r#gen.query,
620            "Find all occurrences of async fn in src/lib.rs"
621        );
622        assert_eq!(r#gen.template_id, "grep_find_occurrences");
623        assert_eq!(r#gen.kind, TemplateKind::Grep);
624        assert_eq!(r#gen.file, "src/lib.rs");
625    }
626
627    #[test]
628    fn template_kind_to_payload_kind() {
629        assert_eq!(TemplateKind::Grep.as_payload_kind(), "grep");
630        assert_eq!(TemplateKind::Ast.as_payload_kind(), "ast");
631        assert_eq!(TemplateKind::Semantic.as_payload_kind(), "semantic");
632    }
633}