feat: agent override file (#5215)

Add a file that overrides `AGENTS.md` but is not versioned (for local
devs)
This commit is contained in:
jif-oai
2025-10-15 17:46:01 +01:00
committed by GitHub
parent 8a281cd1f4
commit 897d4d5f17
3 changed files with 41 additions and 14 deletions

1
.gitignore vendored
View File

@@ -30,6 +30,7 @@ result
# cli tools # cli tools
CLAUDE.md CLAUDE.md
.claude/ .claude/
AGENTS.override.md
# caches # caches
.cache/ .cache/

View File

@@ -28,6 +28,8 @@ use crate::model_family::find_family_for_model;
use crate::model_provider_info::ModelProviderInfo; use crate::model_provider_info::ModelProviderInfo;
use crate::model_provider_info::built_in_model_providers; use crate::model_provider_info::built_in_model_providers;
use crate::openai_model_info::get_model_info; use crate::openai_model_info::get_model_info;
use crate::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME;
use crate::protocol::AskForApproval; use crate::protocol::AskForApproval;
use crate::protocol::SandboxPolicy; use crate::protocol::SandboxPolicy;
use anyhow::Context; use anyhow::Context;
@@ -1217,20 +1219,18 @@ impl Config {
} }
fn load_instructions(codex_dir: Option<&Path>) -> Option<String> { fn load_instructions(codex_dir: Option<&Path>) -> Option<String> {
let mut p = match codex_dir { let base = codex_dir?;
Some(p) => p.to_path_buf(), for candidate in [LOCAL_PROJECT_DOC_FILENAME, DEFAULT_PROJECT_DOC_FILENAME] {
None => return None, let mut path = base.to_path_buf();
}; path.push(candidate);
if let Ok(contents) = std::fs::read_to_string(&path) {
p.push("AGENTS.md"); let trimmed = contents.trim();
std::fs::read_to_string(&p).ok().and_then(|s| { if !trimmed.is_empty() {
let s = s.trim(); return Some(trimmed.to_string());
if s.is_empty() { }
None
} else {
Some(s.to_string())
} }
}) }
None
} }
fn get_base_instructions( fn get_base_instructions(

View File

@@ -21,6 +21,8 @@ use tracing::error;
/// Default filename scanned for project-level docs. /// Default filename scanned for project-level docs.
pub const DEFAULT_PROJECT_DOC_FILENAME: &str = "AGENTS.md"; pub const DEFAULT_PROJECT_DOC_FILENAME: &str = "AGENTS.md";
/// Preferred local override for project-level docs.
pub const LOCAL_PROJECT_DOC_FILENAME: &str = "AGENTS.override.md";
/// When both `Config::instructions` and the project doc are present, they will /// When both `Config::instructions` and the project doc are present, they will
/// be concatenated with the following separator. /// be concatenated with the following separator.
@@ -178,7 +180,8 @@ pub fn discover_project_doc_paths(config: &Config) -> std::io::Result<Vec<PathBu
fn candidate_filenames<'a>(config: &'a Config) -> Vec<&'a str> { fn candidate_filenames<'a>(config: &'a Config) -> Vec<&'a str> {
let mut names: Vec<&'a str> = let mut names: Vec<&'a str> =
Vec::with_capacity(1 + config.project_doc_fallback_filenames.len()); Vec::with_capacity(2 + config.project_doc_fallback_filenames.len());
names.push(LOCAL_PROJECT_DOC_FILENAME);
names.push(DEFAULT_PROJECT_DOC_FILENAME); names.push(DEFAULT_PROJECT_DOC_FILENAME);
for candidate in &config.project_doc_fallback_filenames { for candidate in &config.project_doc_fallback_filenames {
let candidate = candidate.as_str(); let candidate = candidate.as_str();
@@ -381,6 +384,29 @@ mod tests {
assert_eq!(res, "root doc\n\ncrate doc"); assert_eq!(res, "root doc\n\ncrate doc");
} }
/// AGENTS.override.md is preferred over AGENTS.md when both are present.
#[tokio::test]
async fn agents_local_md_preferred() {
let tmp = tempfile::tempdir().expect("tempdir");
fs::write(tmp.path().join(DEFAULT_PROJECT_DOC_FILENAME), "versioned").unwrap();
fs::write(tmp.path().join(LOCAL_PROJECT_DOC_FILENAME), "local").unwrap();
let cfg = make_config(&tmp, 4096, None);
let res = get_user_instructions(&cfg)
.await
.expect("local doc expected");
assert_eq!(res, "local");
let discovery = discover_project_doc_paths(&cfg).expect("discover paths");
assert_eq!(discovery.len(), 1);
assert_eq!(
discovery[0].file_name().unwrap().to_string_lossy(),
LOCAL_PROJECT_DOC_FILENAME
);
}
/// When AGENTS.md is absent but a configured fallback exists, the fallback is used. /// When AGENTS.md is absent but a configured fallback exists, the fallback is used.
#[tokio::test] #[tokio::test]
async fn uses_configured_fallback_when_agents_missing() { async fn uses_configured_fallback_when_agents_missing() {