MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / FileWatcher

Class FileWatcher

src/sync/watcher.ts:263–974  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

261 * without blocking on a sync (issue #403)
262 */
263export class FileWatcher {
264 /** macOS/Windows: the single recursive watcher. Null on Linux. */
265 private recursiveWatcher: fs.FSWatcher | null = null;
266 /** Linux: one watcher per watched directory (keyed by absolute path). */
267 private dirWatchers = new Map<string, fs.FSWatcher>();
268 /** Set once the per-directory watch cap is hit, so we log only once. */
269 private dirCapWarned = false;
270 /**
271 * Set once the Linux inotify watch limit (ENOSPC) is hit. Double duty: we
272 * warn only once, AND we stop attempting new directory watches for the rest
273 * of the session — once the kernel budget is exhausted every further
274 * `inotify_add_watch` fails too, so trying the rest of the tree is pure
275 * waste. NON-fatal (does not degrade): installed watches keep working.
276 */
277 private inotifyLimitWarned = false;
278 /**
279 * One-way latch: the reason live watching was permanently disabled at runtime
280 * (watch-resource exhaustion, lock contention past the retry budget, or a
281 * persistent generic sync failure past the retry budget), or null while
282 * healthy. Set by {@link degrade}; cleared only by a fresh start().
283 */
284 private degradedReason: string | null = null;
285 /** Consecutive lock-contention retries for watcher-triggered syncs. */
286 private lockRetryCount = 0;
287 /** Consecutive generic (non-lock) sync failures; reset only by a clean sync. */
288 private syncFailureRetryCount = 0;
289 /** Test-only inert mode: started, but with no OS watcher installed. */
290 private inert = false;
291 private debounceTimer: ReturnType<typeof setTimeout> | null = null;
292 /**
293 * True when the pending set does NOT exactly describe the change (a
294 * directory removal's children are unknown from the event, #1285) — the
295 * next sync must be a full scan-diff. Cleared only after a successful FULL
296 * sync reconciles the tree.
297 */
298 private needsFullScan = false;
299 /**
300 * Files seen by the watcher since the last successful sync — populated on
301 * every change event, cleared at the start of a sync, and re-populated by
302 * events that arrive mid-sync (or restored on sync failure). Keyed by the
303 * same project-relative POSIX path the rest of the codebase uses, so a
304 * caller can intersect tool-response file paths against this map cheaply.
305 */
306 private pendingFiles = new Map<string, { firstSeenMs: number; lastSeenMs: number }>();
307 /**
308 * Wall-clock ms at which the in-flight sync began. Combined with
309 * {@link pendingFiles}'s `lastSeenMs`, this distinguishes "still in the
310 * debounce window" (lastSeen > syncStarted, sync hasn't started yet for
311 * this edit) from "currently being indexed" (lastSeen <= syncStarted).
312 */
313 private syncStartedMs = 0;
314 private syncing = false;
315 private stopped = false;
316 /**
317 * True once the initial watch set is established. Unlike the previous
318 * chokidar implementation there is no asynchronous initial "crawl" emitting
319 * an `add` per existing file — `fs.watch` only reports changes from the
320 * moment it's installed — so this flips to true synchronously at the end of

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected