oxicode/discovery/builtin_rules/
mod.rs1use oxicode_agent::agent_loop::ttsr::{InterruptMode, Rule, RuleSource, ScopeToken};
8use regex::Regex;
9use serde::Deserialize;
10
11fn raw_bundled_rules() -> Vec<(&'static str, &'static str)> {
13 vec![
14 ("rs-future-prelude", include_str!("rs-future-prelude.md")),
15 ("rs-box-leak", include_str!("rs-box-leak.md")),
16 (
17 "rs-match-ergonomics",
18 include_str!("rs-match-ergonomics.md"),
19 ),
20 ("rs-parking-lot", include_str!("rs-parking-lot.md")),
21 ("rs-result-type", include_str!("rs-result-type.md")),
22 ("rs-lazylock", include_str!("rs-lazylock.md")),
23 ("rs-tokio-mutex", include_str!("rs-tokio-mutex.md")),
24 ]
25}
26
27#[derive(Debug, Deserialize)]
29struct RuleFrontmatter {
30 description: Option<String>,
31 condition: String,
32 scope: Option<String>,
33 #[serde(rename = "interruptMode")]
34 interrupt_mode: Option<String>,
35 globs: Option<Vec<String>>,
36 #[serde(rename = "alwaysApply")]
37 always_apply: Option<bool>,
38}
39
40fn parse_scope(s: &str) -> Vec<ScopeToken> {
42 match s.trim().to_lowercase().as_str() {
43 "text" => vec![ScopeToken::Text],
44 "thinking" => vec![ScopeToken::Thinking],
45 "tool" => vec![ScopeToken::Tool {
46 name: String::new(),
47 globs: vec![],
48 }],
49 _ => vec![ScopeToken::Text],
50 }
51}
52
53fn parse_interrupt_mode(s: &str) -> InterruptMode {
55 match s.trim().to_lowercase().as_str() {
56 "never" => InterruptMode::Never,
57 "prose-only" | "proseonly" => InterruptMode::ProseOnly,
58 "tool-only" | "toolonly" => InterruptMode::ToolOnly,
59 "always" => InterruptMode::Always,
60 _ => InterruptMode::ProseOnly,
61 }
62}
63
64pub(crate) fn parse_rule_file(content: &str, name: &str, source: RuleSource) -> Option<Rule> {
69 let parts: Vec<&str> = content.splitn(3, "---").collect();
71 if parts.len() < 3 {
72 return None;
73 }
74
75 let yaml_str = parts[1].trim();
76 let body = parts[2].trim();
77
78 let fm: RuleFrontmatter = serde_yaml::from_str(yaml_str).ok()?;
79 let condition = Regex::new(&fm.condition).ok()?;
80
81 let scope = parse_scope(fm.scope.as_deref().unwrap_or("text"));
82 let interrupt_mode = parse_interrupt_mode(fm.interrupt_mode.as_deref().unwrap_or("prose-only"));
83
84 Some(Rule {
85 name: name.to_string(),
86 content: body.to_string(),
87 description: fm.description,
88 condition: vec![condition],
89 scope,
90 interrupt_mode,
91 globs: fm.globs.unwrap_or_default(),
92 always_apply: fm.always_apply.unwrap_or(false),
93 source,
94 ast_condition: None,
95 })
96}
97
98pub fn load_all() -> Vec<Rule> {
103 raw_bundled_rules()
104 .into_iter()
105 .filter_map(|(name, content)| parse_rule_file(content, name, RuleSource::BuiltinDefaults))
106 .collect()
107}