MAESTRO: Prevent CLAUDE.md generation in unsafe directories (PR #929 concept)
Add exclusion list for directories where CLAUDE.md generation breaks toolchains: res/ (Android aapt2), .git/, build/, node_modules/, __pycache__/. Closes issue #912. Credit to @jayvenn21. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,8 @@ These all modify `src/utils/claude-md-utils.ts` — review together.
|
||||
- [x] Review PR #834 (`fix: detect subdirectories inside git repos to prevent CLAUDE.md pollution` by @Glucksberg). File: `src/utils/claude-md-utils.ts`. Steps: (1) `gh pr checkout 834` (2) Review git repo detection — should check for `.git` directory to avoid creating CLAUDE.md inside nested repos (3) Run `npm run build` (4) If clean: `gh pr merge 834 --rebase --delete-branch`
|
||||
- **CLOSED — WOULD DISABLE FOLDER CLAUDE.MD FEATURE** (2026-02-06): PR replaces `isProjectRoot()` (checks if folder directly contains `.git`) with `isInsideGitRepo()` (walks up parent directories). Since `isInsideGitRepo()` returns `true` for ALL subdirectories inside any git repo, this would skip CLAUDE.md generation for every subfolder in every git project — effectively disabling the core folder CLAUDE.md feature. The underlying pollution issues (#793, #778) are real but have been addressed through targeted fixes: PR #836 (duplicate path segments), PR #974 (race conditions), and path validation improvements. Remaining unsafe-directory issues (`.git/refs/`, `node_modules/`) are addressed by PR #929's exclusion list approach. Credit to @Glucksberg for flagging the issue.
|
||||
|
||||
- [ ] Review PR #929 (`Prevent CLAUDE.md generation in Android res/ and other unsafe directories` by @jayvenn21). File: `src/utils/claude-md-utils.ts`. Steps: (1) `gh pr checkout 929` (2) Review exclusion list — should include `res/`, `node_modules/`, `.git/`, etc. (3) Run `npm run build` (4) If clean: `gh pr merge 929 --rebase --delete-branch`
|
||||
- [x] Review PR #929 (`Prevent CLAUDE.md generation in Android res/ and other unsafe directories` by @jayvenn21). File: `src/utils/claude-md-utils.ts`. Steps: (1) `gh pr checkout 929` (2) Review exclusion list — should include `res/`, `node_modules/`, `.git/`, etc. (3) Run `npm run build` (4) If clean: `gh pr merge 929 --rebase --delete-branch`
|
||||
- **CLOSED — FIX APPLIED ON MAIN** (2026-02-06): PR was based on an older version of the file and would have merge conflicts on the insertion point. Applied the exact approach to main: added `EXCLUDED_UNSAFE_DIRECTORIES` Set containing `res`, `.git`, `build`, `node_modules`, `__pycache__` and `isExcludedUnsafeDirectory()` function that checks if any path segment matches the exclusion list. The check is placed in `updateFolderClaudeMdFiles` after the project root check but before the active CLAUDE.md race condition check. Added 7 new tests (53 total for claude-md-utils) covering all excluded directories, deeply nested cases, and safe directory pass-through. Build passes. Credit to @jayvenn21 for identifying issue #912 and proposing a clean solution.
|
||||
|
||||
## Folder CLAUDE.md Setting (winner from Phase 02 dedup)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -257,6 +257,27 @@ export function formatTimelineForClaudeMd(timelineText: string): string {
|
||||
return lines.join('\n').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in directory names where CLAUDE.md generation is unsafe or undesirable.
|
||||
* e.g. Android res/ is compiler-strict (non-XML breaks build); .git, build, node_modules are tooling-owned.
|
||||
*/
|
||||
const EXCLUDED_UNSAFE_DIRECTORIES = new Set([
|
||||
'res',
|
||||
'.git',
|
||||
'build',
|
||||
'node_modules',
|
||||
'__pycache__'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Returns true if folder path contains any excluded segment (e.g. .../res/..., .../node_modules/...).
|
||||
*/
|
||||
function isExcludedUnsafeDirectory(folderPath: string): boolean {
|
||||
const normalized = path.normalize(folderPath);
|
||||
const segments = normalized.split(path.sep);
|
||||
return segments.some(segment => EXCLUDED_UNSAFE_DIRECTORIES.has(segment));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a folder is a project root (contains .git directory).
|
||||
* Project root CLAUDE.md files should remain user-managed, not auto-updated.
|
||||
@@ -331,6 +352,11 @@ export async function updateFolderClaudeMdFiles(
|
||||
logger.debug('FOLDER_INDEX', 'Skipping project root CLAUDE.md', { folderPath });
|
||||
continue;
|
||||
}
|
||||
// Skip known-unsafe directories (e.g. Android res/, .git, build, node_modules)
|
||||
if (isExcludedUnsafeDirectory(folderPath)) {
|
||||
logger.debug('FOLDER_INDEX', 'Skipping unsafe directory for CLAUDE.md', { folderPath });
|
||||
continue;
|
||||
}
|
||||
// Skip folders where CLAUDE.md was read/modified in this observation (issue #859)
|
||||
if (foldersWithActiveClaudeMd.has(folderPath)) {
|
||||
logger.debug('FOLDER_INDEX', 'Skipping folder with active CLAUDE.md to avoid race condition', { folderPath });
|
||||
|
||||
@@ -834,3 +834,110 @@ describe('issue #859 - skip folders with active CLAUDE.md', () => {
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('issue #912 - skip unsafe directories for CLAUDE.md generation', () => {
|
||||
it('should skip node_modules directories', async () => {
|
||||
const fetchMock = mock(() => Promise.resolve({ ok: true } as Response));
|
||||
global.fetch = fetchMock;
|
||||
|
||||
await updateFolderClaudeMdFiles(
|
||||
['node_modules/lodash/index.js'],
|
||||
'test-project',
|
||||
37777,
|
||||
tempDir
|
||||
);
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip .git directories', async () => {
|
||||
const fetchMock = mock(() => Promise.resolve({ ok: true } as Response));
|
||||
global.fetch = fetchMock;
|
||||
|
||||
await updateFolderClaudeMdFiles(
|
||||
['.git/refs/heads/main'],
|
||||
'test-project',
|
||||
37777,
|
||||
tempDir
|
||||
);
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip Android res/ directories', async () => {
|
||||
const fetchMock = mock(() => Promise.resolve({ ok: true } as Response));
|
||||
global.fetch = fetchMock;
|
||||
|
||||
await updateFolderClaudeMdFiles(
|
||||
['app/src/main/res/layout/activity_main.xml'],
|
||||
'test-project',
|
||||
37777,
|
||||
tempDir
|
||||
);
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip build/ directories', async () => {
|
||||
const fetchMock = mock(() => Promise.resolve({ ok: true } as Response));
|
||||
global.fetch = fetchMock;
|
||||
|
||||
await updateFolderClaudeMdFiles(
|
||||
['build/outputs/apk/debug/app-debug.apk'],
|
||||
'test-project',
|
||||
37777,
|
||||
tempDir
|
||||
);
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip __pycache__/ directories', async () => {
|
||||
const fetchMock = mock(() => Promise.resolve({ ok: true } as Response));
|
||||
global.fetch = fetchMock;
|
||||
|
||||
await updateFolderClaudeMdFiles(
|
||||
['src/__pycache__/module.cpython-311.pyc'],
|
||||
'test-project',
|
||||
37777,
|
||||
tempDir
|
||||
);
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should allow safe directories like src/', async () => {
|
||||
const apiResponse = {
|
||||
content: [{ text: '| #123 | 4:30 PM | 🔵 | Test | ~100 |' }]
|
||||
};
|
||||
const fetchMock = mock(() => Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(apiResponse)
|
||||
} as Response));
|
||||
global.fetch = fetchMock;
|
||||
|
||||
await updateFolderClaudeMdFiles(
|
||||
['src/utils/file.ts'],
|
||||
'test-project',
|
||||
37777,
|
||||
tempDir
|
||||
);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should skip deeply nested unsafe directories', async () => {
|
||||
const fetchMock = mock(() => Promise.resolve({ ok: true } as Response));
|
||||
global.fetch = fetchMock;
|
||||
|
||||
// node_modules nested deep inside project
|
||||
await updateFolderClaudeMdFiles(
|
||||
['packages/frontend/node_modules/react/index.js'],
|
||||
'test-project',
|
||||
37777,
|
||||
tempDir
|
||||
);
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user