Prefer AGENTS.md over CLAUDE.md for project instructions (#439)

* Prefer AGENTS.md over CLAUDE.md for project instructions

* fix: preserve CLAUDE.md fallback behavior

* fix: isolate onboarding tests and preserve legacy init

* fix: restore full fsOperations exports in test mock and align compact cwd

* Fix onboarding test isolation and init migration guidance

* Tighten init prompt coverage and onboarding copy

* Handle nested project instruction paths consistently

* Fix NEW_INIT feature gate for Bun build

---------

Co-authored-by: 赵小落 <zhaoxiaoluo@zhaoxiaoluodeMac-mini.local>
Co-authored-by: zhaomo01 <zhaomo01@baidu.com>
This commit is contained in:
ZhaoXiaoLuo
2026-04-12 21:31:33 +08:00
committed by GitHub
parent 2e0e14d713
commit b3f3dc4e66
18 changed files with 521 additions and 105 deletions

View File

@@ -3,7 +3,7 @@
*
* 1. Managed memory (eg. /etc/claude-code/CLAUDE.md) - Global instructions for all users
* 2. User memory (~/.claude/CLAUDE.md) - Private global instructions for all projects
* 3. Project memory (CLAUDE.md, .claude/CLAUDE.md, and .claude/rules/*.md in project roots) - Instructions checked into the codebase
* 3. Project memory (AGENTS.md or fallback CLAUDE.md, plus .claude/CLAUDE.md and .claude/rules/*.md in project roots) - Instructions checked into the codebase
* 4. Local memory (CLAUDE.local.md in project roots) - Private project-specific instructions
*
* Files are loaded in reverse order of priority, i.e. the latest files are highest priority
@@ -13,7 +13,8 @@
* - User memory is loaded from the user's home directory
* - Project and Local files are discovered by traversing from the current directory up to root
* - Files closer to the current directory have higher priority (loaded later)
* - CLAUDE.md, .claude/CLAUDE.md, and all .md files in .claude/rules/ are checked in each directory for Project memory
* - AGENTS.md is preferred for root project instructions; CLAUDE.md is only used when AGENTS.md is absent
* - .claude/CLAUDE.md and all .md files in .claude/rules/ are checked in each directory for Project memory
*
* Memory @include directive:
* - Memory files can include other files using @ notation
@@ -75,6 +76,10 @@ import {
import type { MemoryType } from './memory/types.js'
import { expandPath } from './path.js'
import { pathInWorkingPath } from './permissions/filesystem.js'
import {
getProjectInstructionFilePath,
isProjectInstructionFileName,
} from './projectInstructions.js'
import { isSettingSourceEnabled } from './settings/constants.js'
import { getInitialSettings } from './settings/settings.js'
@@ -868,7 +873,7 @@ export const getMemoryFiles = memoize(
// When running from a git worktree nested inside its main repo (e.g.,
// .claude/worktrees/<name>/ from `claude -w`), the upward walk passes
// through both the worktree root and the main repo root. Both contain
// checked-in files like CLAUDE.md and .claude/rules/*.md, so the same
// checked-in files like AGENTS.md/CLAUDE.md and .claude/rules/*.md, so the same
// content gets loaded twice. Skip Project-type (checked-in) files from
// directories above the worktree but within the main repo — the worktree
// already has its own checkout. CLAUDE.local.md is gitignored so it only
@@ -892,9 +897,12 @@ export const getMemoryFiles = memoize(
pathInWorkingPath(dir, canonicalRoot) &&
!pathInWorkingPath(dir, gitRoot)
// Try reading CLAUDE.md (Project) - only if projectSettings is enabled
// Try reading the root project instruction file (AGENTS.md first, otherwise CLAUDE.md)
if (isSettingSourceEnabled('projectSettings') && !skipProject) {
const projectPath = join(dir, 'CLAUDE.md')
const projectPath = getProjectInstructionFilePath(
dir,
getFsImplementation().existsSync,
)
result.push(
...(await processMemoryFile(
projectPath,
@@ -942,15 +950,18 @@ export const getMemoryFiles = memoize(
}
}
// Process CLAUDE.md from additional directories (--add-dir) if env var is enabled
// Process root project instruction files from additional directories (--add-dir) if env var is enabled
// This is controlled by CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD and defaults to off
// Note: we don't check isSettingSourceEnabled('projectSettings') here because --add-dir
// is an explicit user action and the SDK defaults settingSources to [] when not specified
if (isEnvTruthy(process.env.CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD)) {
const additionalDirs = getAdditionalDirectoriesForClaudeMd()
for (const dir of additionalDirs) {
// Try reading CLAUDE.md from the additional directory
const projectPath = join(dir, 'CLAUDE.md')
// Try reading the root project instruction file from the additional directory
const projectPath = getProjectInstructionFilePath(
dir,
getFsImplementation().existsSync,
)
result.push(
...(await processMemoryFile(
projectPath,
@@ -1248,7 +1259,7 @@ export async function getManagedAndUserConditionalRules(
/**
* Gets memory files for a single nested directory (between CWD and target).
* Loads CLAUDE.md, unconditional rules, and conditional rules for that directory.
* Loads the root project instruction file, unconditional rules, and conditional rules for that directory.
*
* @param dir The directory to process
* @param targetPath The target file path (for conditional rule matching)
@@ -1262,9 +1273,12 @@ export async function getMemoryFilesForNestedDirectory(
): Promise<MemoryFileInfo[]> {
const result: MemoryFileInfo[] = []
// Process project memory files (CLAUDE.md and .claude/CLAUDE.md)
// Process project memory files (AGENTS.md first, otherwise CLAUDE.md, plus .claude/CLAUDE.md)
if (isSettingSourceEnabled('projectSettings')) {
const projectPath = join(dir, 'CLAUDE.md')
const projectPath = getProjectInstructionFilePath(
dir,
getFsImplementation().existsSync,
)
result.push(
...(await processMemoryFile(
projectPath,
@@ -1439,13 +1453,13 @@ export async function shouldShowClaudeMdExternalIncludesWarning(): Promise<boole
}
/**
* Check if a file path is a memory file (CLAUDE.md, CLAUDE.local.md, or .claude/rules/*.md)
* Check if a file path is a memory file (AGENTS.md, CLAUDE.md, CLAUDE.local.md, or .claude/rules/*.md)
*/
export function isMemoryFilePath(filePath: string): boolean {
const name = basename(filePath)
// CLAUDE.md or CLAUDE.local.md anywhere
if (name === 'CLAUDE.md' || name === 'CLAUDE.local.md') {
// Root instruction files or CLAUDE.local.md anywhere
if (isProjectInstructionFileName(name) || name === 'CLAUDE.local.md') {
return true
}