1use regex::Regex;
6
7use super::types::{CustomRule, ProjectRules, RuleAction, RuleApplyResult};
8
9pub fn apply_rules(content: &str, rules: &[CustomRule]) -> RuleApplyResult {
11 let mut result = content.to_string();
12 let mut warnings = Vec::new();
13 let mut blocked = false;
14
15 for rule in rules {
16 let pattern = match &rule.pattern {
17 Some(p) => p,
18 None => continue,
19 };
20
21 let regex = match Regex::new(pattern) {
22 Ok(r) => r,
23 Err(_) => continue, };
25
26 if regex.is_match(content) {
27 match rule.action {
28 RuleAction::Deny => {
29 blocked = true;
30 warnings.push(format!(
31 "Blocked by rule \"{}\": {}",
32 rule.name,
33 rule.message.as_deref().unwrap_or("No message")
34 ));
35 }
36 RuleAction::Warn => {
37 warnings.push(format!(
38 "Warning from rule \"{}\": {}",
39 rule.name,
40 rule.message.as_deref().unwrap_or("No message")
41 ));
42 }
43 RuleAction::Transform => {
44 if let Some(ref transform) = rule.transform {
45 result = regex.replace_all(&result, transform.as_str()).to_string();
46 }
47 }
48 RuleAction::Allow => {
49 }
51 }
52 }
53 }
54
55 RuleApplyResult {
56 result,
57 warnings,
58 blocked,
59 }
60}
61
62pub fn generate_system_prompt_addition(rules: &ProjectRules) -> String {
64 let mut parts = Vec::new();
65
66 if let Some(ref instructions) = rules.instructions {
67 parts.push("## Project Instructions\n".to_string());
68 parts.push(instructions.clone());
69 parts.push(String::new());
70 }
71
72 if let Some(ref memory) = rules.memory {
73 if !memory.is_empty() {
74 parts.push("## Project Context\n".to_string());
75 for (key, value) in memory {
76 parts.push(format!("- **{}**: {}", key, value));
77 }
78 parts.push(String::new());
79 }
80 }
81
82 if let Some(ref custom_rules) = rules.custom_rules {
83 if !custom_rules.is_empty() {
84 parts.push("## Custom Rules\n".to_string());
85 for rule in custom_rules {
86 parts.push(format!(
87 "- **{}** ({:?}): {}",
88 rule.name,
89 rule.action,
90 rule.message.as_deref().unwrap_or("No description")
91 ));
92 }
93 parts.push(String::new());
94 }
95 }
96
97 parts.join("\n")
98}
99
100pub fn create_agents_md_template() -> String {
102 r#"# Project Instructions
103
104Add your project-specific instructions here. The agent will follow these when working on your codebase.
105
106## Guidelines
107
108- Describe your coding style preferences
109- List important conventions
110- Mention key architecture decisions
111
112## Memory
113
114- **Project Type**: (e.g., Web App, CLI Tool, Library)
115- **Language**: (e.g., TypeScript, Python, Rust)
116- **Framework**: (e.g., React, Express, Actix)
117
118## Allowed Tools
119
120- Read
121- Write
122- Edit
123- Bash
124- Glob
125- Grep
126
127## Rules
128
129- **No Console Logs**: Avoid adding console.log statements in production code
130- **Test Coverage**: All new features should include tests
131"#
132 .to_string()
133}
134
135pub fn init_agents_md(dir: Option<&std::path::Path>) -> Result<std::path::PathBuf, String> {
137 let target_dir = dir
138 .map(|p| p.to_path_buf())
139 .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
140
141 let file_path = target_dir.join("AGENTS.md");
142
143 if file_path.exists() {
144 return Err("AGENTS.md already exists".to_string());
145 }
146
147 let template = create_agents_md_template();
148 std::fs::write(&file_path, template).map_err(|e| format!("Failed to write file: {}", e))?;
149
150 Ok(file_path)
151}