Skip to content
Open
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions .changeset/load-claude-md.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@moonshot-ai/agent-core": minor
"@moonshot-ai/kimi-code": minor
---

Load `CLAUDE.md` as a fallback when discovering project/user memory. `AGENTS.md`/`agents.md` still take priority in each directory; `CLAUDE.md` is only read when neither exists, so repositories that already use Claude Code's `CLAUDE.md` work without maintaining a duplicate `AGENTS.md`.
38 changes: 26 additions & 12 deletions packages/agent-core/src/profile/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,35 @@ export async function loadAgentsMd(kaos: Kaos): Promise<string> {

// User-level files come first so any project-level AGENTS.md overrides them.
const home = kaos.gethome();
await collect(join(home, '.kimi-code', 'AGENTS.md'));

// Generic user-level dir (.agents) matches skill discovery.
const genericDirs = [join(home, '.agents')];
const genericFiles = genericDirs.flatMap((dir) =>
['AGENTS.md', 'agents.md'].map((name) => join(dir, name)),
);
for (const file of genericFiles) {
if (await collect(file)) break;
const foundBrandedUser = await collect(join(home, '.kimi-code', 'AGENTS.md'));

// Generic user-level dir (.agents) matches skill discovery. Load the first
// AGENTS-style file found; fall back to ~/.claude/CLAUDE.md (Claude Code's
// documented global memory path) only when no user-level AGENTS file exists
// at all — including the branded ~/.kimi-code/AGENTS.md checked above.
let foundGenericUser = false;
for (const name of ['AGENTS.md', 'agents.md']) {
if (await collect(join(home, '.agents', name))) { foundGenericUser = true; break; }
}
if (!foundBrandedUser && !foundGenericUser) {
await collect(join(home, '.claude', 'CLAUDE.md'));
}

for (const dir of dirs) {
await collect(join(dir, '.kimi-code', 'AGENTS.md'));
for (const fileName of ['AGENTS.md', 'agents.md']) {
if (await collect(join(dir, fileName))) break;
// Kimi-branded override takes highest priority within this scope.
const foundKimiCode = await collect(join(dir, '.kimi-code', 'AGENTS.md'));
// Standard AGENTS.md / agents.md (second priority).
let foundRegular = false;
for (const name of ['AGENTS.md', 'agents.md']) {
if (await collect(join(dir, name))) { foundRegular = true; break; }
}
// CLAUDE.md paths are Claude Code compatibility fallbacks. Only check them
// when no AGENTS-style file exists anywhere in this directory scope, so that
// .kimi-code/AGENTS.md (or a top-level AGENTS.md) fully supersedes them.
if (!foundKimiCode && !foundRegular) {
if (!(await collect(join(dir, 'CLAUDE.md')))) {
await collect(join(dir, '.claude', 'CLAUDE.md'));
}
}
}

Expand Down
82 changes: 82 additions & 0 deletions packages/agent-core/test/profile/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,88 @@ describe('loadAgentsMd user-level discovery', () => {
expect(result).not.toContain(homeDir);
});

it('loads CLAUDE.md when no AGENTS.md is present', async () => {
await writeFile(join(workDir, 'CLAUDE.md'), 'claude instructions', 'utf-8');

const result = await loadAgentsMd(testKaos);

expect(result).toContain('claude instructions');
});

it('prefers AGENTS.md over CLAUDE.md in the same directory', async () => {
await writeFile(join(workDir, 'AGENTS.md'), 'agents instructions', 'utf-8');
await writeFile(join(workDir, 'CLAUDE.md'), 'claude instructions', 'utf-8');

const result = await loadAgentsMd(testKaos);

expect(result).toContain('agents instructions');
expect(result).not.toContain('claude instructions');
});

it('loads ~/.claude/CLAUDE.md when no .agents file exists', async () => {
await mkdir(join(homeDir, '.claude'), { recursive: true });
await writeFile(join(homeDir, '.claude', 'CLAUDE.md'), 'global claude memory', 'utf-8');

const result = await loadAgentsMd(testKaos);

expect(result).toContain('global claude memory');
});

it('does not load ~/.claude/CLAUDE.md when ~/.kimi-code/AGENTS.md exists', async () => {
await mkdir(join(homeDir, '.kimi-code'), { recursive: true });
await writeFile(join(homeDir, '.kimi-code', 'AGENTS.md'), 'kimi user memory', 'utf-8');
await mkdir(join(homeDir, '.claude'), { recursive: true });
await writeFile(join(homeDir, '.claude', 'CLAUDE.md'), 'global claude memory', 'utf-8');

const result = await loadAgentsMd(testKaos);

expect(result).toContain('kimi user memory');
expect(result).not.toContain('global claude memory');
});

it('prefers .agents/AGENTS.md over ~/.claude/CLAUDE.md', async () => {
await mkdir(join(homeDir, '.agents'), { recursive: true });
await writeFile(join(homeDir, '.agents', 'AGENTS.md'), 'dot-agents', 'utf-8');
await mkdir(join(homeDir, '.claude'), { recursive: true });
await writeFile(join(homeDir, '.claude', 'CLAUDE.md'), 'global claude memory', 'utf-8');

const result = await loadAgentsMd(testKaos);

expect(result).toContain('dot-agents');
expect(result).not.toContain('global claude memory');
});

it('loads .claude/CLAUDE.md in the project directory', async () => {
await mkdir(join(workDir, '.claude'), { recursive: true });
await writeFile(join(workDir, '.claude', 'CLAUDE.md'), 'dot-claude instructions', 'utf-8');

const result = await loadAgentsMd(testKaos);

expect(result).toContain('dot-claude instructions');
});

it('prefers bare CLAUDE.md over .claude/CLAUDE.md in the same directory', async () => {
await writeFile(join(workDir, 'CLAUDE.md'), 'bare claude', 'utf-8');
await mkdir(join(workDir, '.claude'), { recursive: true });
await writeFile(join(workDir, '.claude', 'CLAUDE.md'), 'dot-claude', 'utf-8');

const result = await loadAgentsMd(testKaos);

expect(result).toContain('bare claude');
expect(result).not.toContain('dot-claude');
});

it('does not load CLAUDE.md when .kimi-code/AGENTS.md exists in the same scope', async () => {
await mkdir(join(workDir, '.kimi-code'), { recursive: true });
await writeFile(join(workDir, '.kimi-code', 'AGENTS.md'), 'kimi override', 'utf-8');
await writeFile(join(workDir, 'CLAUDE.md'), 'claude instructions', 'utf-8');

const result = await loadAgentsMd(testKaos);

expect(result).toContain('kimi override');
expect(result).not.toContain('claude instructions');
});

it('does not load the same file twice when the work dir is the home dir', async () => {
vi.spyOn(testKaos, 'getcwd').mockReturnValue(homeDir);
await mkdir(join(homeDir, '.kimi-code'), { recursive: true });
Expand Down