-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat: read global AGENTS.md from ~/.agents/ as vendor-neutral fallback #2236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,10 +35,13 @@ const PROJECT_CONTEXT_FILES: &[&str] = &[ | |
|
|
||
| /// User-level project instructions loaded as a fallback when the workspace and | ||
| /// its parents do not define project context. `.codewhale/` takes priority | ||
| /// over `.deepseek/` for both WHALE.md and AGENTS.md. | ||
| /// over vendor-neutral `.agents/`, which takes priority over legacy | ||
| /// `.deepseek/`, for both WHALE.md and AGENTS.md. | ||
| const GLOBAL_AGENTS_RELATIVE_PATH: &[&str] = &[".codewhale", "AGENTS.md"]; | ||
| const GLOBAL_AGENTS_VENDOR_NEUTRAL_PATH: &[&str] = &[".agents", "AGENTS.md"]; | ||
| const GLOBAL_AGENTS_LEGACY_PATH: &[&str] = &[".deepseek", "AGENTS.md"]; | ||
| const GLOBAL_WHALE_RELATIVE_PATH: &[&str] = &[".codewhale", "WHALE.md"]; | ||
| const GLOBAL_WHALE_VENDOR_NEUTRAL_PATH: &[&str] = &[".agents", "WHALE.md"]; | ||
| const GLOBAL_WHALE_LEGACY_PATH: &[&str] = &[".deepseek", "WHALE.md"]; | ||
|
|
||
| /// Maximum size for project context files (to prevent loading huge files) | ||
|
|
@@ -436,7 +439,7 @@ fn load_project_context_with_parents_and_home( | |
| } | ||
| } | ||
|
|
||
| // Always check `~/.deepseek/AGENTS.md` so user-wide preferences | ||
| // Always check global instruction files so user-wide preferences | ||
| // travel into every session (#1157). When both global and project | ||
| // instructions exist, the global block prepends the project's so | ||
| // workspace overrides win the last word; when only global exists, | ||
|
|
@@ -486,12 +489,11 @@ fn load_project_context_with_parents_and_home( | |
| ctx | ||
| } | ||
|
|
||
| /// Combine `~/.deepseek/AGENTS.md` (global, user-wide preferences) with a | ||
| /// project-local AGENTS.md/CLAUDE.md/instructions.md. Global comes first | ||
| /// so workspace-specific rules can override it — the model reads in | ||
| /// declared order. Each block is wrapped in a labelled fence so the | ||
| /// model can tell which level any rule comes from when the two sets | ||
| /// disagree (#1157). | ||
| /// Combine global user-wide preferences with a project-local | ||
| /// AGENTS.md/CLAUDE.md/instructions.md. Global comes first so | ||
| /// workspace-specific rules can override it — the model reads in declared | ||
| /// order. Each block is wrapped in a labelled fence so the model can tell | ||
| /// which level any rule comes from when the two sets disagree (#1157). | ||
| fn merge_global_and_project_instructions( | ||
| global: &str, | ||
| global_source: Option<&Path>, | ||
|
|
@@ -513,11 +515,15 @@ fn load_global_agents_context(workspace: &Path, home_dir: Option<&Path>) -> Opti | |
| // Priority order: | ||
| // 1. ~/.codewhale/WHALE.md (CodeWhale-native) | ||
| // 2. ~/.codewhale/AGENTS.md (new config directory) | ||
| // 3. ~/.deepseek/WHALE.md (legacy fallback) | ||
| // 4. ~/.deepseek/AGENTS.md (legacy fallback) | ||
| // 3. ~/.agents/WHALE.md (vendor-neutral fallback) | ||
| // 4. ~/.agents/AGENTS.md (vendor-neutral fallback) | ||
| // 5. ~/.deepseek/WHALE.md (legacy fallback) | ||
| // 6. ~/.deepseek/AGENTS.md (legacy fallback) | ||
| let candidates: &[&[&str]] = &[ | ||
| GLOBAL_WHALE_RELATIVE_PATH, | ||
| GLOBAL_AGENTS_RELATIVE_PATH, | ||
| GLOBAL_WHALE_VENDOR_NEUTRAL_PATH, | ||
| GLOBAL_AGENTS_VENDOR_NEUTRAL_PATH, | ||
| GLOBAL_WHALE_LEGACY_PATH, | ||
| GLOBAL_AGENTS_LEGACY_PATH, | ||
| ]; | ||
|
|
@@ -1043,6 +1049,58 @@ mod tests { | |
| assert_eq!(ctx.source_path, Some(global_agents)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_load_global_agents_falls_back_to_vendor_neutral_path() { | ||
| let workspace = tempdir().expect("workspace tempdir"); | ||
| let home = tempdir().expect("home tempdir"); | ||
| let global_dir = home.path().join(".agents"); | ||
| fs::create_dir(&global_dir).expect("mkdir .agents"); | ||
| let global_agents = global_dir.join("AGENTS.md"); | ||
| fs::write(&global_agents, "Vendor-neutral instructions").expect("write global agents"); | ||
|
|
||
| let ctx = load_project_context_with_parents_and_home(workspace.path(), Some(home.path())); | ||
|
|
||
| assert!(ctx.has_instructions()); | ||
| assert!( | ||
| ctx.instructions | ||
| .as_ref() | ||
| .unwrap() | ||
| .contains("Vendor-neutral instructions") | ||
| ); | ||
| assert_eq!(ctx.source_path, Some(global_agents)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_codewhale_specific_path_wins_over_agents_path() { | ||
| let workspace = tempdir().expect("workspace tempdir"); | ||
| let home = tempdir().expect("home tempdir"); | ||
|
|
||
| let codewhale_dir = home.path().join(".codewhale"); | ||
| fs::create_dir(&codewhale_dir).expect("mkdir .codewhale"); | ||
| let codewhale_agents = codewhale_dir.join("AGENTS.md"); | ||
| fs::write(&codewhale_agents, "CodeWhale-specific instructions") | ||
| .expect("write codewhale agents"); | ||
|
|
||
| let agents_dir = home.path().join(".agents"); | ||
| fs::create_dir(&agents_dir).expect("mkdir .agents"); | ||
| fs::write(agents_dir.join("AGENTS.md"), "Vendor-neutral instructions") | ||
| .expect("write vendor-neutral agents"); | ||
|
|
||
| let ctx = load_project_context_with_parents_and_home(workspace.path(), Some(home.path())); | ||
|
|
||
| assert!(ctx.has_instructions()); | ||
| let instructions = ctx.instructions.as_ref().unwrap(); | ||
| assert!( | ||
| instructions.contains("CodeWhale-specific instructions"), | ||
| "CodeWhale-specific global file should win:\n{instructions}" | ||
| ); | ||
| assert!( | ||
| !instructions.contains("Vendor-neutral instructions"), | ||
| "lower-priority .agents file should be skipped:\n{instructions}" | ||
| ); | ||
| assert_eq!(ctx.source_path, Some(codewhale_agents)); | ||
| } | ||
|
Comment on lines
+1052
to
+1102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| #[test] | ||
| fn test_local_and_global_agents_merge_when_both_exist() { | ||
| // #1157: when both `~/.deepseek/AGENTS.md` and a project AGENTS.md | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WHALE.mdin a vendor-neutral directory is self-contradictory~/.agents/is introduced as a vendor-neutral location, butGLOBAL_WHALE_VENDOR_NEUTRAL_PATHadds~/.agents/WHALE.mdat priority 3, ahead of~/.agents/AGENTS.mdat priority 4. A user who creates~/.agents/AGENTS.mdto share config across multiple tools could have it silently shadowed if they also happen to have~/.agents/WHALE.md, without any warning. Lookups ofWHALE.mdthere also only benefit codewhale, which undermines the vendor-neutral intent. Consider restricting~/.agents/toAGENTS.mdonly, or document theWHALE.mdprecedence explicitly.