use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct ContextDiscovery {
cwd: PathBuf,
}
impl ContextDiscovery {
pub fn new() -> Result<Self, std::io::Error> {
let cwd = std::env::current_dir()?;
Ok(Self { cwd })
}
pub fn from_dir(cwd: PathBuf) -> Self {
Self { cwd }
}
pub fn discover_agents_md(&self) -> Vec<PathBuf> {
self.discover_file("AGENTS.md")
}
pub fn discover_system_md(&self) -> Vec<PathBuf> {
self.discover_file("SYSTEM.md")
}
fn discover_file(&self, filename: &str) -> Vec<PathBuf> {
let mut found = Vec::new();
let cwd_file = self.cwd.join(filename);
if cwd_file.exists() {
found.push(cwd_file);
}
let mut current = self.cwd.as_path();
let home = dirs::home_dir();
while let Some(parent) = current.parent() {
if let Some(ref home_path) = home
&& parent == home_path
{
break;
}
if parent == Path::new("/") {
break;
}
let parent_file = parent.join(filename);
if parent_file.exists() {
found.push(parent_file);
}
current = parent;
}
if let Some(home_path) = home {
let global_file = home_path.join(".saorsa").join(filename);
if global_file.exists() {
found.push(global_file);
}
}
found
}
}
impl Default for ContextDiscovery {
fn default() -> Self {
Self::new().unwrap_or_else(|_| Self {
cwd: PathBuf::from("."),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn make_temp_dir() -> TempDir {
match TempDir::new() {
Ok(t) => t,
Err(e) => unreachable!("Failed to create temp dir: {e}"),
}
}
fn create_file(dir: &Path, name: &str) -> PathBuf {
let path = dir.join(name);
assert!(fs::write(&path, "test content").is_ok());
path
}
#[test]
fn test_discovery_finds_cwd_file() {
let temp = make_temp_dir();
let cwd = temp.path().to_path_buf();
create_file(&cwd, "AGENTS.md");
let discovery = ContextDiscovery::from_dir(cwd.clone());
let found = discovery.discover_agents_md();
assert_eq!(found.len(), 1);
assert_eq!(found[0], cwd.join("AGENTS.md"));
}
#[test]
fn test_discovery_empty_when_no_files() {
let temp = make_temp_dir();
let cwd = temp.path().to_path_buf();
let discovery = ContextDiscovery::from_dir(cwd);
let found = discovery.discover_agents_md();
assert!(found.is_empty());
}
#[test]
fn test_discovery_walks_parent_directories() {
let temp = make_temp_dir();
let parent = temp.path().to_path_buf();
let child = parent.join("child");
let grandchild = child.join("grandchild");
assert!(fs::create_dir_all(&grandchild).is_ok());
create_file(&parent, "AGENTS.md");
create_file(&child, "AGENTS.md");
let discovery = ContextDiscovery::from_dir(grandchild.clone());
let found = discovery.discover_agents_md();
assert_eq!(found.len(), 2);
assert_eq!(found[0], child.join("AGENTS.md"));
assert_eq!(found[1], parent.join("AGENTS.md"));
}
#[test]
fn test_precedence_ordering_cwd_over_parent() {
let temp = make_temp_dir();
let parent = temp.path().to_path_buf();
let child = parent.join("child");
assert!(fs::create_dir(&child).is_ok());
create_file(&parent, "AGENTS.md");
create_file(&child, "AGENTS.md");
let discovery = ContextDiscovery::from_dir(child.clone());
let found = discovery.discover_agents_md();
assert_eq!(found.len(), 2);
assert_eq!(found[0], child.join("AGENTS.md"));
assert_eq!(found[1], parent.join("AGENTS.md"));
}
#[test]
fn test_system_md_discovery_works_same_way() {
let temp = make_temp_dir();
let cwd = temp.path().to_path_buf();
create_file(&cwd, "SYSTEM.md");
let discovery = ContextDiscovery::from_dir(cwd.clone());
let found = discovery.discover_system_md();
assert_eq!(found.len(), 1);
assert_eq!(found[0], cwd.join("SYSTEM.md"));
}
#[test]
fn test_discovery_filters_nonexistent_paths() {
let temp = make_temp_dir();
let parent = temp.path().to_path_buf();
let child = parent.join("child");
assert!(fs::create_dir(&child).is_ok());
create_file(&parent, "AGENTS.md");
let discovery = ContextDiscovery::from_dir(child);
let found = discovery.discover_agents_md();
assert_eq!(found.len(), 1);
assert_eq!(found[0], parent.join("AGENTS.md"));
}
#[test]
fn test_new_uses_current_dir() {
let result = ContextDiscovery::new();
assert!(result.is_ok());
match result {
Ok(discovery) => {
assert!(discovery.cwd.is_absolute() || discovery.cwd == *".");
}
Err(_) => unreachable!("new() should succeed"),
}
}
#[test]
fn test_default_creates_valid_instance() {
let discovery = ContextDiscovery::default();
assert!(!discovery.cwd.as_os_str().is_empty());
}
}