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

8
cli.ts
View File

@@ -2,8 +2,7 @@
import * as path from "node:path";
import { startClientForFile } from "./src/client.ts";
import { isLspCommand, listCommands, runCommand } from "./src/commands.ts";
import { pickServer, isServerAvailable } from "./src/root.ts";
import { servers } from "./server.ts";
import { pickServer, isServerAvailable, getServersForPath } from "./src/root.ts";
import {
daemonDiagnostics,
daemonRequest,
@@ -105,9 +104,10 @@ async function runViaDaemon(
const filePath = path.resolve(fileArg);
let result: unknown;
if (cmdArg === "diagnostics") {
// Pick All Available Servers For Diagnostics
// Pick All Available Servers For Diagnostics - Resolves against any
// `.pi-lsp.json` reachable from the file so per-repo overrides apply.
const ext = path.extname(filePath).replace(/^\./, "");
const serverIds = servers
const serverIds = getServersForPath(filePath)
.filter((s) => s.match.includes(ext) && isServerAvailable(s))
.map((s) => s.id);
result = await daemonDiagnostics(filePath, serverIds);