use std::path::Path;
pub fn load_agents_md(project_dir: &Path) -> Option<String> {
let candidates: &[&str] = &[".xcodeai/AGENTS.md", "AGENTS.md", ".agents.md", "agents.md"];
for relative_path in candidates {
let full_path = project_dir.join(relative_path);
if full_path.is_file() {
match std::fs::read_to_string(&full_path) {
Ok(content) if !content.trim().is_empty() => {
tracing::info!("Loaded AGENTS.md from: {}", full_path.display());
return Some(content);
}
Ok(_) => {
tracing::debug!("AGENTS.md at {} is empty, skipping", full_path.display());
}
Err(e) => {
tracing::warn!("Could not read AGENTS.md at {}: {}", full_path.display(), e);
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn write_file(dir: &std::path::Path, relative_path: &str, content: &str) {
let path = dir.join(relative_path);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&path, content).unwrap();
}
#[test]
fn test_no_agents_md_returns_none() {
let dir = tempfile::tempdir().expect("tempdir");
let result = load_agents_md(dir.path());
assert!(result.is_none(), "Expected None for empty directory");
}
#[test]
fn test_agents_md_is_loaded() {
let dir = tempfile::tempdir().expect("tempdir");
write_file(dir.path(), "AGENTS.md", "# Rules\nAlways use snake_case.\n");
let result = load_agents_md(dir.path());
assert!(result.is_some(), "Expected Some(content)");
assert!(result.unwrap().contains("snake_case"));
}
#[test]
fn test_xcodeai_agents_md_takes_priority() {
let dir = tempfile::tempdir().expect("tempdir");
write_file(dir.path(), ".xcodeai/AGENTS.md", "xcodeai-specific rules");
write_file(dir.path(), "AGENTS.md", "generic rules");
let result = load_agents_md(dir.path()).expect("Expected Some");
assert_eq!(result.trim(), "xcodeai-specific rules");
}
#[test]
fn test_agents_md_beats_dot_agents_md() {
let dir = tempfile::tempdir().expect("tempdir");
write_file(dir.path(), "AGENTS.md", "uppercase wins");
write_file(dir.path(), ".agents.md", "hidden variant");
let result = load_agents_md(dir.path()).expect("Expected Some");
assert_eq!(result.trim(), "uppercase wins");
}
#[test]
fn test_dot_agents_md_fallback() {
let dir = tempfile::tempdir().expect("tempdir");
write_file(dir.path(), ".agents.md", "hidden rules");
let result = load_agents_md(dir.path()).expect("Expected Some");
assert_eq!(result.trim(), "hidden rules");
}
#[test]
fn test_lowercase_agents_md_last_resort() {
let dir = tempfile::tempdir().expect("tempdir");
write_file(dir.path(), "agents.md", "lowercase rules");
let result = load_agents_md(dir.path()).expect("Expected Some");
assert_eq!(result.trim(), "lowercase rules");
}
#[test]
fn test_empty_agents_md_is_skipped() {
let dir = tempfile::tempdir().expect("tempdir");
write_file(dir.path(), "AGENTS.md", " \n \n ");
write_file(dir.path(), ".agents.md", "fallback content");
let result = load_agents_md(dir.path()).expect("Expected Some from fallback");
assert_eq!(result.trim(), "fallback content");
}
}