feat(config): add per-repo .pi-lsp.json server overrides

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.
This commit is contained in:
2026-05-07 22:43:41 -04:00
parent 0b23e203f4
commit 46e3cc4ccd
8 changed files with 400 additions and 49 deletions

View File

@@ -123,6 +123,44 @@ Edit `server.ts`:
}
```
## Per-Repo Config (`.pi-lsp.json`)
Drop a `.pi-lsp.json` at any ancestor of your working files to add or override
servers for that repo. The nearest config (walking upward) wins.
```json
{
"servers": [
{
"id": "rust-analyzer",
"match": ["rs"],
"command": "rust-analyzer",
"args": [],
"rootMarkers": ["Cargo.toml"],
"languageId": "rust"
},
{ "id": "gopls", "args": ["-remote=auto", "-vv"] }
],
"disable": ["oxlint"]
}
```
**Merge rules:**
- Entry with a built-in `id` → fields shallow-merge over the built-in (user wins).
- Entry with a new `id` → appended; must include `match`, `command`, `args`, `rootMarkers`.
- `disable` → filters out matching ids (built-in or user-defined).
**Reloading after edits:** Config is re-read on mtime change, so new lookups
pick up changes automatically. However, **already-running** language servers in
the daemon keep their original spawn args. If you change `command`, `args`, or
`rootMarkers`, run `/lsp-destroy` (or `pi-lsp daemon stop`) so they respawn
with the new config.
**Security note:** `.pi-lsp.json` controls what binary pi-lsp spawns. Treat it
like `.vscode/settings.json` — don't accept untrusted configs from arbitrary
repos.
## Adding A Command
1. Add to the `LspCommand` union in `src/types.ts`.