* fix(watcher): retain pending files on zero-result sync
* refactor(watcher): detect lock-unavailable at the wrapper
Replace the heuristic `(filesChanged === 0 && durationMs === 0)` check
inside `FileWatcher.flush()` with a typed `LockUnavailableError` thrown
by `CodeGraph.watch()`'s sync wrapper. The wrapper has access to the
full `SyncResult`, including `filesChecked` — which is **only** zero
when `sync()` failed to acquire the cross-process file lock (a real
empty sync always has `filesChecked > 0` because `scanDirectory` ran).
That eliminates the heuristic's edge case where a fast no-op sync
returns `durationMs === 0` by `Date.now()` rounding and gets mistaken
for a lock failure on tiny projects.
The watcher's `catch` block now distinguishes `LockUnavailableError`
from real errors: it logs at `logDebug` (not `logWarn`) and does NOT
call `onSyncError` — so a long-running external indexer holding the
lock doesn't spam stderr every debounce cycle via the MCP daemon's
`Auto-sync error` handler. The existing post-catch path already
preserves `pendingFiles` and reschedules, so no new control flow is
needed.
A/B validated end-to-end against the built dist on macOS with a
three-scenario repro (lock held, lock released mid-flight, real sync
error):
- main: lock-held silently clears pendingFiles (BUG);
lock-released never recovers (no real sync runs).
- PR-as-is: lock-held preserves pendingFiles; lock-released
drains. Same observable behavior as wrapper-level.
- wrapper-level: same outcomes; lock-failure goes through the catch
path silently (logDebug only, no onSyncError noise);
real errors still surface via onSyncError.
Updates the regression test to throw `LockUnavailableError` (the real
contract surfaced to `FileWatcher` by `CodeGraph.watch()`), and
asserts `onSyncError` stays quiet during the lock-held cycle.
Closes #449.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Colby McHenry <me@colbymchenry.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 lines
1.0 KiB
TypeScript
34 lines
1.0 KiB
TypeScript
/**
|
|
* Sync Module
|
|
*
|
|
* Provides synchronization functionality for keeping the code graph
|
|
* up-to-date with file system changes.
|
|
*
|
|
* Components:
|
|
* - FileWatcher: Debounced fs.watch that auto-triggers sync on file changes
|
|
* - Watch policy: decides when the watcher must be disabled (e.g. WSL2 /mnt)
|
|
* - Git sync hooks: opt-in commit/merge/checkout hooks when watching is off
|
|
* - Git worktree awareness: detect when a query borrows another tree's index
|
|
* - Content hashing for change detection (in extraction module)
|
|
* - Incremental reindexing (in extraction module)
|
|
*/
|
|
|
|
export { FileWatcher, WatchOptions, PendingFile, LockUnavailableError } from './watcher';
|
|
export { watchDisabledReason, detectWsl } from './watch-policy';
|
|
export {
|
|
installGitSyncHook,
|
|
removeGitSyncHook,
|
|
isSyncHookInstalled,
|
|
isGitRepo,
|
|
DEFAULT_SYNC_HOOKS,
|
|
type GitHookName,
|
|
type GitHookResult,
|
|
} from './git-hooks';
|
|
export {
|
|
gitWorktreeRoot,
|
|
detectWorktreeIndexMismatch,
|
|
worktreeMismatchWarning,
|
|
worktreeMismatchNotice,
|
|
type WorktreeIndexMismatch,
|
|
} from './worktree';
|