Users can now drop a .pi-lsp.json at any ancestor of their working
files to add new LSP servers, override built-in ones, or disable
servers entirely. The nearest config (walking upward) wins.
- New src/config.ts: walks upward for .pi-lsp.json, parses, and
merges with the built-in registry. Cached per config-file path
with mtime invalidation. Falls back to built-ins on parse error.
- Merge rules: matching id shallow-merges (user wins); new id
appends (must include match/command/args/rootMarkers); `disable`
filters at the end.
- src/root.ts: pickServer() now resolves servers via the per-repo
registry. Adds findServerById(filePath, id) and re-exports
getServersForPath() for callers.
- src/daemon.ts: getOrCreateEntry() resolves serverId against the
filePath's config so spawned servers reflect repo overrides.
- index.ts and cli.ts: replace direct `servers` imports with
path-aware getServersForPath() lookups.
- Tests: 9 new unit tests covering merge semantics, walk-up
discovery, mtime invalidation, and graceful fallback.
- Docs: README "Per-Repo Config" section + AGENTS.md updates.
Move all server matching logic to the extension/CLI side. The daemon no
longer calls pickServer() — it receives an explicit serverId (or
serverIds[] for diagnostics) and uses it directly for cache lookup and
server spawning.
Key changes:
- request op requires serverId: string
- diagnostics op requires serverIds: string[] — daemon fans out in
parallel via Promise.allSettled and returns grouped map
- formatDiagnostics() handles grouped results with per-server headers
when multiple servers contribute (single-server omits header)
- CLI picks servers locally before calling daemon helpers
- New pickDiagnosticServers() in extension returns all available,
non-disabled servers matching the file extension
This makes multi-server diagnostics (e.g., typescript-language-server +
oxlint) work naturally — the extension decides which servers to query,
the daemon just executes.