Addresses review feedback on 7787626:
1. Deleted->Created at the same path is a file replacement, not a no-op.
Previous coalescing dropped both Created->Deleted and Deleted->Created;
the latter left the server with no signal to re-read replaced content.
Now: Deleted->Created collapses to Changed, Created->Changed keeps
Created (server didn't know the file at all). Extracted coalesce() so
the matrix is reviewable in one place.
2. setPatterns([]) (server unregistered all watchers) stopped chokidar
but left pending events + timers intact, so a queued batch could
still fire after the server stopped caring. Now drains via
cancelPending() before stopping chokidar.
3. Added ready() returning a promise resolved by chokidar's initial-scan
'ready' event. Production daemon doesn't need to await it (LSP
handshake gives chokidar ample wall-time), but tests now use it
instead of fixed 200ms sleeps - deflakes the suite on slower
filesystems and addresses the (narrow) startup race where a file
created during chokidar's initial crawl could be missed.
4. Unit tests replace 11 hardcoded sleeps with watcher.ready(), and add
coverage for the two coalesce fixes plus the unregister-drains case.
LSP servers maintain their own workspace index built at initialize time
and rely on the client to push file-system events. Previously the daemon
only synced the single file being queried, so externally created/changed
files (codegen, build scripts, git checkout, the agent's own writes from
the perspective of other open files) left the server's index stale until
manual /lsp-destroy.
Each ClientEntry now lazily owns a WorkspaceWatcher (chokidar + picomatch)
that translates FS events into workspace/didChangeWatchedFiles batches.
Patterns come from the server via client/registerCapability (no
speculative watching). Ignores layer a tiny baseline (.git, .DS_Store)
over the repo's root .gitignore, with a fallback list for non-git
workspaces. Events debounce 50ms quiet / 500ms max wait.
Notable: gopls registers absolute-path globs (/abs/root/**/*.go) rather
than relative ones, so compileWatchers() matches each event against both
relative and absolute path forms. Caught by the integration test; unit
regression test added.
Rollback: PI_LSP_DISABLE_WATCHERS=1 disables all watcher creation.
- src/client.ts: honor register/unregisterCapability for
workspace/didChangeWatchedFiles; advertise dynamicRegistration;
expose getFileWatchers/onWatchersChanged/sendNotification
- src/watcher.ts: new WorkspaceWatcher with layered ignores,
debounce+batch, Created+Deleted coalescing, dual-form glob matching
- src/daemon.ts: per-entry watcher lifecycle, PI_LSP_DISABLE_WATCHERS,
LSP_DEBUG-gated pattern/event logging
- test/unit/watcher.test.ts: 11 tests against real chokidar + temp dir
- test/integration/watcher-gopls.test.ts: end-to-end against gopls
- AGENTS.md: new "Workspace File Watching" section
- flake.nix: add go (required by gopls integration test)