Skip to main content

aster/rules/
parser.rs

1//! AGENTS.md 解析器
2//!
3//! 解析项目指令和规则
4
5use std::collections::HashMap;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9use regex::Regex;
10
11use super::types::{AgentsMdSection, CustomRule, ProjectRules, RuleAction};
12
13/// 要查找的 AGENTS.md 文件名
14const AGENTS_MD_FILES: &[&str] = &[
15    "AGENTS.md",
16    ".agents.md",
17    "agents.md",
18    ".aster/AGENTS.md",
19    ".aster/instructions.md",
20];
21
22/// 设置文件名
23const SETTINGS_FILES: &[&str] = &[".aster/settings.json", ".aster/settings.local.json"];
24
25/// 在目录层级中查找 AGENTS.md 文件
26pub fn find_agents_md(start_dir: Option<&Path>) -> Option<PathBuf> {
27    let mut dir = start_dir
28        .map(|p| p.to_path_buf())
29        .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
30
31    // 向上遍历目录树
32    loop {
33        for filename in AGENTS_MD_FILES {
34            let file_path = dir.join(filename);
35            if file_path.exists() {
36                return Some(file_path);
37            }
38        }
39
40        match dir.parent() {
41            Some(parent) if parent != dir => dir = parent.to_path_buf(),
42            _ => break,
43        }
44    }
45
46    // 检查 home 目录
47    if let Some(home) = dirs::home_dir() {
48        let home_agents_md = home.join(".aster").join("AGENTS.md");
49        if home_agents_md.exists() {
50            return Some(home_agents_md);
51        }
52    }
53
54    None
55}
56
57/// 查找设置文件
58pub fn find_settings_files(start_dir: Option<&Path>) -> Vec<PathBuf> {
59    let dir = start_dir
60        .map(|p| p.to_path_buf())
61        .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
62
63    let mut found = Vec::new();
64
65    // 本地设置
66    for filename in SETTINGS_FILES {
67        let file_path = dir.join(filename);
68        if file_path.exists() {
69            found.push(file_path);
70        }
71    }
72
73    // 全局设置
74    if let Some(home) = dirs::home_dir() {
75        let global_settings = home.join(".aster").join("settings.json");
76        if global_settings.exists() {
77            found.push(global_settings);
78        }
79    }
80
81    found
82}
83
84/// 解析 AGENTS.md 文件
85pub fn parse_agents_md(file_path: &Path) -> Vec<AgentsMdSection> {
86    let content = match fs::read_to_string(file_path) {
87        Ok(c) => c,
88        Err(_) => return Vec::new(),
89    };
90
91    let mut sections = Vec::new();
92    let lines: Vec<&str> = content.lines().collect();
93
94    let heading_re = Regex::new(r"^(#{1,6})\s+(.+)$").unwrap();
95
96    let mut current_section: Option<AgentsMdSection> = None;
97    let mut content_lines: Vec<&str> = Vec::new();
98
99    for line in lines {
100        if let Some(caps) = heading_re.captures(line) {
101            // 保存之前的章节
102            if let Some(mut section) = current_section.take() {
103                section.content = content_lines.join("\n").trim().to_string();
104                sections.push(section);
105            }
106
107            // 开始新章节
108            current_section = Some(AgentsMdSection {
109                title: caps.get(2).unwrap().as_str().trim().to_string(),
110                content: String::new(),
111                level: caps.get(1).unwrap().as_str().len(),
112            });
113            content_lines.clear();
114        } else if current_section.is_some() {
115            content_lines.push(line);
116        } else if !line.trim().is_empty() {
117            // 第一个标题之前的内容
118            current_section = Some(AgentsMdSection {
119                title: "Instructions".to_string(),
120                content: String::new(),
121                level: 0,
122            });
123            content_lines.push(line);
124        }
125    }
126
127    // 保存最后一个章节
128    if let Some(mut section) = current_section {
129        section.content = content_lines.join("\n").trim().to_string();
130        sections.push(section);
131    }
132
133    sections
134}
135
136/// 从章节中提取规则
137pub fn extract_rules(sections: &[AgentsMdSection]) -> ProjectRules {
138    let mut rules = ProjectRules::default();
139
140    for section in sections {
141        let title_lower = section.title.to_lowercase();
142
143        if title_lower.contains("instruction") || section.level == 0 {
144            let instructions = rules.instructions.get_or_insert_with(String::new);
145            instructions.push_str(&section.content);
146            instructions.push('\n');
147        } else if title_lower.contains("allowed tool") {
148            rules.allowed_tools = Some(parse_list_from_content(&section.content));
149        } else if title_lower.contains("disallowed tool") || title_lower.contains("forbidden tool")
150        {
151            rules.disallowed_tools = Some(parse_list_from_content(&section.content));
152        } else if title_lower.contains("permission") {
153            let mode = section.content.lines().next().unwrap_or("").trim();
154            if ["default", "acceptEdits", "bypassPermissions", "plan"].contains(&mode) {
155                rules.permission_mode = Some(mode.to_string());
156            }
157        } else if title_lower.contains("model") {
158            rules.model = section.content.lines().next().map(|s| s.trim().to_string());
159        } else if title_lower.contains("system prompt") {
160            rules.system_prompt = Some(section.content.clone());
161        } else if title_lower.contains("rule") {
162            rules.custom_rules = Some(parse_custom_rules(&section.content));
163        } else if title_lower.contains("memory") || title_lower.contains("context") {
164            rules.memory = Some(parse_memory_from_content(&section.content));
165        }
166    }
167
168    rules
169}
170
171/// 从内容中解析列表项
172fn parse_list_from_content(content: &str) -> Vec<String> {
173    let list_re = Regex::new(r"^\s*[-*+]\s+(.+)$").unwrap();
174    let mut items = Vec::new();
175
176    for line in content.lines() {
177        if let Some(caps) = list_re.captures(line) {
178            items.push(caps.get(1).unwrap().as_str().trim().to_string());
179        }
180    }
181
182    items
183}
184
185/// 解析自定义规则
186fn parse_custom_rules(content: &str) -> Vec<CustomRule> {
187    let rule_re = Regex::new(r"^\s*[-*+]\s+\*\*(.+?)\*\*:\s*(.+)$").unwrap();
188    let action_re = Regex::new(r"(?i)action:\s*(allow|deny|warn|transform)").unwrap();
189    let pattern_re = Regex::new(r"(?i)pattern:\s*(.+)").unwrap();
190
191    let mut rules = Vec::new();
192    let mut current_rule: Option<CustomRule> = None;
193
194    for line in content.lines() {
195        if let Some(caps) = rule_re.captures(line) {
196            // 保存之前的规则
197            if let Some(rule) = current_rule.take() {
198                rules.push(rule);
199            }
200
201            current_rule = Some(CustomRule {
202                name: caps.get(1).unwrap().as_str().trim().to_string(),
203                pattern: None,
204                action: RuleAction::Warn,
205                message: Some(caps.get(2).unwrap().as_str().trim().to_string()),
206                transform: None,
207            });
208        } else if let Some(ref mut rule) = current_rule {
209            if let Some(caps) = action_re.captures(line) {
210                rule.action = match caps.get(1).unwrap().as_str().to_lowercase().as_str() {
211                    "allow" => RuleAction::Allow,
212                    "deny" => RuleAction::Deny,
213                    "transform" => RuleAction::Transform,
214                    _ => RuleAction::Warn,
215                };
216            }
217
218            if let Some(caps) = pattern_re.captures(line) {
219                rule.pattern = Some(caps.get(1).unwrap().as_str().trim().to_string());
220            }
221        }
222    }
223
224    if let Some(rule) = current_rule {
225        rules.push(rule);
226    }
227
228    rules
229}
230
231/// 解析记忆/上下文内容
232fn parse_memory_from_content(content: &str) -> HashMap<String, String> {
233    let memory_re = Regex::new(r"^\s*[-*+]\s+\*\*(.+?)\*\*:\s*(.+)$").unwrap();
234    let mut memory = HashMap::new();
235
236    for line in content.lines() {
237        if let Some(caps) = memory_re.captures(line) {
238            memory.insert(
239                caps.get(1).unwrap().as_str().trim().to_string(),
240                caps.get(2).unwrap().as_str().trim().to_string(),
241            );
242        }
243    }
244
245    memory
246}
247
248/// 加载所有项目规则
249pub fn load_project_rules(project_dir: Option<&Path>) -> ProjectRules {
250    let dir = project_dir
251        .map(|p| p.to_path_buf())
252        .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
253
254    let mut rules = ProjectRules::default();
255
256    // 加载 AGENTS.md
257    if let Some(agents_md_path) = find_agents_md(Some(&dir)) {
258        let sections = parse_agents_md(&agents_md_path);
259        rules = merge_rules(rules, extract_rules(&sections));
260    }
261
262    // 加载设置文件
263    for settings_path in find_settings_files(Some(&dir)) {
264        if let Ok(content) = fs::read_to_string(&settings_path) {
265            if let Ok(settings) = serde_json::from_str::<ProjectRules>(&content) {
266                rules = merge_rules(rules, settings);
267            }
268        }
269    }
270
271    rules
272}
273
274/// 合并规则(后者优先)
275fn merge_rules(base: ProjectRules, override_rules: ProjectRules) -> ProjectRules {
276    ProjectRules {
277        instructions: override_rules.instructions.or(base.instructions),
278        allowed_tools: override_rules.allowed_tools.or(base.allowed_tools),
279        disallowed_tools: override_rules.disallowed_tools.or(base.disallowed_tools),
280        permission_mode: override_rules.permission_mode.or(base.permission_mode),
281        model: override_rules.model.or(base.model),
282        system_prompt: override_rules.system_prompt.or(base.system_prompt),
283        custom_rules: match (base.custom_rules, override_rules.custom_rules) {
284            (Some(mut b), Some(o)) => {
285                b.extend(o);
286                Some(b)
287            }
288            (b, o) => o.or(b),
289        },
290        memory: match (base.memory, override_rules.memory) {
291            (Some(mut b), Some(o)) => {
292                b.extend(o);
293                Some(b)
294            }
295            (b, o) => o.or(b),
296        },
297    }
298}