feat: Add file watcher with debounced auto-sync and comprehensive test coverage

Addresses the need for automatic graph synchronization on file changes. Implements FileWatcher using native OS file events (FSEvents/inotify/ReadDirectoryChangesW) with 2-second debouncing to prevent thrashing on rapid saves. Filters changes against include/exclude patterns and ignores .codegraph directory modifications. Integrates with CodeGraph API (watch/unwatch/isWatching methods) and MCP server for automatic activation. Updates documentation to reflect shift from semantic to full-text search and removal of manual hook installation requirements.
This commit is contained in:
Colby McHenry
2026-04-07 16:02:15 -05:00
parent 453c39d774
commit 3da5c96a0b
6 changed files with 599 additions and 30 deletions
+53
View File
@@ -48,6 +48,7 @@ import {
import { GraphTraverser, GraphQueryManager } from './graph';
import { ContextBuilder, createContextBuilder } from './context';
import { Mutex, FileLock } from './utils';
import { FileWatcher, WatchOptions } from './sync';
// Re-export types for consumers
export * from './types';
@@ -77,6 +78,7 @@ export {
defaultLogger,
} from './errors';
export { Mutex, FileLock, processInBatches, debounce, throttle, MemoryMonitor } from './utils';
export { FileWatcher, WatchOptions } from './sync';
export { MCPServer } from './mcp';
/**
@@ -140,6 +142,9 @@ export class CodeGraph {
// File lock for preventing concurrent writes across processes (CLI, MCP, git hooks)
private fileLock: FileLock;
// File watcher for auto-sync on file changes
private watcher: FileWatcher | null = null;
private constructor(
db: DatabaseConnection,
queries: QueryBuilder,
@@ -319,6 +324,7 @@ export class CodeGraph {
* Close the CodeGraph instance and release resources
*/
close(): void {
this.unwatch();
// Release file lock if held
this.fileLock.release();
this.db.close();
@@ -491,6 +497,53 @@ export class CodeGraph {
return this.indexMutex.isLocked();
}
// ===========================================================================
// File Watching
// ===========================================================================
/**
* Start watching for file changes and auto-syncing.
*
* Uses native OS file events (FSEvents on macOS, inotify on Linux 19+,
* ReadDirectoryChangesW on Windows) with debouncing to avoid thrashing.
*
* @param options - Watch options (debounce delay, callbacks)
* @returns true if watching started successfully
*/
watch(options: WatchOptions = {}): boolean {
if (this.watcher?.isActive()) return true;
this.watcher = new FileWatcher(
this.projectRoot,
this.config,
async () => {
const result = await this.sync();
const filesChanged = result.filesAdded + result.filesModified + result.filesRemoved;
return { filesChanged, durationMs: result.durationMs };
},
options
);
return this.watcher.start();
}
/**
* Stop watching for file changes.
*/
unwatch(): void {
if (this.watcher) {
this.watcher.stop();
this.watcher = null;
}
}
/**
* Check if the file watcher is active.
*/
isWatching(): boolean {
return this.watcher?.isActive() ?? false;
}
/**
* Get files that have changed since last index
*/