Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 68 additions & 10 deletions crates/tui/src/project_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Comment on lines 35 to +41
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 WHALE.md in a vendor-neutral directory is self-contradictory

~/.agents/ is introduced as a vendor-neutral location, but GLOBAL_WHALE_VENDOR_NEUTRAL_PATH adds ~/.agents/WHALE.md at priority 3, ahead of ~/.agents/AGENTS.md at priority 4. A user who creates ~/.agents/AGENTS.md to share config across multiple tools could have it silently shadowed if they also happen to have ~/.agents/WHALE.md, without any warning. Lookups of WHALE.md there also only benefit codewhale, which undermines the vendor-neutral intent. Consider restricting ~/.agents/ to AGENTS.md only, or document the WHALE.md precedence explicitly.

Fix in Codex Fix in Claude Code Fix in Cursor

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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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>,
Expand All @@ -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,
];
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 ~/.agents/WHALE.md path has no test coverage

GLOBAL_WHALE_VENDOR_NEUTRAL_PATH (~/.agents/WHALE.md) is a new constant added at priority 3, but neither new test exercises it. Both new tests only write to ~/.agents/AGENTS.md. If the path-building loop for WHALE.md had a typo or the constant were mis-ordered, nothing would catch it. A test confirming ~/.agents/WHALE.md is found when that file exists (and wins over ~/.agents/AGENTS.md in the same directory) would close this gap.

Fix in Codex Fix in Claude Code Fix in Cursor


#[test]
fn test_local_and_global_agents_merge_when_both_exist() {
// #1157: when both `~/.deepseek/AGENTS.md` and a project AGENTS.md
Expand Down