use std::path::{Path, PathBuf};
const MAX_CONTEXT_FILE_SIZE: u64 = 128 * 1024;
const CONTEXT_FILE_NAMES: &[&str] = &["AGENTS.md", "CLAUDE.md"];
pub struct ContextFiles {
pub content: String,
pub files_loaded: usize,
}
pub fn discover_context_files(cwd: &Path, global_config_dir: Option<&Path>) -> ContextFiles {
let stop_at = find_git_root(cwd);
let mut parts: Vec<String> = Vec::new();
let mut current: Option<&Path> = Some(cwd);
while let Some(dir) = current {
load_dir_context(dir, &mut parts);
if stop_at.as_deref() == Some(dir) {
break;
}
current = dir.parent();
}
if let Some(global_dir) = global_config_dir {
load_dir_context(global_dir, &mut parts);
}
if parts.is_empty() {
return ContextFiles {
content: String::new(),
files_loaded: 0,
};
}
ContextFiles {
content: parts.join("\n\n"),
files_loaded: parts.len(),
}
}
fn load_dir_context(dir: &Path, parts: &mut Vec<String>) {
for name in CONTEXT_FILE_NAMES {
let path = dir.join(name);
if let Some(content) = read_context_file(&path) {
parts.push(format!("--- {name} ---\n{content}"));
}
}
}
fn read_context_file(path: &Path) -> Option<String> {
let metadata = std::fs::metadata(path).ok()?;
if metadata.len() > MAX_CONTEXT_FILE_SIZE {
return None;
}
let content = std::fs::read_to_string(path).ok()?;
if content.trim().is_empty() {
return None;
}
Some(content)
}
fn find_git_root(start: &Path) -> Option<PathBuf> {
let mut current = Some(start);
while let Some(dir) = current {
if dir.join(".git").exists() {
return Some(dir.to_path_buf());
}
current = dir.parent();
}
None
}