1use std::env;
2use std::fs;
3use std::path::Path;
4
5use codei_config::ResolvedConfig;
6
7pub fn build_system_prompt(config: &ResolvedConfig, project_instructions: &str) -> String {
8 let cwd = config.cwd.display();
9 let os = env::consts::OS;
10 let language = &config.config.defaults.language;
11
12 format!(
13 r#"You are CodeI, an AI coding assistant running in the user's local terminal.
14
15## Capabilities
16- Read, write, and edit files; search the codebase; run shell commands
17- Working directory: {cwd}
18- Operating system: {os}
19
20## Project instructions
21{project_instructions}
22
23## Tool usage
24- Read relevant files before editing code
25- Prefer specialized tools over shell for file operations
26- When using edit, ensure old_string matches exactly once
27- Do not make unrelated changes the user did not ask for
28
29## Output
30- Communicate with the user in {language}
31- Be concise and actionable"#
32 )
33}
34
35pub fn load_project_instructions(config: &ResolvedConfig) -> String {
37 let Some(root) = config.project_root.as_ref() else {
38 return String::new();
39 };
40
41 let mut sections = Vec::new();
42
43 for rel in [".codei/AGENTS.md", "AGENTS.md"] {
44 let path = root.join(rel);
45 if let Some(content) = read_file_if_exists(&path) {
46 sections.push(format!("### From `{rel}`\n{content}"));
47 break;
48 }
49 }
50
51 let rules_dir = root.join(".codei/rules");
52 if rules_dir.is_dir() {
53 let mut rule_files: Vec<_> = fs::read_dir(&rules_dir)
54 .into_iter()
55 .flatten()
56 .filter_map(|e| e.ok())
57 .filter(|e| e.path().extension().is_some_and(|ext| ext == "md"))
58 .collect();
59 rule_files.sort_by_key(|e| e.file_name());
60
61 for entry in rule_files {
62 let path = entry.path();
63 if let Some(content) = read_file_if_exists(&path) {
64 let name = entry.file_name().to_string_lossy().into_owned();
65 sections.push(format!("### Rule `{name}`\n{content}"));
66 }
67 }
68 }
69
70 sections.join("\n\n")
71}
72
73fn read_file_if_exists(path: &Path) -> Option<String> {
74 if path.is_file() {
75 fs::read_to_string(path).ok()
76 } else {
77 None
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use codei_config::ResolvedConfig;
85 use std::path::PathBuf;
86
87 #[test]
88 fn loads_rules_directory() {
89 let dir = tempfile::tempdir().unwrap();
90 let rules = dir.path().join(".codei/rules");
91 fs::create_dir_all(&rules).unwrap();
92 fs::write(rules.join("rust.md"), "Use idiomatic Rust.").unwrap();
93
94 let config = ResolvedConfig {
95 config: Default::default(),
96 cwd: dir.path().to_path_buf(),
97 project_root: Some(dir.path().to_path_buf()),
98 user_config_path: PathBuf::from("/tmp/config.toml"),
99 project_config_path: None,
100 };
101
102 let text = load_project_instructions(&config);
103 assert!(text.contains("Use idiomatic Rust."));
104 }
105}