opi_coding_agent/
context_files.rs1use std::path::{Path, PathBuf};
10
11const MAX_CONTEXT_FILE_SIZE: u64 = 128 * 1024;
13
14const CONTEXT_FILE_NAMES: &[&str] = &["AGENTS.md", "CLAUDE.md"];
16
17pub struct ContextFiles {
19 pub content: String,
21 pub files_loaded: usize,
23}
24
25pub fn discover_context_files(cwd: &Path, global_config_dir: Option<&Path>) -> ContextFiles {
31 let stop_at = find_git_root(cwd);
32 let mut parts: Vec<String> = Vec::new();
33
34 let mut current: Option<&Path> = Some(cwd);
36 while let Some(dir) = current {
37 load_dir_context(dir, &mut parts);
38 if stop_at.as_deref() == Some(dir) {
39 break;
40 }
41 current = dir.parent();
42 }
43
44 if let Some(global_dir) = global_config_dir {
46 load_dir_context(global_dir, &mut parts);
47 }
48
49 if parts.is_empty() {
50 return ContextFiles {
51 content: String::new(),
52 files_loaded: 0,
53 };
54 }
55
56 ContextFiles {
57 content: parts.join("\n\n"),
58 files_loaded: parts.len(),
59 }
60}
61
62fn load_dir_context(dir: &Path, parts: &mut Vec<String>) {
63 for name in CONTEXT_FILE_NAMES {
64 let path = dir.join(name);
65 if let Some(content) = read_context_file(&path) {
66 parts.push(format!("--- {name} ---\n{content}"));
67 }
68 }
69}
70
71fn read_context_file(path: &Path) -> Option<String> {
72 let metadata = std::fs::metadata(path).ok()?;
73
74 if metadata.len() > MAX_CONTEXT_FILE_SIZE {
76 return None;
77 }
78
79 let content = std::fs::read_to_string(path).ok()?;
80
81 if content.trim().is_empty() {
83 return None;
84 }
85
86 Some(content)
87}
88
89fn find_git_root(start: &Path) -> Option<PathBuf> {
91 let mut current = Some(start);
92 while let Some(dir) = current {
93 if dir.join(".git").exists() {
94 return Some(dir.to_path_buf());
95 }
96 current = dir.parent();
97 }
98 None
99}