feat: Allow configuring hints filename(s) (#3269)

Signed-off-by: Rami Chowdhury <rami.chowdhury@gmail.com>
This commit is contained in:
Rami Chowdhury
2025-07-31 13:06:08 -04:00
committed by GitHub
parent 71c8d9e7aa
commit d9313aca57
3 changed files with 83 additions and 29 deletions
+77 -27
View File
@@ -461,40 +461,57 @@ impl DeveloperRouter {
}, },
}; };
// choose_app_strategy().config_dir() let hints_filenames: Vec<String> = std::env::var("CONTEXT_FILE_NAMES")
// - macOS/Linux: ~/.config/goose/ .ok()
// - Windows: ~\AppData\Roaming\Block\goose\config\ .and_then(|s| serde_json::from_str(&s).ok())
// keep previous behavior of expanding ~/.config in case this fails .unwrap_or_else(|| vec![".goosehints".to_string()]);
let global_hints_path = choose_app_strategy(crate::APP_STRATEGY.clone())
.map(|strategy| strategy.in_config_dir(".goosehints"))
.unwrap_or_else(|_| {
PathBuf::from(shellexpand::tilde("~/.config/goose/.goosehints").to_string())
});
// Create the directory if it doesn't exist let mut global_hints_contents = Vec::with_capacity(hints_filenames.len());
let _ = std::fs::create_dir_all(global_hints_path.parent().unwrap()); let mut local_hints_contents = Vec::with_capacity(hints_filenames.len());
// Check for local hints in current directory for hints_filename in &hints_filenames {
let local_hints_path = cwd.join(".goosehints"); // Global hints
// choose_app_strategy().config_dir()
// - macOS/Linux: ~/.config/goose/
// - Windows: ~\AppData\Roaming\Block\goose\config\
// keep previous behavior of expanding ~/.config in case this fails
let global_hints_path = choose_app_strategy(crate::APP_STRATEGY.clone())
.map(|strategy| strategy.in_config_dir(hints_filename))
.unwrap_or_else(|_| {
let path_str = format!("~/.config/goose/{}", hints_filename);
PathBuf::from(shellexpand::tilde(&path_str).to_string())
});
// Read global hints if they exist if let Some(parent) = global_hints_path.parent() {
let mut hints = String::new(); let _ = std::fs::create_dir_all(parent);
if global_hints_path.is_file() { }
if let Ok(global_hints) = std::fs::read_to_string(&global_hints_path) {
hints.push_str("\n### Global Hints\nThe developer extension includes some global hints that apply to all projects & directories.\n"); if global_hints_path.is_file() {
hints.push_str(&global_hints); if let Ok(content) = std::fs::read_to_string(&global_hints_path) {
global_hints_contents.push(content);
}
}
let local_hints_path = cwd.join(hints_filename);
if local_hints_path.is_file() {
if let Ok(content) = std::fs::read_to_string(&local_hints_path) {
local_hints_contents.push(content);
}
} }
} }
// Read local hints if they exist let mut hints = String::new();
if local_hints_path.is_file() { if !global_hints_contents.is_empty() {
if let Ok(local_hints) = std::fs::read_to_string(&local_hints_path) { hints.push_str("\n### Global Hints\nThe developer extension includes some global hints that apply to all projects & directories.\n");
if !hints.is_empty() { hints.push_str(&global_hints_contents.join("\n"));
hints.push_str("\n\n"); }
}
hints.push_str("### Project Hints\nThe developer extension includes some hints for working on the project in this directory.\n"); if !local_hints_contents.is_empty() {
hints.push_str(&local_hints); if !hints.is_empty() {
hints.push_str("\n\n");
} }
hints.push_str("### Project Hints\nThe developer extension includes some hints for working on the project in this directory.\n");
hints.push_str(&local_hints_contents.join("\n"));
} }
// Return base instructions directly when no hints are found // Return base instructions directly when no hints are found
@@ -1749,6 +1766,39 @@ mod tests {
temp_dir.close().unwrap(); temp_dir.close().unwrap();
} }
#[test]
#[serial]
fn test_goosehints_multiple_filenames() {
let dir = TempDir::new().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
std::env::set_var("CONTEXT_FILE_NAMES", r#"["CLAUDE.md", ".goosehints"]"#);
fs::write("CLAUDE.md", "Custom hints file content from CLAUDE.md").unwrap();
fs::write(".goosehints", "Custom hints file content from .goosehints").unwrap();
let router = DeveloperRouter::new();
let instructions = router.instructions();
assert!(instructions.contains("Custom hints file content from CLAUDE.md"));
assert!(instructions.contains("Custom hints file content from .goosehints"));
std::env::remove_var("CONTEXT_FILE_NAMES");
}
#[test]
#[serial]
fn test_goosehints_configurable_filename() {
let dir = TempDir::new().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
std::env::set_var("CONTEXT_FILE_NAMES", r#"["CLAUDE.md"]"#);
fs::write("CLAUDE.md", "Custom hints file content").unwrap();
let router = DeveloperRouter::new();
let instructions = router.instructions();
assert!(instructions.contains("Custom hints file content"));
assert!(!instructions.contains(".goosehints")); // Make sure it's not loading the default
std::env::remove_var("CONTEXT_FILE_NAMES");
}
#[tokio::test] #[tokio::test]
#[serial] #[serial]
#[cfg(windows)] #[cfg(windows)]
+2 -2
View File
@@ -27,7 +27,7 @@ LLMs have context windows, which are limits on how much conversation history the
Turning on too many extensions can degrade performance. Enable only essential [extensions and tools](/docs/guides/managing-tools/tool-permissions) to improve tool selection accuracy, save context window space, and stay within provider tool limits. Turning on too many extensions can degrade performance. Enable only essential [extensions and tools](/docs/guides/managing-tools/tool-permissions) to improve tool selection accuracy, save context window space, and stay within provider tool limits.
### Teach Goose your preferences ### Teach Goose your preferences
Help Goose remember how you like to work by using [`.goosehints`](/docs/guides/using-goosehints/) for permanent project preferences and the [Memory extension](/docs/mcp/memory-mcp) for things you want Goose to dynamically recall later. Both can help save valuable context window space while keeping your preferences available. Help Goose remember how you like to work by using [`.goosehints` or other context files](/docs/guides/using-goosehints/) for permanent project preferences and the [Memory extension](/docs/mcp/memory-mcp) for things you want Goose to dynamically recall later. Both can help save valuable context window space while keeping your preferences available.
### Protect sensitive files ### Protect sensitive files
Goose is often eager to make changes. You can stop it from changing specific files by creating a [.gooseignore](/docs/guides/using-gooseignore) file. In this file, you can list all the file paths you want it to avoid. Goose is often eager to make changes. You can stop it from changing specific files by creating a [.gooseignore](/docs/guides/using-gooseignore) file. In this file, you can list all the file paths you want it to avoid.
@@ -45,4 +45,4 @@ You can turn a successful session into a reusable "[recipe](/docs/guides/recipes
You dont need to get it right the first time. Iterating on prompts and tools is part of the workflow. You dont need to get it right the first time. Iterating on prompts and tools is part of the workflow.
### Keep Goose updated ### Keep Goose updated
Regularly [update](/docs/guides/updating-goose) Goose to benefit from the latest features, bug fixes, and performance improvements. Regularly [update](/docs/guides/updating-goose) Goose to benefit from the latest features, bug fixes, and performance improvements.
@@ -39,6 +39,10 @@ Goose supports two types of hint files:
You can use both global and local hints at the same time. When both exist, Goose will consider both your global preferences and project-specific requirements. If the instructions in your local hints file conflict with your global preferences, Goose will prioritize the local hints. You can use both global and local hints at the same time. When both exist, Goose will consider both your global preferences and project-specific requirements. If the instructions in your local hints file conflict with your global preferences, Goose will prioritize the local hints.
::: :::
:::tip
You can name your context files differently -- e.g. `AGENTS.md` -- and Goose can still pick them up. Configure the `CONTEXT_FILE_NAMES` setting!
:::
<Tabs groupId="interface"> <Tabs groupId="interface">
<TabItem value="ui" label="Goose Desktop" default> <TabItem value="ui" label="Goose Desktop" default>