MAESTRO: Merge PRs #920 and #699 - Add project exclusion and folder CLAUDE.md exclusion settings

Cherry-picked both PRs to main (both had merge conflicts with current main).

PR #920 (@Spunky84): CLAUDE_MEM_EXCLUDED_PROJECTS setting with glob patterns
to exclude entire projects from memory tracking (privacy/confidentiality).
Early-exit in session-init and observation handlers. 11 unit tests.

PR #699 (@leepokai): CLAUDE_MEM_FOLDER_MD_EXCLUDE setting with JSON array
of paths to exclude from CLAUDE.md file generation (fixes SwiftUI/Xcode
build conflicts and drizzle kit migration failures). Closes #620.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-02-06 05:23:01 -05:00
parent 24ce746b91
commit bf439043cf
8 changed files with 237 additions and 2 deletions
+10
View File
@@ -8,6 +8,9 @@ import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js'
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
import { logger } from '../../utils/logger.js';
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
import { isProjectExcluded } from '../../utils/project-filter.js';
import { SettingsDefaultsManager } from '../../shared/SettingsDefaultsManager.js';
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
export const observationHandler: EventHandler = {
async execute(input: NormalizedHookInput): Promise<HookResult> {
@@ -37,6 +40,13 @@ export const observationHandler: EventHandler = {
throw new Error(`Missing cwd in PostToolUse hook input for session ${sessionId}, tool ${toolName}`);
}
// Check if project is excluded from tracking
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
if (isProjectExcluded(cwd, settings.CLAUDE_MEM_EXCLUDED_PROJECTS)) {
logger.debug('HOOK', 'Project excluded from tracking, skipping observation', { cwd, toolName });
return { continue: true, suppressOutput: true };
}
// Send to worker - worker handles privacy check and database operations
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/observations`, {
method: 'POST',
+10
View File
@@ -9,6 +9,9 @@ import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js
import { getProjectName } from '../../utils/project-name.js';
import { logger } from '../../utils/logger.js';
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
import { isProjectExcluded } from '../../utils/project-filter.js';
import { SettingsDefaultsManager } from '../../shared/SettingsDefaultsManager.js';
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
export const sessionInitHandler: EventHandler = {
async execute(input: NormalizedHookInput): Promise<HookResult> {
@@ -21,6 +24,13 @@ export const sessionInitHandler: EventHandler = {
const { sessionId, cwd, prompt: rawPrompt } = input;
// Check if project is excluded from tracking
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
if (cwd && isProjectExcluded(cwd, settings.CLAUDE_MEM_EXCLUDED_PROJECTS)) {
logger.info('HOOK', 'Project excluded from tracking', { cwd });
return { continue: true, suppressOutput: true };
}
// Handle image-only prompts (where text prompt is empty/undefined)
// Use placeholder so sessions still get created and tracked for memory
const prompt = (!rawPrompt || !rawPrompt.trim()) ? '[media prompt]' : rawPrompt;