mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
feat: load global hints from ~/.agents/AGENTS.md (#9736)
Signed-off-by: Michael Neale <michael.neale@gmail.com> Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com> Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
@@ -13,6 +13,7 @@ impl Paths {
|
||||
DirType::State => base.join("state"),
|
||||
DirType::Plugins => base.join(".agents").join("plugins"),
|
||||
DirType::Agents => base.join(".agents").join("agents"),
|
||||
DirType::AgentsHome => base.join(".agents"),
|
||||
}
|
||||
} else {
|
||||
// NOTE: "Block" is kept here for backwards compatibility with existing
|
||||
@@ -31,6 +32,7 @@ impl Paths {
|
||||
DirType::State => strategy.state_dir().unwrap_or(strategy.data_dir()),
|
||||
DirType::Plugins => strategy.home_dir().join(".agents").join("plugins"),
|
||||
DirType::Agents => strategy.home_dir().join(".agents").join("agents"),
|
||||
DirType::AgentsHome => strategy.home_dir().join(".agents"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +57,14 @@ impl Paths {
|
||||
Self::get_dir(DirType::Agents)
|
||||
}
|
||||
|
||||
pub fn agents_home_dir() -> PathBuf {
|
||||
Self::get_dir(DirType::AgentsHome)
|
||||
}
|
||||
|
||||
pub fn in_agents_home_dir(subpath: &str) -> PathBuf {
|
||||
Self::agents_home_dir().join(subpath)
|
||||
}
|
||||
|
||||
pub fn in_state_dir(subpath: &str) -> PathBuf {
|
||||
Self::state_dir().join(subpath)
|
||||
}
|
||||
@@ -74,4 +84,5 @@ enum DirType {
|
||||
State,
|
||||
Plugins,
|
||||
Agents,
|
||||
AgentsHome,
|
||||
}
|
||||
|
||||
@@ -230,17 +230,30 @@ pub fn load_hint_files(
|
||||
let mut global_hints_contents = Vec::with_capacity(hints_filenames.len());
|
||||
let mut local_hints_contents = Vec::with_capacity(hints_filenames.len());
|
||||
|
||||
for hints_filename in hints_filenames {
|
||||
let global_hints_path = Paths::in_config_dir(hints_filename);
|
||||
let mut global_hints_paths: Vec<PathBuf> = hints_filenames
|
||||
.iter()
|
||||
.map(|name| Paths::in_config_dir(name))
|
||||
.collect();
|
||||
if hints_filenames
|
||||
.iter()
|
||||
.any(|name| name == AGENTS_MD_FILENAME)
|
||||
{
|
||||
global_hints_paths.push(Paths::in_agents_home_dir(AGENTS_MD_FILENAME));
|
||||
}
|
||||
|
||||
for global_hints_path in &global_hints_paths {
|
||||
if global_hints_path.is_file() {
|
||||
let mut visited = HashSet::new();
|
||||
let hints_dir = global_hints_path.parent().unwrap();
|
||||
let global_ignore_patterns = GitignoreBuilder::new(hints_dir)
|
||||
.build()
|
||||
.unwrap_or_else(|_| Gitignore::empty());
|
||||
let expanded_content = read_referenced_files(
|
||||
&global_hints_path,
|
||||
global_hints_path,
|
||||
hints_dir,
|
||||
&mut visited,
|
||||
0,
|
||||
ignore_patterns,
|
||||
&global_ignore_patterns,
|
||||
);
|
||||
if !expanded_content.is_empty() {
|
||||
global_hints_contents.push(expanded_content);
|
||||
@@ -314,6 +327,98 @@ mod tests {
|
||||
assert!(hints.contains("Test hint content"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn test_global_agents_md_in_agents_home() {
|
||||
let root = TempDir::new().unwrap();
|
||||
std::env::set_var("GOOSE_PATH_ROOT", root.path());
|
||||
|
||||
let agents_home = root.path().join(".agents");
|
||||
fs::create_dir_all(&agents_home).unwrap();
|
||||
fs::write(
|
||||
agents_home.join(AGENTS_MD_FILENAME),
|
||||
"Global agents home instructions",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let project = TempDir::new().unwrap();
|
||||
let gitignore = create_dummy_gitignore();
|
||||
let hints = load_hint_files(
|
||||
project.path(),
|
||||
&[
|
||||
GOOSE_HINTS_FILENAME.to_string(),
|
||||
AGENTS_MD_FILENAME.to_string(),
|
||||
],
|
||||
&gitignore,
|
||||
);
|
||||
|
||||
std::env::remove_var("GOOSE_PATH_ROOT");
|
||||
|
||||
assert!(hints.contains("Global Hints"));
|
||||
assert!(hints.contains("Global agents home instructions"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn test_global_agents_md_imports_not_filtered_by_project_gitignore() {
|
||||
let root = TempDir::new().unwrap();
|
||||
std::env::set_var("GOOSE_PATH_ROOT", root.path());
|
||||
|
||||
let agents_home = root.path().join(".agents");
|
||||
fs::create_dir_all(&agents_home).unwrap();
|
||||
fs::write(agents_home.join("policy.md"), "Imported policy content").unwrap();
|
||||
fs::write(
|
||||
agents_home.join(AGENTS_MD_FILENAME),
|
||||
"Global header\n@policy.md\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let project = TempDir::new().unwrap();
|
||||
let mut builder = GitignoreBuilder::new(project.path());
|
||||
builder.add_line(None, "*.md").unwrap();
|
||||
let gitignore = builder.build().unwrap();
|
||||
|
||||
let hints = load_hint_files(
|
||||
project.path(),
|
||||
&[
|
||||
GOOSE_HINTS_FILENAME.to_string(),
|
||||
AGENTS_MD_FILENAME.to_string(),
|
||||
],
|
||||
&gitignore,
|
||||
);
|
||||
|
||||
std::env::remove_var("GOOSE_PATH_ROOT");
|
||||
|
||||
assert!(hints.contains("Imported policy content"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn test_global_agents_md_skipped_when_not_in_context_file_names() {
|
||||
let root = TempDir::new().unwrap();
|
||||
std::env::set_var("GOOSE_PATH_ROOT", root.path());
|
||||
|
||||
let agents_home = root.path().join(".agents");
|
||||
fs::create_dir_all(&agents_home).unwrap();
|
||||
fs::write(
|
||||
agents_home.join(AGENTS_MD_FILENAME),
|
||||
"Global agents home instructions",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let project = TempDir::new().unwrap();
|
||||
let gitignore = create_dummy_gitignore();
|
||||
let hints = load_hint_files(
|
||||
project.path(),
|
||||
&[GOOSE_HINTS_FILENAME.to_string()],
|
||||
&gitignore,
|
||||
);
|
||||
|
||||
std::env::remove_var("GOOSE_PATH_ROOT");
|
||||
|
||||
assert!(!hints.contains("Global agents home instructions"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_goosehints_when_missing() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user