466
467
constructor(
469
>
userHome: URI,
470
>
fileService: IFileService,
471
>
logService: ILogService,
472
>
debounceMs: number = ClaudeCustomizationWatcher.DEBOUNCE_MS,
473
>
) {
474
>
super();
475
>
476
>
// URIs whose subtree (or exact file, for `.mcp.json`) signals a re-scan.
477
>
const triggers: URI[] = [];
478
>
const watch = (uri: URI, recursive: boolean) => {
479
>
try {
480
>
this._register(fileService.watch(uri, { recursive, excludes: [] }));
481
>
} catch (err) {
482
logService.warn(`[ClaudeCustomizationWatcher] failed to watch '${uri.toString()}': ${err instanceof Error ? err.message : String(err)}`);
483
}
485
>
486
>
// Trigger only on the customization sources under a `.claude` root, not
487
>
// on the root itself — that would fire on every SDK runtime write (see
488
>
// CLAUDE_CUSTOMIZATION_SUBPATHS).
489
>
const addClaudeTriggers = (base: URI) => {
490
>
for (const sub of CLAUDE_CUSTOMIZATION_SUBPATHS) {
491
>
triggers.push(URI.joinPath(base, sub));
492
>
}
493
>
};
494
>
495
>
if (workingDirectory) {
496
>
const projectClaude = URI.joinPath(workingDirectory, '.claude');
497
>
watch(projectClaude, true);
498
>
addClaudeTriggers(projectClaude);
499
>
watch(workingDirectory, false);
500
>
triggers.push(URI.joinPath(workingDirectory, '.mcp.json'));
501
>
}
502
>
const userClaude = URI.joinPath(userHome, '.claude');
503
>
watch(userClaude, true);
504
>
addClaudeTriggers(userClaude);
505
>
506
>
// Memory files (CLAUDE.md / CLAUDE.local.md) — reuse the scanner's
507
>
// canonical list so the watcher never drifts from what it actually
508
>
// reads. Entries already under a recursively-watched `.claude` root
509
>
// (e.g. `.claude/CLAUDE.md`) are harmless duplicate triggers.
510
>
triggers.push(...claudeMemoryFiles(workingDirectory, userHome));
511
>
512
>
// Collapse the raw file-change stream into a single debounced signal.
513
>
// The `DisposableStore` argument is required because `onDidChange` is a
514
>
// public property (see the `Event.debounce` leak-safety note).
515
>
this.onDidChange = Event.signal(Event.debounce(
516
>
Event.filter(fileService.onDidFilesChange, e => triggers.some(t => e.affects(t)), this._store),
517
>
(_last, e) => e,
518
>
debounceMs,
519
>
undefined,
520
>
undefined,
521
>
undefined,
522
>
this._store,
523
>
));
524
>
}
525
}