feat(coding-agent): support AGENTS.override.md (#7681)

This commit is contained in:
m3 2026-08-05 15:24:31 -04:00 • committed by GitHub
parent 3852cb2b81
commit 8ecf8a9883
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 41 additions and 5 deletions

View file

@ -324,6 +324,8 @@ Pi loads `AGENTS.md` (or `CLAUDE.md`) at startup from:
- Parent directories (walking up from cwd)
- Current directory
If a directory contains `AGENTS.override.md`, Pi loads it instead of `AGENTS.md` or `CLAUDE.md` from that directory. Context files from other directories are still concatenated.
Use for project instructions (`AGENTS.md`/`CLAUDE.md`), conventions, common commands. All matching files are concatenated.
Disable context file loading with `--no-context-files` (or `-nc`).

View file

@ -100,6 +100,8 @@ Pi loads:
- `~/.pi/agent/AGENTS.md` for global instructions
- `AGENTS.md` or `CLAUDE.md` from parent directories and the current directory
If a directory contains `AGENTS.override.md`, Pi loads it instead of `AGENTS.md` or `CLAUDE.md` from that directory.
Restart pi, or run `/reload`, after changing context files.
## Common things to try

View file

@ -24,7 +24,7 @@ Trusting a project allows pi to load project resources that require trust, inclu
- missing project packages configured through project settings
- project-local extensions and project package-managed extensions
Declining trust skips protected resources. `AGENTS.md` and `CLAUDE.md` context files are loaded regardless of project trust unless context loading is disabled. Before trust is resolved, pi only loads context files, user/global extensions, and CLI `-e` extensions. User/global and CLI extensions can handle the `project_trust` event; the first extension that returns a yes/no decision owns the decision.
Declining trust skips protected resources. Context files such as `AGENTS.override.md`, `AGENTS.md`, and `CLAUDE.md` are loaded regardless of project trust unless context loading is disabled. Before trust is resolved, pi only loads context files, user/global extensions, and CLI `-e` extensions. User/global and CLI extensions can handle the `project_trust` event; the first extension that returns a yes/no decision owns the decision.
Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, `defaultProjectTrust: "ask"` and `"never"` ignore such resources, while `"always"` trusts them. Use `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.

View file

@ -103,6 +103,8 @@ Pi loads `AGENTS.md` or `CLAUDE.md` at startup from:
- parent directories, walking up from the current working directory
- the current directory
If a directory contains `AGENTS.override.md`, Pi loads it instead of `AGENTS.md` or `CLAUDE.md` from that directory. Context files from other directories still layer normally.
Use context files for project conventions, commands, safety rules, and preferences. Disable loading with `--no-context-files` or `-nc`.
### System Prompt Files

View file

@ -68,7 +68,7 @@ function resolvePromptInput(input: string | undefined, description: string): str
}
function loadContextFileFromDir(dir: string): { path: string; content: string } | null {
const candidates = ["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"];
const candidates = ["AGENTS.override.md", "AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"];
for (const filename of candidates) {
const filePath = join(dir, filename);
if (existsSync(filePath)) {
@ -90,7 +90,7 @@ function loadContextFileFromDir(dir: string): { path: string; content: string }
/**
* The main repo's context file that a nested linked worktree's own copy shadows: both
* are the same tracked AGENTS.md/CLAUDE.md, so loading both loads it twice. Returns
* occupy the same logical repository scope, so loading both applies that context twice. Returns
* undefined when nothing is shadowed, leaving normal ancestor inheritance alone.
*
* Returned canonicalized (realpath), because `git worktree add` writes the `.git`

View file

@ -34,7 +34,7 @@ interface CompactReadClassification {
label: string;
}
const COMPACT_RESOURCE_FILE_NAMES = new Set(["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
const COMPACT_RESOURCE_FILE_NAMES = new Set(["AGENTS.override.md", "AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
/**
* Pluggable operations for the read tool.

View file

@ -355,7 +355,27 @@ Content`,
expect(agentsFiles.some((f) => f.path.includes("AGENTS.md"))).toBe(true);
});
it("should prefer AGENTS.override.md within each directory while preserving ancestor layering", async () => {
const nestedCwd = join(cwd, "service");
mkdirSync(nestedCwd);
writeFileSync(join(agentDir, "AGENTS.md"), "global instructions");
writeFileSync(join(agentDir, "AGENTS.override.md"), "global override");
writeFileSync(join(cwd, "AGENTS.md"), "project instructions");
writeFileSync(join(nestedCwd, "AGENTS.md"), "service instructions");
writeFileSync(join(nestedCwd, "AGENTS.override.md"), "service override");
const loader = new DefaultResourceLoader({ cwd: nestedCwd, agentDir });
await loader.reload();
expect(loader.getAgentsFiles().agentsFiles).toEqual([
{ path: join(agentDir, "AGENTS.override.md"), content: "global override" },
{ path: join(cwd, "AGENTS.md"), content: "project instructions" },
{ path: join(nestedCwd, "AGENTS.override.md"), content: "service override" },
]);
});
it("should ignore context file candidates that are directories", async () => {
mkdirSync(join(cwd, "AGENTS.override.md"));
mkdirSync(join(cwd, "AGENTS.md"));
writeFileSync(join(cwd, "CLAUDE.md"), "Fallback instructions");
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
@ -368,10 +388,12 @@ Content`,
content: "Fallback instructions",
});
expect(consoleError).not.toHaveBeenCalledWith(expect.stringContaining(join(cwd, "AGENTS.md")));
expect(consoleError).not.toHaveBeenCalledWith(expect.stringContaining(join(cwd, "AGENTS.override.md")));
consoleError.mockRestore();
});
it("should skip AGENTS.md and CLAUDE.md discovery when noContextFiles is true", async () => {
it("should skip context file discovery when noContextFiles is true", async () => {
writeFileSync(join(cwd, "AGENTS.override.md"), "# Override Guidelines\n\nBe helpful.");
writeFileSync(join(cwd, "AGENTS.md"), "# Project Guidelines\n\nBe helpful.");
writeFileSync(join(cwd, "CLAUDE.md"), "# Claude Guidelines\n\nBe helpful.");

View file

@ -461,6 +461,14 @@ describe("ToolExecutionComponent parity", () => {
hidden: "Hidden resource instructions",
absent: undefined,
},
{
title: "AGENTS.override.md",
path: join(process.cwd(), ".pi", "AGENTS.override.md"),
content: "Hidden override instructions",
compact: "read resource .pi/AGENTS.override.md",
hidden: "Hidden override instructions",
absent: undefined,
},
{
title: "outside AGENTS.md",
path: resolve(process.cwd(), "..", "AGENTS.md"),