/** * File Watcher Engine * * Provides real-time filesystem monitoring using chokidar. Watches for file changes * and triggers incremental index updates. Includes debouncing to handle rapid saves * and policy filtering to ignore unwanted changes. * * Features: * - Real-time file add/change/delete detection * - Debouncing for rapid changes (500ms) * - Policy filtering to ignore unwanted files * - Fingerprint comparison to detect actual content changes * - Graceful error handling (watcher errors don't crash server) */ import { type ChokidarOptions } from 'chokidar'; import { IndexManager } from './indexManager.js'; import { DocsIndexManager } from './docsIndexManager.js'; import { IndexingPolicy } from './indexPolicy.js'; import { FingerprintsManager } from '../storage/fingerprints.js'; import { DocsFingerprintsManager } from '../storage/docsFingerprints.js'; /** * Watch event types from chokidar */ export type WatchEvent = 'add' | 'change' | 'unlink'; /** * File event information */ export interface FileEvent { /** Type of file system event */ type: WatchEvent; /** Absolute path to the file */ path: string; /** Relative path from project root (forward-slash separated) */ relativePath: string; } /** * Watcher statistics */ export interface WatcherStats { /** Number of events processed since start */ eventsProcessed: number; /** Number of events skipped (policy/fingerprint) */ eventsSkipped: number; /** Number of index updates triggered */ indexUpdates: number; /** Number of errors encountered */ errors: number; /** Timestamp when watcher was started */ startedAt: number | null; } /** * Default debounce delay in milliseconds */ export declare const DEFAULT_DEBOUNCE_DELAY = 500; /** * Stability threshold for awaitWriteFinish */ export declare const STABILITY_THRESHOLD = 500; /** * Poll interval for awaitWriteFinish */ export declare const POLL_INTERVAL = 100; /** * Windows polling interval in milliseconds * Used when usePolling is enabled (Windows file watching) * 300ms is a good balance between responsiveness and CPU usage */ export declare const WINDOWS_POLL_INTERVAL = 300; /** * Windows binary file polling interval in milliseconds * Can be higher since binary files change less frequently */ export declare const WINDOWS_BINARY_POLL_INTERVAL = 500; /** * Maximum restart attempts on error */ export declare const MAX_RESTART_ATTEMPTS = 3; /** * Delay before restart attempt in milliseconds */ export declare const RESTART_DELAY_MS = 5000; /** * Chokidar watcher options * * Windows-specific configuration: * - usePolling: Required for Windows/network drives for reliable change detection * - interval: Throttle polling to avoid high CPU usage (Bug #18) * - binaryInterval: Higher interval for binary files which change less frequently */ export declare const WATCHER_OPTIONS: ChokidarOptions; /** * Callback to check if reconciliation is in progress * Used for SMCP-057: Prevent race condition between FileWatcher and IntegrityEngine */ export type ReconciliationCheckCallback = () => boolean; /** * File Watcher for real-time filesystem monitoring * * Watches a project directory for file changes and triggers incremental * index updates. Includes debouncing and policy filtering. * * SECURITY FIX (SMCP-057): Coordinates with IntegrityEngine to prevent race conditions * during reconciliation. Events are queued when reconciliation is in progress. * * @example * ```typescript * const watcher = new FileWatcher( * '/path/to/project', * '/path/to/index', * indexManager, * policy, * fingerprints * ); * * await watcher.start(); * * // Later, when done * await watcher.stop(); * ``` */ export declare class FileWatcher { private readonly projectPath; private readonly indexPath; private readonly indexManager; private readonly policy; private readonly fingerprints; private readonly debounceDelay; /** Optional DocsIndexManager for routing doc file changes */ private readonly docsIndexManager?; /** Optional docs fingerprints for doc file change detection */ private readonly docsFingerprints?; /** * SMCP-057: Optional callback to check if reconciliation is in progress * When provided, file events will be queued during reconciliation to prevent race conditions */ private readonly isReconciling?; /** * SMCP-057: Queue for events that occurred during reconciliation * These events are processed after reconciliation completes */ private reconciliationEventQueue; private watcher; private isRunning; private stats; /** * Map of pending events for debouncing * Key: relative path, Value: timeout handle */ private pendingEvents; /** * Queue of events being processed to avoid concurrent updates to same file */ private processingQueue; /** * Reference to cleanup handler for unregistration */ private cleanupHandler; /** * Number of restart attempts after errors */ private restartAttempts; /** * Timer for scheduled restart */ private restartTimer; /** * Create a new FileWatcher instance * * @param projectPath - Absolute path to the project root * @param indexPath - Absolute path to the index directory * @param indexManager - IndexManager instance for updates * @param policy - IndexingPolicy instance for filtering * @param fingerprints - FingerprintsManager instance for change detection * @param debounceDelay - Debounce delay in milliseconds (default: 500) * @param docsIndexManager - Optional DocsIndexManager for doc file routing * @param docsFingerprints - Optional DocsFingerprintsManager for doc file change detection * @param isReconciling - Optional callback to check if reconciliation is in progress (SMCP-057) */ constructor(projectPath: string, indexPath: string, indexManager: IndexManager, policy: IndexingPolicy, fingerprints: FingerprintsManager, debounceDelay?: number, docsIndexManager?: DocsIndexManager, docsFingerprints?: DocsFingerprintsManager, isReconciling?: ReconciliationCheckCallback); /** * Start watching the project directory * * @throws Error if watcher is already running */ start(): Promise; /** * Stop watching the project directory */ stop(): Promise; /** * Check if the watcher is currently running * * @returns true if watcher is running */ isWatching(): boolean; /** * Get watcher statistics * * @returns WatcherStats object */ getStats(): WatcherStats; /** * Get the project path being watched */ getProjectPath(): string; /** * Get the index path */ getIndexPath(): string; /** * SMCP-057: Get the count of events queued during reconciliation */ getQueuedEventCount(): number; /** * SMCP-057: Process any events that were queued during reconciliation * * This should be called after reconciliation completes to ensure * any file changes that occurred during reconciliation are processed. */ processQueuedEvents(): Promise; /** * Handle file add event */ private onAdd; /** * Handle file change event */ private onChange; /** * Handle file unlink (delete) event */ private onUnlink; /** * Handle watcher error * * Implements error recovery by attempting to restart the watcher * after a delay, up to MAX_RESTART_ATTEMPTS times. */ private onError; /** * Restart the file watcher * * Stops the current watcher and starts a new one. * Used for error recovery. */ restart(): Promise; /** * Get the current restart attempt count */ getRestartAttempts(): number; /** * Reset the restart attempt counter * Useful after manual intervention or successful long-running operation */ resetRestartAttempts(): void; /** * Handle a file event (add/change/unlink) * * Applies debouncing and queues the event for processing. * * SMCP-057: If reconciliation is in progress, the event is queued * to be processed after reconciliation completes. */ private handleFileEvent; /** * Debounce an event * * If multiple events for the same file occur within the debounce window, * only the last one will be processed. * * Fixes: * - Bug #2: Race condition in processingQueue check is fixed by atomic check-and-add * - Bug #4: Unhandled promise rejection is fixed by wrapping in IIFE with try/catch * * DoS Protection: * - Limits the number of pending events to prevent memory exhaustion * - Warns when approaching limit * - Rejects new events when limit is exceeded */ private debounceEvent; /** * Process a file event * * This is called after debouncing. It checks the policy, compares * fingerprints, and triggers index updates if needed. */ private processFileEvent; /** * Handle add or change event * * 1. Check if file is a doc file (route to DocsIndexManager if provided) * 2. Check if file passes policy * 3. Calculate file hash * 4. Compare with stored fingerprint * 5. Update index if changed */ private handleAddOrChange; /** * Handle add or change event for doc files * * Routes doc file changes to DocsIndexManager instead of IndexManager. */ private handleDocAddOrChange; /** * Handle unlink (delete) event * * 1. Check if file is a doc file (route to DocsIndexManager if provided) * 2. Delete chunks from index * 3. Remove from fingerprints */ private handleUnlink; /** * Handle unlink (delete) event for doc files * * Routes doc file deletions to DocsIndexManager instead of IndexManager. */ private handleDocUnlink; } /** * Create a FileWatcher for a project * * Convenience function to create a FileWatcher with all required dependencies. * * @param projectPath - Absolute path to the project root * @param indexPath - Absolute path to the index directory * @param indexManager - IndexManager instance * @param policy - IndexingPolicy instance * @param fingerprints - FingerprintsManager instance * @param debounceDelay - Optional debounce delay in milliseconds * @param docsIndexManager - Optional DocsIndexManager for doc file routing * @param docsFingerprints - Optional DocsFingerprintsManager for doc file change detection * @param isReconciling - Optional callback to check if reconciliation is in progress (SMCP-057) * @returns FileWatcher instance (not yet started) */ export declare function createFileWatcher(projectPath: string, indexPath: string, indexManager: IndexManager, policy: IndexingPolicy, fingerprints: FingerprintsManager, debounceDelay?: number, docsIndexManager?: DocsIndexManager, docsFingerprints?: DocsFingerprintsManager, isReconciling?: ReconciliationCheckCallback): FileWatcher; //# sourceMappingURL=fileWatcher.d.ts.map