codetether_agent/agent/
builtin.rs1use super::{AgentInfo, AgentMode};
4use std::path::Path;
5
6pub fn build_agent() -> AgentInfo {
8 AgentInfo {
9 name: "build".to_string(),
10 description: Some("Full access agent for development work".to_string()),
11 mode: AgentMode::Primary,
12 native: true,
13 hidden: false,
14 model: None,
15 temperature: None,
16 top_p: None,
17 max_steps: Some(100),
18 }
19}
20
21pub fn plan_agent() -> AgentInfo {
23 AgentInfo {
24 name: "plan".to_string(),
25 description: Some("Read-only agent for analysis and code exploration".to_string()),
26 mode: AgentMode::Primary,
27 native: true,
28 hidden: false,
29 model: None,
30 temperature: None,
31 top_p: None,
32 max_steps: Some(50),
33 }
34}
35
36pub fn explore_agent() -> AgentInfo {
38 AgentInfo {
39 name: "explore".to_string(),
40 description: Some("Fast agent for exploring codebases".to_string()),
41 mode: AgentMode::Subagent,
42 native: true,
43 hidden: false,
44 model: None,
45 temperature: None,
46 top_p: None,
47 max_steps: Some(20),
48 }
49}
50
51#[allow(dead_code)]
53pub const BUILD_SYSTEM_PROMPT: &str = r#"You are an expert AI programming assistant called CodeTether Agent.
54
55You help users with software development tasks including:
56- Writing and editing code
57- Debugging and fixing issues
58- Implementing new features
59- Refactoring and improving code quality
60- Explaining code and concepts
61
62You have access to tools that let you:
63- Read and write files
64- Run shell commands
65- Search the codebase
66- List directories
67
68Always:
69- Be concise and helpful
70- Show your work by using tools
71- Explain what you're doing
72- Ask clarifying questions when needed
73- Follow best practices for the language/framework being used
74
75Current working directory: {cwd}
76"#;
77
78#[allow(dead_code)]
80pub const PLAN_SYSTEM_PROMPT: &str = r#"You are an expert AI assistant for code analysis and planning.
81
82Your role is to:
83- Explore and understand codebases
84- Analyze code structure and architecture
85- Plan changes and refactoring
86- Answer questions about the code
87
88You have read-only access to the codebase. You can:
89- Read files
90- Search the codebase
91- List directories
92- Run safe commands
93
94You should NOT modify any files. If the user wants changes, explain what should be changed and suggest switching to the build agent.
95
96Current working directory: {cwd}
97"#;
98
99#[allow(dead_code)]
104pub const EXPLORE_SYSTEM_PROMPT: &str = r#"You are a fast, focused agent for codebase exploration.
105
106Your job is to quickly find relevant code and information. Use tools efficiently:
107- Use glob to find files by pattern
108- Use grep to search for text
109- Use list to see directory contents
110- Read files to get details
111
112Be thorough but fast. Return the most relevant results without unnecessary exploration.
113"#;
114
115pub fn load_agents_md(start_dir: &Path) -> Option<(String, std::path::PathBuf)> {
118 let mut current = start_dir.to_path_buf();
119 let repo_root = find_git_root(start_dir);
120
121 loop {
122 let agents_path = current.join("AGENTS.md");
123 if agents_path.exists() {
124 if let Ok(content) = std::fs::read_to_string(&agents_path) {
125 return Some((content, agents_path));
126 }
127 }
128
129 if repo_root.as_ref() == Some(¤t) {
131 break;
132 }
133
134 if !current.pop() {
136 break;
137 }
138 }
139
140 None
141}
142
143pub fn load_all_agents_md(start_dir: &Path) -> Vec<(String, std::path::PathBuf)> {
147 let mut results = Vec::new();
148 let mut current = start_dir.to_path_buf();
149 let repo_root = find_git_root(start_dir);
150
151 loop {
152 let agents_path = current.join("AGENTS.md");
153 if agents_path.exists() {
154 if let Ok(content) = std::fs::read_to_string(&agents_path) {
155 results.push((content, agents_path));
156 }
157 }
158
159 if repo_root.as_ref() == Some(¤t) {
161 break;
162 }
163
164 if !current.pop() {
166 break;
167 }
168 }
169
170 results
171}
172
173fn find_git_root(start_dir: &Path) -> Option<std::path::PathBuf> {
174 let mut current = start_dir.to_path_buf();
175
176 loop {
177 if current.join(".git").exists() {
178 return Some(current);
179 }
180
181 if !current.pop() {
182 break;
183 }
184 }
185
186 None
187}
188
189pub fn build_system_prompt(cwd: &Path) -> String {
191 let base_prompt = BUILD_SYSTEM_PROMPT.replace("{cwd}", &cwd.display().to_string());
192
193 let agents_files = load_all_agents_md(cwd);
195
196 if agents_files.is_empty() {
197 return base_prompt;
198 }
199
200 let mut agents_section = String::new();
202 agents_section.push_str("\n\n## Project Instructions (AGENTS.md)\n\n");
203 agents_section
204 .push_str("The following instructions were loaded from AGENTS.md files in the project.\n");
205 agents_section
206 .push_str("Follow these project-specific guidelines when working on this codebase.\n\n");
207
208 for (content, path) in agents_files.iter().rev() {
210 agents_section.push_str(&format!("### From {}\n\n", path.display()));
211 agents_section.push_str(content);
212 agents_section.push_str("\n\n");
213 }
214
215 format!("{base_prompt}{agents_section}")
216}
217
218#[allow(dead_code)]
220pub fn build_plan_system_prompt(cwd: &Path) -> String {
221 let base_prompt = PLAN_SYSTEM_PROMPT.replace("{cwd}", &cwd.display().to_string());
222
223 let agents_files = load_all_agents_md(cwd);
225
226 if agents_files.is_empty() {
227 return base_prompt;
228 }
229
230 let mut agents_section = String::new();
231 agents_section.push_str("\n\n## Project Instructions (AGENTS.md)\n\n");
232
233 for (content, path) in agents_files.iter().rev() {
234 agents_section.push_str(&format!("### From {}\n\n", path.display()));
235 agents_section.push_str(content);
236 agents_section.push_str("\n\n");
237 }
238
239 format!("{base_prompt}{agents_section}")
240}
241
242#[cfg(test)]
243mod tests {
244 use super::{load_agents_md, load_all_agents_md};
245 use tempfile::tempdir;
246
247 #[test]
248 fn load_agents_md_stops_at_git_root() {
249 let tmp = tempdir().expect("tempdir");
250 let parent_agents = tmp.path().join("AGENTS.md");
251 std::fs::write(&parent_agents, "parent instructions").expect("write parent AGENTS");
252
253 let repo_root = tmp.path().join("repo");
254 let nested = repo_root.join("src/nested");
255 std::fs::create_dir_all(repo_root.join(".git")).expect("create .git dir");
256 std::fs::create_dir_all(&nested).expect("create nested dir");
257 let repo_agents = repo_root.join("AGENTS.md");
258 std::fs::write(&repo_agents, "repo instructions").expect("write repo AGENTS");
259
260 let loaded = load_agents_md(&nested).expect("expected AGENTS");
261 assert_eq!(loaded.1, repo_agents);
262 assert_eq!(loaded.0, "repo instructions");
263 }
264
265 #[test]
266 fn load_all_agents_md_collects_within_repo_only() {
267 let tmp = tempdir().expect("tempdir");
268 let parent_agents = tmp.path().join("AGENTS.md");
269 std::fs::write(&parent_agents, "parent instructions").expect("write parent AGENTS");
270
271 let repo_root = tmp.path().join("repo");
272 let subdir = repo_root.join("service");
273 let nested = subdir.join("src");
274 std::fs::create_dir_all(repo_root.join(".git")).expect("create .git dir");
275 std::fs::create_dir_all(&nested).expect("create nested dir");
276 let repo_agents = repo_root.join("AGENTS.md");
277 std::fs::write(&repo_agents, "repo instructions").expect("write repo AGENTS");
278 let sub_agents = subdir.join("AGENTS.md");
279 std::fs::write(&sub_agents, "sub instructions").expect("write sub AGENTS");
280
281 let loaded = load_all_agents_md(&nested);
282
283 assert_eq!(loaded.len(), 2);
284 assert_eq!(loaded[0].1, sub_agents);
285 assert_eq!(loaded[1].1, repo_agents);
286 assert!(!loaded.iter().any(|(_, path)| path == &parent_agents));
287 }
288}