feat(lsp): Phase 1 skeleton — initialize/sync/shutdown over jsonrpc2
Lands the Layer 2 LSP skeleton following the plan in
.claude/plans.local/layer2-lsp.md. Implements the minimum surface
required to validate the stdio plumbing in Zed and VS Code:
- main.go: stdio runloop, jsonrpc2.Conn, ShutdownSignal coordination
between the dispatch goroutine and main so exit/shutdown drain
cleanly.
- internal/server: server struct + RWMutex-protected config; per-
method dispatch in dispatch.go for initialize, initialized,
shutdown, exit, didOpen, didChange (full sync), didClose, and
workspace/didChangeConfiguration; concurrent document store.
- internal/config: schema mirroring the polytmpl.* settings tree
(flavours, delimiters, functions). Decode accepts both the raw
block and a {"polytmpl": {...}} envelope so it works with both
initializationOptions and workspace/didChangeConfiguration.
Capability advertisement is minimal for now: textDocumentSync=Full
(openClose + change). semanticTokensProvider and diagnosticProvider
land in Steps 2 and 3.
Dependencies added: go.lsp.dev/protocol v0.12.0 + go.lsp.dev/jsonrpc2
v0.10.0.
Zed wiring: uncomment [language_servers.polytmpl] in extension.toml
and register against all six per-flavour languages. The Zed shell's
worktree.which("polytmpl-lsp") discovery is unchanged.
Verified locally with a scripted initialize/initialized/shutdown/exit
exchange: the server applies initializationOptions, returns
ServerCapabilities, and exits cleanly on the LSP exit notification.