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:
@@ -2,7 +2,7 @@ import { c as _c } from "react-compiler-runtime";
|
||||
import { feature } from 'bun:bundle';
|
||||
import chalk from 'chalk';
|
||||
import { mkdir } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { basename, join } from 'path';
|
||||
import * as React from 'react';
|
||||
import { use, useEffect, useState } from 'react';
|
||||
import { getOriginalCwd } from '../../bootstrap/state.js';
|
||||
@@ -24,6 +24,7 @@ import { projectIsInGitRepo } from '../../utils/memory/versions.js';
|
||||
import { updateSettingsForSource } from '../../utils/settings/settings.js';
|
||||
import { Select } from '../CustomSelect/index.js';
|
||||
import { ListItem } from '../design-system/ListItem.js';
|
||||
import { getProjectMemoryPathForSelector } from './memoryFileSelectorPaths.js';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const teamMemPaths = feature('TEAMMEM') ? require('../../memdir/teamMemPaths.js') as typeof import('../../memdir/teamMemPaths.js') : null;
|
||||
@@ -48,8 +49,10 @@ export function MemoryFileSelector(t0) {
|
||||
onCancel
|
||||
} = t0;
|
||||
const existingMemoryFiles = use(getMemoryFiles());
|
||||
const originalCwd = getOriginalCwd();
|
||||
const userMemoryPath = join(getClaudeConfigHomeDir(), "CLAUDE.md");
|
||||
const projectMemoryPath = join(getOriginalCwd(), "CLAUDE.md");
|
||||
const projectMemoryPath = getProjectMemoryPathForSelector(existingMemoryFiles, originalCwd);
|
||||
const projectMemoryFileName = basename(projectMemoryPath);
|
||||
const hasUserMemory = existingMemoryFiles.some(f => f.path === userMemoryPath);
|
||||
const hasProjectMemory = existingMemoryFiles.some(f_0 => f_0.path === projectMemoryPath);
|
||||
const allMemoryFiles = [...existingMemoryFiles.filter(_temp).map(_temp2), ...(hasUserMemory ? [] : [{
|
||||
@@ -85,12 +88,12 @@ export function MemoryFileSelector(t0) {
|
||||
}
|
||||
}
|
||||
let description;
|
||||
const isGit = projectIsInGitRepo(getOriginalCwd());
|
||||
const isGit = projectIsInGitRepo(originalCwd);
|
||||
if (file.type === "User" && !file.isNested) {
|
||||
description = "Saved in ~/.claude/CLAUDE.md";
|
||||
} else {
|
||||
if (file.type === "Project" && !file.isNested && file.path === projectMemoryPath) {
|
||||
description = `${isGit ? "Checked in at" : "Saved in"} ./CLAUDE.md`;
|
||||
description = `${isGit ? "Checked in at" : "Saved in"} ./${projectMemoryFileName}`;
|
||||
} else {
|
||||
if (file.parent) {
|
||||
description = "@-imported";
|
||||
|
||||
69
src/components/memory/memoryFileSelectorPaths.test.ts
Normal file
69
src/components/memory/memoryFileSelectorPaths.test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import type { MemoryFileInfo } from '../../utils/claudemd.js'
|
||||
import { getProjectMemoryPathForSelector } from './memoryFileSelectorPaths.js'
|
||||
|
||||
function projectFile(path: string): MemoryFileInfo {
|
||||
return {
|
||||
path,
|
||||
type: 'Project',
|
||||
content: '',
|
||||
}
|
||||
}
|
||||
|
||||
describe('getProjectMemoryPathForSelector', () => {
|
||||
test('uses the loaded repo-level AGENTS.md from a nested cwd', () => {
|
||||
const repoDir = '/repo'
|
||||
const nestedDir = join(repoDir, 'packages', 'app')
|
||||
|
||||
expect(
|
||||
getProjectMemoryPathForSelector(
|
||||
[projectFile(join(repoDir, 'AGENTS.md'))],
|
||||
nestedDir,
|
||||
),
|
||||
).toBe(join(repoDir, 'AGENTS.md'))
|
||||
})
|
||||
|
||||
test('uses the loaded repo-level CLAUDE.md fallback from a nested cwd', () => {
|
||||
const repoDir = '/repo'
|
||||
const nestedDir = join(repoDir, 'packages', 'app')
|
||||
|
||||
expect(
|
||||
getProjectMemoryPathForSelector(
|
||||
[projectFile(join(repoDir, 'CLAUDE.md'))],
|
||||
nestedDir,
|
||||
),
|
||||
).toBe(join(repoDir, 'CLAUDE.md'))
|
||||
})
|
||||
|
||||
test('prefers the closest loaded ancestor instruction file', () => {
|
||||
const repoDir = '/repo'
|
||||
const nestedProjectDir = join(repoDir, 'packages', 'app')
|
||||
|
||||
expect(
|
||||
getProjectMemoryPathForSelector(
|
||||
[
|
||||
projectFile(join(repoDir, 'AGENTS.md')),
|
||||
projectFile(join(nestedProjectDir, 'CLAUDE.md')),
|
||||
],
|
||||
join(nestedProjectDir, 'src'),
|
||||
),
|
||||
).toBe(join(nestedProjectDir, 'CLAUDE.md'))
|
||||
})
|
||||
|
||||
test('defaults to a new AGENTS.md in the current cwd when no project file is loaded', () => {
|
||||
expect(getProjectMemoryPathForSelector([], '/repo/packages/app')).toBe(
|
||||
'/repo/packages/app/AGENTS.md',
|
||||
)
|
||||
})
|
||||
|
||||
test('ignores loaded project instruction files outside the current cwd ancestry', () => {
|
||||
expect(
|
||||
getProjectMemoryPathForSelector(
|
||||
[projectFile('/other-worktree/AGENTS.md')],
|
||||
'/repo/packages/app',
|
||||
),
|
||||
).toBe('/repo/packages/app/AGENTS.md')
|
||||
})
|
||||
})
|
||||
34
src/components/memory/memoryFileSelectorPaths.ts
Normal file
34
src/components/memory/memoryFileSelectorPaths.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { basename, join } from 'path'
|
||||
|
||||
import type { MemoryFileInfo } from '../../utils/claudemd.js'
|
||||
import {
|
||||
findProjectInstructionFilePathInAncestors,
|
||||
isProjectInstructionFileName,
|
||||
PRIMARY_PROJECT_INSTRUCTION_FILE,
|
||||
} from '../../utils/projectInstructions.js'
|
||||
|
||||
function isLoadedProjectInstructionFile(file: MemoryFileInfo): boolean {
|
||||
return (
|
||||
file.type === 'Project' &&
|
||||
file.parent === undefined &&
|
||||
isProjectInstructionFileName(basename(file.path))
|
||||
)
|
||||
}
|
||||
|
||||
export function getProjectMemoryPathForSelector(
|
||||
existingMemoryFiles: MemoryFileInfo[],
|
||||
cwd: string,
|
||||
): string {
|
||||
const loadedProjectInstructionPaths = new Set(
|
||||
existingMemoryFiles
|
||||
.filter(isLoadedProjectInstructionFile)
|
||||
.map(file => file.path),
|
||||
)
|
||||
|
||||
return (
|
||||
findProjectInstructionFilePathInAncestors(
|
||||
cwd,
|
||||
path => loadedProjectInstructionPaths.has(path),
|
||||
) ?? join(cwd, PRIMARY_PROJECT_INSTRUCTION_FILE)
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user