Changelog¶
All notable changes to this project will be documented in this file. The format is based on Keep a Changelog, Semantic Versioning.
[0.13.0] - 2026-06-04¶
Added¶
- GCF output (30-51% fewer tokens): All 66 tool handlers now output GCF (Graph Compact Format) by default. GCF replaces JSON field-name repetition with positional encoding. Measured savings: 50.6% on list_symbols, 49.1% on find_references, 37.6% on get_diagnostics, 30.6% on blast_radius. Set
AGENT_LSP_OUTPUT_FORMAT=jsonto revert. - New package:
internal/encoding/gcf/wrappinggcf-goEncodeGeneric - New helper:
EncodeResult(ctx, data)ininternal/tools/helpers.goswitches JSON vs GCF based on context - Format injected automatically via
addToolWithPhaseCheck; no per-handler configuration needed - New dependency:
github.com/blackwell-systems/gcf-gov0.1.1 AGENT_LSP_OUTPUT_FORMATenvironment variable: Controls output encoding (gcfdefault,jsonto revert)- Enum constraints on
direction,detail_level, andlevelparameters (improves schema quality for MCP clients) - Standalone mcp-assert workflow:
gh workflow run mcp-assert.ymlfor independent CI testing - New documentation:
docs/getting-started/mcp-clients.md: copy-paste configs for Claude Code, Cursor, Windsurf, Continue.devdocs/getting-started/troubleshooting.md: common issues and fixesdocs/guide/common-workflows.md: "I want to..." mapped to tools and skillsdocs/reference/env-vars.md: allAGENT_LSP_*variablesscripts/gcf-benchmark.go: reproducible GCF vs JSON token comparison
Changed¶
- Documentation restructured into
getting-started/,guide/,reference/,architecture/for clearer navigation - mcp-assert CI: 3,535 errors reduced to 0 errors, 87/87 assertions passing
[0.12.0] - 2026-06-02¶
Fixed¶
- Windows daemon mode: Fixed 12 bugs that made daemon mode (Python/TypeScript) unusable on Windows. Process detachment now uses
CREATE_NEW_PROCESS_GROUP+CREATE_NO_WINDOW. PID liveness usesOpenProcess+GetExitCodeProcess. Daemon registry race fixed (writedaemon.jsonafter socket bind). URI handling produces RFC 8089 canonical formfile:///X:/path. Cross-repo path comparison is case-insensitive on NTFS.StopDaemonusesTerminateProcess. Configurable broker timeout viaAGENT_LSP_BROKER_TIMEOUT_MS(default 30s). Spawn diagnostics captured to~/.cache/agent-lsp/spawn-logs/<language>.log. Contributed by @TheodorKleynhans.
Changed¶
- README positioning: Restructured README to clarify that agent-lsp is an MCP server orchestrating LSP servers, not an LSP server itself. Added "What is it?" and "Why agent-lsp?" sections.
- Test coverage: 785 new unit tests across 5 core packages:
internal/config: 70.0% → 96.2% (+26.2pp)internal/resources: 39.3% → 73.2% (+33.9pp)internal/session: 64.6% → 76.9% (+12.3pp)internal/lsp: 39.2% → 50.1% (+10.9pp)internal/tools: 39.3% → 40.7% (+1.4pp)
[0.11.2] - 2026-05-18¶
Fixed¶
- jdtls (Java) full initialization chain: set
cmd.Dirto project root (workspace data directory hashing), sendworkspace/didChangeConfigurationafterinitialized(triggers Gradle/Maven import), auto-detect installed JDK runtimes, decouple jdtls JDK from Gradle JDK via--java-executable(jdtls runs on JDK 21, Gradle sees JDK 17 viaJAVA_HOME), fix JDK 17/18/19 detection bug (HasPrefix("1")incorrectly skipped versions starting with 1), reopen documents after import completes - Dynamic capability registration (
client/registerCapability): servers like jdtls register document-level providers (documentSymbol, definition, references, hover) dynamically after workspace import, not in the initialize response. Previously these registrations were acknowledged but discarded, causing all queries to return empty results. hierarchicalDocumentSymbolSupport: declare in client capabilities so servers returnDocumentSymbol[]instead of flatSymbolInformation[]window/logMessagehandling: log warning/error messages from language servers so failures (like Gradle import errors) are visible instead of silent
[0.11.1] - 2026-05-13¶
Fixed¶
- MCP server key renamed from
"lsp"to"agent-lsp"in init config. Skills now display asagent-lsp:lsp-*instead of the redundantlsp:lsp-*in Claude Code and other MCP hosts. Runningagent-lsp initauto-removes the legacy"lsp"key from existing configs. - Duplicate "Explore Symbol" title in tool lists: the
explorealias now shows as "Explore" to distinguish it from the canonicalexplore_symboltool.
Removed¶
.serena/and.spec-workflow/directories removed from repository (workflow scaffolding that didn't belong in the tool repo).
[0.11.0] - 2026-05-10¶
Breaking¶
get_change_impactrenamed toblast_radius. Same handler, same parameters, new name. Update any scripts, CLAUDE.md rules, or agent memory that references the old name.
Added¶
- Inspector evolution (8 improvements to
/lsp-inspect): - Batch mode (
--top N): directory-level inspection with ranked output - Comparison mode (
--diff): branch-only issue detection - Fix suggestions: exact fix text for every finding
- Confidence tiers: verified/suspected/advisory
- Blast-radius severity calibration using caller counts
- Results persisted to
.agent-lsp/last-inspection.json - MCP resource
inspect://lastfor programmatic access -
Inspector now has 12 check types (was 8)
-
Concurrency analysis (language-agnostic, 25 languages):
- 4 new inspector checks:
unrecovered_concurrent_entry,unchecked_shared_state,channel_never_closed,shared_field_without_sync blast_radius:sync_guardedmetadata on mutex-protected typesfind_callers:cross_concurrentflag traces through goroutine/thread boundaries-
/lsp-concurrency-auditskill (24th skill): field-level safety report -
Agent DX improvements:
explore_symbol: type info + source + callers + refs in one callsafe_apply_edit: preview + auto-apply when net_delta == 0- Auto-diagnostics:
errors_after/warnings_afterin symbol edit responses - Indexed indicator:
indexed: true/falsein blast_radius/find_references/find_symbol - Proactive diagnostic regression notifications via DiagChangeTracker
-
Intent aliases:
callers,explore,safe_edit -
blast_radius:scope: "all"for unexported dead code detection blast_radius:filter: "untested"for coverage gap queries
Summary¶
24 skills. 65 tools. 12 inspector check types. 30 CI-verified languages.
[0.10.0] - 2026-05-10¶
Added¶
-
/lsp-onboardskill (23rd skill). First-session project onboarding. Explores the project structure via LSP tools: detects languages and build system, identifies entry points, maps package structure, finds hotspots (most-referenced files), and checks for pre-existing diagnostics. Produces a structured project profile for the agent's reference throughout the session. -
get_editing_contextcomposite tool (tool #61). Single call returns file symbols with signatures, callers partitioned by test/non-test, callees, and imports. Supportsif_none_matchfor conditional responses. Replaces the 3-5 tool sequence agents previously used to gather pre-edit context. -
Token savings metadata.
list_symbols,get_symbol_source, andget_editing_contextnow include_meta.token_savingsin responses showing tokens returned vs full file size. Makes the efficiency story visible on every call. -
ETag/conditional responses. File-scoped tools accept
if_none_matchparameter. When the file's content hash matches, returnsnot_modifiedinstead of recomputing.
Fixed¶
-
get_change_impactnow includes exported methods. Methods like(*Hub).SetSenderwere missed because the name starts with(, failing the uppercase export check. Now extracts the method name after the last dot before checking case. Previously only top-level functions and types were analyzed; methods on types (e.g.,(*Hub).Send) were skipped becausecollectExportedSymbolsdidn't recurse into children. Now recurses into type children while filtering out struct fields. Found by GPT-5.5 agent evaluation. -
safe_delete_symbolcolumn resolution. SameSelectionRange.Startbug as the v0.8.1 symbol position fix: gopls returns positions pointing to thefunckeyword, not the identifier name. Unexported symbols likeappendHintreturned "no identifier found" when checking references. Fixed by resolving the actual identifier column from the source line. Found by GPT-5.5 agent evaluation. -
Token savings wiring.
AppendTokenMetawas implemented but not wired intolist_symbolsorget_symbol_sourcehandlers (Agent D's merge didn't land). Manually wired. -
Flaky
TestSubscribeHealth_Stop. Timing race on CI: health poller could fire one message before the stop channel was read. Fixed by comparing message count before/after stop instead of asserting absolute zero. -
get_change_impactper-symbol test callers. Response now includesaffected_symbolswith per-symboltest_callersandnon_test_callerslists. Agents can see which tests cover each specific method, not just a flat list for the file. Backward compatible: existing top-level fields remain. -
destroy_sessionno longer returns an error on missing sessions. Returns success withstatus: "already_destroyed"instead ofisError: true. Agents callingdestroy_sessionafterpreview_edit(which auto-cleans up) no longer see a confusing error. -
preview_editnet_delta no longer counts hints.DiffDiagnosticsnow filters out severity 3 (info) and 4 (hint) before computing the delta. Previously, hints like "interface{} can be replaced by any" counted toward net_delta, making preview_edit report confusing deltas unrelated to the actual edit. Found by GPT-5.5 agent evaluation. -
destroy_sessionerror message improved. Now explains thatpreview_editcreates and destroys sessions automatically, so a separatedestroy_sessioncall is not needed. Addresses confusion from agent evaluations where models called destroy_session after preview_edit and got errors. -
position_patternnow works without line/column.find_referencesandinspect_symbolhandlers calledextractPosition(requires line/column) instead ofExtractPositionWithPattern(supports position_pattern fallback). Also changed line/column fields to*intpointers so the JSON Schema generator marks them as truly optional. Previously, agents using position_pattern got "line: missing required argument." Found in all three GPT-5.5 agent evaluations. -
find_referencesandinspect_symbolschema fix (superseded).lineandcolumnwere required in the JSON schema even whenposition_patternwas provided as an alternative. Made them optional so agents can useposition_patternalone without validation errors. -
get_change_impactdiscoverability. Promoted to IMPORTANT in MCP Instructions with "replaces manual loops over find_references." Agent evaluations showed agents manually looping over exports instead of calling it. find_callerstype confusion. Description now clarifies it works on functions/methods only; for types, usefind_references. Both agent evaluations showed confusion when call hierarchy returned nothing for types.format_documentscope clarity. Description now clarifies single-file scope and suggests shell (e.g.gofmt -l) for discovering which files need formatting.
[0.9.0] - 2026-05-10¶
Changed¶
- Breaking: 7 tools renamed for intent-based naming. get_info_on_location -> inspect_symbol, get_document_symbols -> list_symbols, get_workspace_symbols -> find_symbol, get_code_actions -> suggest_fixes, get_references -> find_references, call_hierarchy -> find_callers, simulate_edit_atomic -> preview_edit. Function signatures unchanged; only the MCP tool name strings are affected.
Added¶
-
Symbol-level editing tools (4 new tools).
replace_symbol_bodyreplaces a function/method/type body by name without needing line/column coordinates.insert_after_symbolandinsert_before_symboladd code adjacent to a named symbol.safe_delete_symbolremoves a symbol only after confirming zero references viafind_references. All four resolve symbols internally vialist_symbols. Skills updated:lsp-edit-symbolnow usesreplace_symbol_bodyas the primary edit path,lsp-dead-codeoffers optional cleanup viasafe_delete_symbol, andlsp-refactor/lsp-edit-exportsupportreplace_symbol_bodyas an alternative to positional edits. Tool count: 56 to 60. -
Provider-specific rules files during
init.agent-lsp initnow writes a skill awareness rules file alongside the MCP config, giving every AI provider immediate context about the 22 skills and when to use them. Claude Code gets a managed CLAUDE.md section (between sentinel comments, safe to run repeatedly). Cursor gets.cursor/rules/agent-lsp.mdc. Cline gets.clinerules. Windsurf gets~/.windsurfrules. Gemini CLI getsGEMINI.md. All use managed sections to preserve existing user content. Content generated from embedded SKILL.md files at runtime, staying in sync with shipped skills automatically. -
Fix: skill description parsing.
parseSkillMDnow skips indented keys in SKILL.md frontmatter, preventing nesteddescriptionfields (fromtool_permissionsphase definitions) from overwriting the top-level skill description. Four skills (lsp-refactor, lsp-rename, lsp-safe-edit, lsp-verify) had wrong descriptions inprompts/listresponses. -
Server instructions on initialize. The MCP
initializeresponse now includes anInstructionsfield with a condensed skill overview: tool count, key workflows (blast radius before edit, simulate before apply, verify after change), and pointer toprompts/getfor full skill workflows. Every MCP client receives this automatically on connect, providing provider-agnostic skill awareness without any configuration files. -
Proactive server notifications. Four server-initiated MCP notification channels push state changes to agents without requiring a tool call: (1) diagnostic changes with 2-second debouncing to coalesce rapid
publishDiagnosticsupdates during indexing, (2) workspace ready (one-shot notification when all$/progressindexing tokens complete, 5-minute timeout), (3) process health (crash/recovery notifications on language server state transitions), (4) stale references (3-second debounce, signals when watched files change on disk). Architecture:internal/notify/package with Hub coordinator and per-channel subscribers,internal/lsp/client_notify.gowith LSPClient hooks (SubscribeToFileChanges,IsAlive,IsWorkspaceLoaded),cmd/agent-lsp/notifications.gowith MCP session wiring (mcpNotifySender,wireNotificationsToClient). Notifications are best-effort; send errors are silently dropped. Wired automatically onstart_lsp. -
Passive mode (
connectparameter onstart_lsp). Connect to an already-running language server via TCP instead of spawning a new process. Passconnect: "localhost:9999"to reuse the IDE's warm index with zero duplicate memory or indexing. Supported by gopls (gopls -listen=:9999), clangd, and other servers with TCP listen mode. On shutdown, agent-lsp closes the TCP connection without killing the server process. -
group_by=symbolparameter onget_diagnostics. Diagnostics can now be grouped by their owning symbol instead of returned as a flat list per file. Each diagnostic is assigned to the innermost containing symbol via range containment. Helps agents understand "this function is broken" vs "this file has problems." Usage:get_diagnostics(file_path: "...", group_by: "symbol"). -
Intent-based tool descriptions and titles. All 7 renamed tools now have descriptions focused on agent intent rather than LSP protocol details. Titles updated to match (e.g., "Inspect Symbol" instead of "Get Hover Info", "Preview Edit" instead of "Simulate Edit (Atomic)").
-
Cross-referencing in tool descriptions. Tools now suggest related tools:
apply_editrecommendsreplace_symbol_bodyfor full function replacements andpreview_editbefore applying.find_referencesrecommendssafe_delete_symbolfor zero-reference symbols andget_change_impactfor blast-radius analysis.suggest_fixespoints to/lsp-fix-allskill.rename_symbolrecommendsfind_referencesbefore renaming exports. -
"No verification needed" assertions.
preview_editdescription now states: "If net_delta is 0, the edit is safe to apply without further verification." Reduces unnecessary follow-up tool calls after clean previews.
Fixed¶
-
Go test path format.
run_testswith bare paths likeinternal/notifywere interpreted as stdlib packages bygo test. Now auto-prefixes./and appends/...for Go paths that don't start with.or/. -
Nil sender crash in notification channels. workspace.go, health.go, and diagnostics.go called
hub.sender.SendLog()directly, bypassing Hub.Send()'s nil-sender and closed-state guards. This would panic during the window betweenstart_lspand MCP session initialization when sender is nil. Fixed to route throughhub.Send(). Found by/lsp-inspect. -
Dead code removal (
AdaptFileChangeEvents). Exported but never called; the conversion was done inline in notifications.go. Removed along with its test. Found by/lsp-dead-code. -
CleanupStaleDaemonswiring. Exported and tested but never called from production code. Wired intostartOrConnectDaemonbeforeFindRunningDaemonso stale daemon state directories are purged before lookup. Found by/lsp-dead-code.
Refactored¶
interface{}toanyacross codebase. Go 1.18+ alias applied viagofmt -r. 85 files, ~930 replacements. No behavioral change.
[0.8.1] - 2026-05-09¶
Added¶
-
Next-step hints in tool responses. Every tool response now includes a contextual
hintfield suggesting the logical next tool call. For example,find_referencesreturns "use get_change_impact to see the full blast radius";preview_editreturns "call get_diagnostics to check for remaining issues." Helps agents chain tools correctly without skills and helps less capable models navigate the 60-tool surface. -
detect_changesrange parameter. Thecommittedscope now accepts arangeparameter for arbitrary git ranges:"v0.7.0..HEAD","abc123..def456", or a single ref like"main"(expands tomain~1..main). Ignored for unstaged/staged scopes. Previously only comparedHEAD~1..HEAD. -
Fix: symbol position resolution in
get_change_impact.collectExportedSymbolsusedDocumentSymbol.SelectionRange.Startdirectly for reference lookups, but gopls returns positions pointing to thefunckeyword for functions and methods, not the identifier name. This causedGetReferencesRawto produce "no identifier found" for every function and method. Fixed by searching the source line for the actual symbol name and using that column. Before: 130 warnings per scan. After: zero warnings, full reference data for all symbols. -
Selective indexing (Layer 2). Auto-detects the package boundary for the agent's current file and generates scoped language server config (pyrightconfig.json, tsconfig.json) limited to that package and its direct local dependencies. Activates automatically when the workspace has 500+ source files for Python or TypeScript and no manual scope was specified. On
open_document, if the file is in a different package, the config is regenerated automatically. Pyright and tsserver watch their config files and reload without a server restart. Combined with the persistent cache (Layer 3), previously-visited packages serve cached results while the current package gets full LSP precision. -
detect_changesMCP tool. Single-call "what did I break?" workflow. Runsgit diff --name-only(scopes: unstaged, staged, committed), filters to recognized language files, feeds them toget_change_impact, and enriches each symbol with risk classification: "high" (callers across multiple packages), "medium" (same-package callers only), "low" (zero non-test callers). -
agent-lsp updatesubcommand. Self-update to the latest GitHub Release. Fetches the release API, compares versions, downloads the correct binary for the current OS/arch, and atomically replaces the running binary. Flags:--check(compare without downloading),--force(update even if already current). -
/lsp-architectureskill (22nd skill). Project-level architecture overview in one call. Composeslist_symbols,find_symbol, andget_change_impactto produce: language distribution, package map (capped at 30), entry points, hotspots (top 10 files by reference count), and dependency flow. SKILL.md only, no Go code. -
Cache artifact export/import.
export_cachecompresses the SQLite reference cache withVACUUM INTO+ gzip.import_cachedecompresses and validates withPRAGMA integrity_check. Onstart_lsp, if the local cache is empty and.agent-lsp/cache.db.gzexists in the workspace root, it auto-imports the artifact. Enables team-shared cache: commit the artifact, teammates skip the cold start. -
agent-lsp uninstallsubcommand. Clean removal of MCP server entries (from.mcp.json,.cursor/mcp.json, etc.), skill installations (~/.claude/skills/lsp-*), CLAUDE.md managed sections (between sentinel comments), and cache directories. Supports--dry-run. Preserves other MCP server configurations. -
Persistent reference cache (knowledge graph Layer 3).
get_change_impactresults are now cached in a per-workspace SQLite database (~/.agent-lsp/cache/<hash>/refs.db). First call queries the language server and stores results keyed by file content hash. Subsequent calls for the same symbols return instantly from cache. File watcher automatically invalidates entries when source files change on disk. Cache is opportunistic: agent-lsp works without it, and missing or corrupted databases fall back to direct LSP queries transparently. Pure Go SQLite viamodernc.org/sqlite, no CGo. -
Fix: progressMu deadlock in WaitForWorkspaceReadyTimeout. The function locked
progressMuat entry but did not unlock on the timeout or normal exit paths. Whenget_change_impactopened multiple files, gopls emitted$/progressnotifications that requiredprogressMuinreadLoop. The held lock deadlocked the entire read pipeline: no LSP responses could be dispatched, blocking all pending requests indefinitely. Root cause of every multi-fileget_change_impacthang. Fixed withdefer progressMu.Unlock(). -
Panic recovery in daemon broker connection handler.
handleBrokerConnectiongoroutines now havedefer recover(), matching the other two goroutines inRunBroker. Previously a panic from a malformed message would kill the entire daemon broker process. -
Context propagation in daemon broker. Forwarded requests now use the broker's lifecycle context instead of
context.Background(). Requests are cancelled when the daemon shuts down instead of running indefinitely. -
Per-symbol timeout in
get_change_impact. Each reference query in the parallel batch is capped at 15 seconds. Previously, a single slow symbol (e.g., a widely-referenced type in a large file) could block the entire operation for minutes. Timed-out symbols are skipped with a warning instead of stalling the batch. -
Write mutex separation fixes stdin pipe deadlock.
writeRawnow uses a dedicatedwriteMuinstead of the sharedc.mu. When gopls's stdin pipe buffer fills under heavy concurrent writes (e.g., 100+ parallel reference queries), theWrite()call blocks. Previously this heldc.mu, deadlocking all subsequent operations including reads and state checks. The separated mutex allows reads and state transitions to proceed while a write is blocked on pipe backpressure. -
Diagnostic logging for all tool calls and process lifecycle. Every tool call now logs its latency via the central
addToolWithPhaseCheckwrapper. Calls exceeding 5 seconds are logged at WARNING level (e.g., "tool get_change_impact: 8.2s (slow)"); all others log at DEBUG. Process lifecycle events are also logged: "LSP server started: gopls (PID 12345)" on spawn, "LSP server gopls (PID 12345) exited cleanly after 45s" on exit, with uptime tracking. These diagnostics make performance bottlenecks and process leaks visible in the audit trail without requiring manual investigation. -
Test coverage expansion. 174+ new tests across 9 packages covering process lifecycle, broker framing, scope config, warmup state, prompt parsing, normalization edge cases, session types, config inference, resource parsing, skill classification, symbol extraction, and more. Coverage improved: internal/tools 31% to 39.6%, internal/lsp 29.8% to 35.7%, cmd/agent-lsp 17.3% to 22.2%, internal/resources 17.5% to 35%, internal/session to 63.6%, internal/config to 69.2%.
-
get_change_impactconcurrency bump. Worker pool increased from 8 to 16 parallel reference queries. Reduces wall time on large files (100+ exports) by keeping the gopls request queue saturated. -
Process lifecycle cleanup. Fixed orphaned language server processes accumulating across sessions. Three changes:
Shutdown()now waits up to 3 seconds for the language server to exit, then force-kills it. Previously it sentshutdown/exitvia stdin and hoped the process would die.StartForLanguageshuts down the previous client before starting a new one, preventing leaks when switching workspaces.-
resolver.Shutdown()is now called on every exit path (stdin EOF, context cancelled), not just on signals and panics. This was the primary cause of process accumulation: normal session ends never cleaned up child processes. -
Skills as MCP prompts. All 22 skills are now discoverable via
prompts/listand retrievable viaprompts/get, making them available to any MCP client (Cursor, Windsurf, etc.), not just Claude Code. The listing returns only short descriptions to minimize context cost; full workflow instructions load on demand when a specific prompt is requested. Skill SKILL.md files are embedded into the binary at build time for portable, self-contained distribution. Skills continue to work as AgentSkills slash commands in parallel. -
/lsp-inspectskill (21st skill). Full code quality audit for a file or package. Combines LSP batch analysis (get_change_impact) with LLM-driven heuristic checks. Check taxonomy:dead_symbol,test_coverage,silent_failure,error_wrapping,coverage_gap,doc_drift,panic_not_recovered,context_propagation. Runs inline (no background agent, no permission gates). Language-agnostic: works with any configured language server. Replaces the externalagentskills-code-inspectorwith a first-party skill that uses the already-warm LSP session directly. -
Skill capability check in
get_server_capabilities. The response now includes askillsarray classifying all 22 skills assupported,partial, orunsupportedbased on the current language server's capabilities. Each entry lists missing required and optional capabilities. Agents can check once at startup which skills will work instead of attempting skills that fail. -
Workspace scoping via
scopeparameter onstart_lsp. Generates a temporary language-server config (pyrightconfig.jsonfor Python,tsconfig.jsonfor TypeScript) that restricts analysis to specified subdirectories. Enables agent-lsp to work on large monorepos without full-workspace indexing. Accepts a string or array of paths relative toroot_dir. Config is automatically removed on server shutdown, with backup/restore of any pre-existing config file. No-op for languages with native module boundaries (Go, Rust). -
Multi-signal warmup gate for
find_references. Servers that don't emit$/progresstokens (pyright, jedi-language-server) previously caused reference queries to time out because the workspace wasn't confirmed ready. New three-signal readiness detection: $/progresstokens (existing path, gopls/rust-analyzer/jdtls)- Diagnostic arrival: waits up to 30s for first
publishDiagnosticsnotification - Hover canary: issues a hover request to confirm file analysis is complete
First reference query on a cold workspace gets a 5-minute timeout (vs 2 minutes for subsequent queries). On success, workspace is marked warm and future queries use normal timeouts. On timeout, returns a guidance message recommending the scope parameter or longer warmup. The warmup gate is only active for daemon-connected clients; direct-mode clients (gopls, rust-analyzer) use the existing fast path with zero overhead.
- Persistent LSP daemon mode for Python and TypeScript. Language servers that need sustained background indexing (pyright, tsserver) now run as persistent daemon brokers that survive between agent sessions. Architecture:
start_lspwithlanguage_id="python"or"typescript"automatically spawns a daemon broker subprocess- The broker owns the language server, listens on a Unix socket, and proxies JSON-RPC
- Agent-lsp connects to the daemon via socket (no subprocess spawn on subsequent sessions)
- Daemon auto-exits after 30 minutes of inactivity
find_referenceson daemon clients returns clear "indexing in progress" status if workspace isn't ready- New CLI commands:
agent-lsp daemon-status,agent-lsp daemon-stop [--all] - Go, Rust, C, and other languages with fast-indexing servers bypass daemon mode entirely (zero overhead)
Validated on FastAPI (1,119 Python files, 80K stars): daemon indexes in ~10 seconds, find_references on the FastAPI class returns 1,214 references across 556 files instantly. Previously timed out at 5 minutes on every attempt.
Fixed¶
- Windows build fix. Extracted
Setsidsyscall attribute into platform-specific files (procattr_unix.go,procattr_windows.go). The daemon broker usedsyscall.SysProcAttr{Setsid: true}which is Unix-only and prevented compilation on Windows. The Windows variant usesCREATE_NEW_PROCESS_GROUPinstead. - Daemon broker panic recovery. Added
defer recover()to warmup and socket accept goroutines in the daemon broker. Previously, a panic in either goroutine would crash the broker silently with no error reporting. - Daemon ready flag write failure now logged.
WriteDaemonInfoin the warmup goroutine previously discarded errors with_ =. If writing the ready flag fails, the daemon would appear permanently stuck in "indexing" state. Now logs a warning. - Content-Length parse error handling in broker. Malformed
Content-Lengthheaders from socket clients now return an error instead of silently producingcontentLength=0. -
Error wrapping in
StopDaemon.os.FindProcesserrors now include the PID and operation context. -
Concurrency fixes in daemon broker and warmup state. Fixed 4 data races found by internal concurrency audit:
- DaemonInfo struct writes synchronized with mutex between warmup goroutine and main event loop
lastDisconnread protected byconnMulock (was racing with write path)firstRefDonemoved from package-level to per-warmupState field (prevents cross-client state leakage in multi-server mode)socketConnnil-write in daemon Shutdown protected byc.mu
Testing¶
- 17 new unit tests for daemon, warmup, and scope packages. Covers
NeedsDaemon,DaemonDir,WriteDaemonInfo/RefreshDaemonInforound-trip,CleanupStaleDaemons,GenerateScopeConfig(Python, TypeScript, Go no-op), backup/restore of existing configs,warmupStatelifecycle (FirstRefTimeout,MarkReady,NotifyDiagnostic).
Performance¶
- get_change_impact: 100x faster on large files. Rewrote the batch reference query system:
- Parallel worker pool (8 concurrent goroutines) replaces sequential loop
GetReferencesRawskips per-fileWaitForFileIndexedfor batch callers- Single warmup query absorbs cold-start cost; result reused (not discarded)
workspaceLoadedatomic flag: once all$/progresstokens complete,WaitForFileIndexedbecomes a no-op for all subsequent calls- Struct fields excluded from export collection (avoids 50%+ unnecessary queries on Go codebases)
- Context cancellation check in workers for early exit
- Transitive references parallelized (was sequential)
- Test function deduplication in output
Before: get_change_impact on a 2,295-line file (80+ exports) hung for 20+ minutes.
After: completes in under 30 seconds on a cold workspace, under 5 seconds on a warm one.
This unblocked the /inspect skill on external repositories.
[0.5.3] - 2026-05-04¶
Fixed¶
- Nullable array schemas (
"type": ["null","array"]) collapsed to"type": "array"for Gemini 2.5 Flash compatibility. Fixes #2.
[0.5.2] - 2026-05-03¶
Added¶
- PyPI distribution:
pip install agent-lsp. Platform-specific wheels published automatically on release. - Download stats script (
scripts/download-stats.sh).
Changed¶
- PyPI publish job added to release workflow (automated on every tag).
[0.5.1] - 2026-05-03¶
Added¶
- Token savings experiment (
experiments/token-savings/): reproducible Go script that measures input token cost of LSP vs grep/read approaches across Go, Python, and TypeScript codebases. 13 tasks covering 7 agent skills. Auto-discovers target symbols. Results: 5-34x savings across 5 codebases (agent-lsp, Hono, FastAPI, Next.js, HashiCorp Consul). SendRequestpublic method onLSPClient: exposes the low-level JSON-RPC request path for batch/measurement scenarios where the workspace is already indexed.- PyPI distribution (
pypi/): platform-specific wheels containing the Go binary.pip install agent-lspworks on macOS, Linux, and Windows without a Go toolchain. - Download stats script (
scripts/download-stats.sh): queries npm, PyPI, GitHub Releases, and Docker Hub. Generates SVG badge.
Changed¶
- File-level comments added to 21 core source files across
cmd/agent-lsp/,internal/lsp/,internal/tools/, andinternal/session/explaining architecture, design decisions, and data flow. - Roadmap updated: "mcp-eval" section replaced with "mcp-assert: shipped sister project" reflecting v0.8.0 reality.
- README: added token savings section with headline numbers (5-34x, link to full experiment).
- Distribution docs: added PyPI channel and marketing/discovery tracking section.
[0.5.0] - 2026-04-25¶
Added¶
- Skill phase enforcement: runtime state machine that enforces tool call ordering during skill workflows. Three new tools:
activate_skill,deactivate_skill,get_skill_phase. Four skills have phase configs:lsp-rename(3 phases),lsp-refactor(5 phases),lsp-safe-edit(4 phases),lsp-verify(5 phases). Two enforcement modes:warn(log and allow) andblock(return error with structured recovery guidance). Phases advance automatically based on tool call patterns. Global forbidden lists prevent tools that don't belong in a skill's workflow. All agent-lsp tool handlers wrapped via genericaddToolWithPhaseCheckfunction. Phase events logged to JSONL audit trail. 17 unit tests covering matching, phase advancement, warn/block modes, and full workflow integration for lsp-rename and lsp-refactor. Newinternal/phase/package. See docs/phase-enforcement.md. - Phase enforcement documentation: standalone deep-dive doc (
docs/phase-enforcement.md), tool reference entries indocs/tools.md, architecture and features docs updated, mcp-assert assertions for all 3 new tools. - Prerequisites section in installation guide: states what you need (a language server, an MCP client) before installing.
- "What to try first" guidance in quickstart: concrete examples of what to ask your AI agent after setup.
Changed¶
- Tool count updated from 50 to 53 across all documentation (README, index, architecture, tools, features, quickstart).
ROADMAP.mdanddocs/changelog.mdreplaced with symlinks to theirdocs/and root counterparts respectively, eliminating duplicate files that had drifted out of sync.required-capabilitiesandoptional-capabilitiesmetadata marked as shipped in roadmap (were listed as planned but shipped in v0.4.0).- Awesome MCP Servers marked as shipped in FEATURES.md distribution table.
ready_timeout_secondsparameter added tostart_lspdocumentation in tools.md (was documented in FEATURES.md but missing from the primary tool reference).- Architecture doc updated: nine
doc.gopackages (addsphase), five tool registration files (addstools_phase.go),addToolWithPhaseCheckwrapper documented. - Cross-reference to phase enforcement added to speculative-execution.md "See also" section.
[0.4.0] - 2026-04-24¶
Added¶
- Elixir: 16 verified capabilities (up from 13). Fixed definition, find_callers, and apply_edit. Symbols (list_symbols) now correctly marked as failing due to ElixirLS needing more compile time than the 20s init wait provides.
- AgentSkills spec conformity — all 20 skills now include
licenseandcompatibilityfrontmatter fields per the AgentSkills specification. Skills work with any conforming agent: Claude Code, Cursor, GitHub Copilot, Gemini CLI, OpenAI Codex, JetBrains Junie, and 30+ others. - Provider-agnostic skill installer —
install.sh --dest DIRinstalls skills to any agent's skill directory, not just Claude Code. Updates CLAUDE.md, AGENTS.md (Codex), and GEMINI.md instruction files when present. - Architecture documentation — concurrency model section (goroutine architecture, four channel patterns, crash recovery), speculative execution sequence diagram, error handling section (three-layer propagation), Key Terms glossary, HTTP transport mode details, audit trail section, config file example.
- LSP Conformance page added to docs site navigation.
- Gleam: 17 verified capabilities (up from 6 skipping). Added
gleam build --target javascriptpre-build step, fixed module import path (personnotfixture/person), enriched fixture with Result type and pattern matching. New dedicatedtypeDefLine/signatureHelpLinetest config fields for independent positioning. - Documentation site — agent-lsp.com live on GitHub Pages with Cloudflare DNS.
- mcp-assert — sister project (github.com/blackwell-systems/mcp-assert). Deterministic correctness testing for MCP servers. 103 assertions across 7 servers in 3 languages: agent-lsp (60, 100% tool coverage), filesystem (14, 92%), memory (5), SQLite (6), mcp-go SDK (18, 100%). 14 assertion types, 100 unit tests, 8 CLI commands (run/ci/matrix/coverage/generate/snapshot/watch/init), setup output capture for chained workflows, mkdocs site. GitHub Action. Found 2 upstream bugs: modelcontextprotocol/servers#4029, mark3labs/mcp-go#826.
- Agent evaluation framework on roadmap — two-layer architecture (deterministic tool correctness + skill workflow trajectory matching), Docker-isolated eval harness, negative evals, capability-gated skills.
- Capability metadata in skills — all 20 SKILL.md files now declare
required-capabilitiesandoptional-capabilitiesin frontmatter metadata. Agents can checkget_server_capabilitiesagainst a skill's requirements before activation. 5 skills have zero required capabilities (work with any LSP);referencesProvideris the most common requirement (8 skills);callHierarchyProviderandtypeHierarchyProviderare always optional, never required. - Zig coverage maximization — upgraded zls from 0.13.0 to 0.14.0 in CI; 21 verified capabilities (up from 18). signature_help now passes (call site position in main.zig), apply_edit now passes (trailing whitespace in fixture), symbol_source now passes (likely zls 0.14 improvement). workspace_symbols fails (zls 0.14.0 advertises support but may need specific query format).
user-invocableon all skills — all 20 SKILL.md files now declareuser-invocable: truein frontmatter, making them available as/lsp-*slash commands in Claude Code and other AgentSkills-conforming agents.- mcp-assert CI job — 51 deterministic protocol-level assertions covering all 50 agent-lsp tools against real gopls on every push and PR. 100% tool coverage through the MCP transport layer. Outputs JUnit XML artifact and shields.io badge JSON.
Fixed¶
- change-impact-test CI flake — replaced fixed
time.Sleepwithready_timeout_secondsand warmup probe that pollsfind_referencesuntil gopls returns cross-file results. Skips on persistent timeout instead of failing. - Docker release pipeline — inlined base layer into all Dockerfiles to eliminate build race; split language/combo/full images into parallel matrix job (10 runners) to avoid 60m GoReleaser timeout; fixed hardcoded
linux-amd64Go download URL for ARM64 builds. - Completions test — handles both raw array and CompletionList object response shapes (fixes Gleam and other servers that return the full CompletionList).
- apply_edit test — detects whole-file replacement formatters (Gleam always returns a full-file TextEdit) by comparing edit content against current file content.
- Parameter naming consistency —
get_symbol_sourcerenamedcharactertocolumnin JSON Schema to match all other position-taking tools. Implementation accepts both for backward compatibility.format_rangeschema descriptions corrected from "0-indexed" to "1-indexed" (the implementation already validated>= 1). Both found by dogfooding mcp-assert.
[0.3.0] - 2026-04-22¶
Added¶
ready_timeout_secondsonstart_lsp— optional parameter that blocks until all$/progressworkspace-indexing tokens complete before returning, up to the specified timeout. Replaces fixed post-initialize sleeps for servers like jdtls that index asynchronously afterinitialize. Fires as soon as indexing completes rather than always waiting the full timeout. Also exportsWaitForWorkspaceReadyTimeoutonLSPClientfor callers needing a configurable timeout beyond the default 60s cap.- Error path integration tests (
test/error_paths_test.go) — 11 subtests covering deliberately bad input acrossgo_to_definition,get_diagnostics,simulate_edit,preview_edit,find_references, andrename_symbol. Asserts well-formed error responses, never nil results or crashes, without asserting specific message text. - Cross-language consistency tests (
test/consistency_test.go) — parallel structural shape validation across Go, TypeScript, Python, and Rust forlist_symbols,go_to_definition,get_diagnostics, andinspect_symbol. Verifies response shape contracts hold across all language servers. - Dedicated
multi-lang-javaCI job — jdtls isolated to its own runner to avoid OOM-induced SIGTERM when sharing memory with other language servers. Runs withcontinue-on-error: true,-Xmx2G, and a 15-minute timeout.multi-lang-coreno longer installs jdtls and drops from 45m to 30m timeout. -
ARM64 Docker images — all 11 Docker image tags now publish as multi-arch manifest lists (
linux/amd64+linux/arm64). Native performance on Apple Silicon and AWS Graviton without Rosetta/QEMU emulation. -
MCP tool annotations — all 50 tools now declare
ToolAnnotationswithTitle,ReadOnlyHint,DestructiveHint,IdempotentHint, andOpenWorldHint. MCP clients can auto-approve read-only tools (~30 of 50) without human confirmation. - JSON Schema parameter descriptions — 171
jsonschemastruct tags across all Args structs. Schema description coverage goes from 0% to 100%. Agents see parameter semantics (1-indexed positions, valid values, defaults) in the tool schema itself. - Speculative session tests expanded to 8 languages —
TestSpeculativeSessionsis now table-driven and covers Go (gopls), TypeScript (typescript-language-server), Python (pyright), Rust (rust-analyzer), C++ (clangd), C# (csharp-ls), Dart (dart analysis server), and Java (jdtls). Each language runs as a parallel subtest with its own MCP process. Theerror_detectionsubtest verifiesnet_delta > 0for a per-language type-breaking edit. Java uses a 300s extended timeout to accommodate jdtls JVM startup. CIspeculative-testjob updated to install all required LSP servers; timeout bumped to 20m. --helpflag —agent-lsp --help(or-horhelp) prints usage summary with all modes and subcommands.docs/skills.md— user-facing skill reference organized by workflow category with concrete use cases and composition examples.glama.json— Glama MCP registry profile for server discovery and quality scoring.
Changed¶
- Graceful startup with no language servers — auto-detect mode now starts the MCP server with all 50 tools registered even when no language servers are found on PATH. Previously exited with an error. Enables introspection, container health checks, and deferred server configuration via
start_lsp.
Fixed¶
- jdtls
JAVA_HOMEon Linux CI —javaHomein the JavalangConfigwas hardcoded to a macOS Homebrew path, causing jdtls to exit immediately on Linux runners. Now readsJAVA_HOMEfrom the environment, resolving correctly on both platforms. - TypeScript speculative test
discard_pathnet_delta — inserting a comment at line 1 ofexample.tsshifted 3 pre-existing error positions, producing a false-positivenet_delta=3. SwitchedsafeEditFiletoconsumer.ts(no pre-existing errors) and added aget_diagnosticsflush after opening the file to ensure baseline is captured against steady-state diagnostics. - Python speculative chain test — chain test hardcoded
// chain step Nbut//is floor division in Python. Now useslang.safeEditText(language-appropriate comment syntax). - BSD awk in
install.sh— fixed CLAUDE.md managed block update failing silently on macOS due to embedded newlines in awk-vvariable. Uses temp file withgetlineinstead. - Docker
USER nonrootinheritance —Dockerfile.lang,Dockerfile.combo, andDockerfile.fullnow switch toUSER rootbeforeapt-get installand back tononrootafter. Previously failed with exit code 100 because the base image'sUSER nonrootwas inherited. Dockerfile.releasefor GoReleaser — GoReleaser Docker builds now use a dedicated Dockerfile that copies the pre-built binary instead of compiling from source. Fixes build context issues where source files were unavailable.- Docker build ordering — release workflow pre-builds and pushes the base image before GoReleaser starts, fixing parallel build race where language images couldn't find the base in the registry.
- Leaked agent constraint in
/lsp-generate— removed SAW agent brief instruction that leaked into the published SKILL.md. - Install script archive extraction —
install.shandinstall.ps1now handle GoReleaser's nested archive directory structure instead of assuming a flat layout. agent-lsp initClaude Code global path — option 2 now writes to~/.claude/.mcp.json(Claude Code) instead ofclaude_desktop_config.json(Claude Desktop). Menu label updated to match.go installpath — documented command was missing/cmd/agent-lspsuffix, causing "not a main package" error.- jdtls CI exit status 15 —
sudo mkdircreated the-datadirectory owned by root, preventing jdtls from writing workspace metadata. Removed hardcoded-datafrom wrapper scripts; tests now control workspace directory viaserverArgs.
[0.2.1] - 2026-04-20¶
Fixed¶
- Exit code on no-args —
agent-lspinvoked with no arguments and no language servers on PATH now exits 0 with usage help instead of exit 1. Fixes Winget validation failure.
[0.2.0] - 2026-04-19¶
Added¶
- Windows install support —
install.ps1PowerShell script (no admin required; installs to%LOCALAPPDATA%\agent-lspand adds to user PATH), Scoop bucket manifest (bucket/agent-lsp.json;scoop bucket add blackwell-systems https://github.com/blackwell-systems/agent-lsp), and Winget manifests (winget/manifests/;winget install BlackwellSystems.agent-lsp). - HTTP+SSE transport — agent-lsp can now serve MCP over HTTP using
--http [--port N]. Enables persistent remote service deployment: Docker containers on remote hosts, shared CI servers, and multi-client setups without cold-start cost. Auth viaAGENT_LSP_TOKENenvironment variable enforces Bearer token authentication usingcrypto/subtle.ConstantTimeCompare. internal/httpauthpackage —BearerTokenMiddleware(token, next http.Handler)wraps any HTTP handler with constant-time Bearer token validation. Returns RFC 7235-compliant 401 withWWW-Authenticate: Bearerheader and{"error":"unauthorized"}JSON body. No-op passthrough when token is empty./healthendpoint — unauthenticatedGET /healthreturns{"status":"ok"}(200). Bypasses Bearer token auth so container orchestrators and Docker healthchecks can probe liveness without credentials.docker-compose.ymlwiresHEALTHCHECKfor theagent-lsp-httpservice.- Docker security hardening — images now run as uid/gid 65532 (
nonroot);EXPOSE 8080added;HOMEset to/tmp(writable by nonroot);docker-compose.ymladdsagent-lsp-httpservice for HTTP mode withAGENT_LSP_TOKENwiring. docker-compose.ymlHTTP service —agent-lsp-httpservice exposes port${AGENT_LSP_HTTP_PORT:-8080}:8080with token read fromAGENT_LSP_TOKENenv var (not CLI arg)./lsp-exploreskill — composes hover, go_to_implementation, find_callers, and find_references into a single "tell me about this symbol" workflow for navigating unfamiliar code./lsp-fix-allskill — apply available quick-fix code actions for all current diagnostics in a file, one at a time with re-collection after each fix. Enforces a sequential fix loop to handle line number shifts after each apply_edit./lsp-refactorskill — end-to-end safe refactor: blast-radius analysis → speculative preview → apply → build verify → targeted tests. Inlines tool sequences from lsp-impact, lsp-safe-edit, lsp-verify, and lsp-test-correlation./lsp-extract-functionskill — extract a selected code block into a named function. Primary path uses the language server's extract-function code action; manual fallback identifies captured variables and constructs the function signature./lsp-generateskill — trigger language server code generation (interface stubs, test skeletons, missing method stubs, mock types) viasuggest_fixes+execute_command. Documents per-language generator patterns for Go, TypeScript, Python, and Rust./lsp-understandskill — deep-dive exploration of unfamiliar code by symbol name or file path. Synthesizes hover, implementations, call hierarchy (2-level depth limit), references, and source into a structured Code Map. Broader than/lsp-explore: operates on files as a unit and surfaces inter-symbol relationships.agent-lsp doctorsubcommand — probes each configured language server, reports version and supported capabilities, exits 1 if any server fails. Useful for CI health checks and debugging setup issues.- LineScope for
position_pattern—line_scope_start/line_scope_endargs restrict pattern matching to a line range, eliminating false matches when the same token appears multiple times in a file. rename_symbolglob exclusions — new optionalexclude_globsparameter (array of glob strings) excludes matching files from the returned WorkspaceEdit. Useful for generated code (**/*_gen.go), vendored files (vendor/**), and test fixtures (testdata/**).- MIT LICENSE file — added explicit license; copyright Blackwell Systems and Dayna Blackwell.
Changed¶
- Auth token reads from env var —
AGENT_LSP_TOKENenvironment variable takes precedence over--tokenCLI flag, keeping credentials out of the process list.--tokenstill accepted for local dev but env var always wins; using--tokenwithout the env var prints a warning to stderr. - HTTP server timeouts —
ReadHeaderTimeout: 10s,ReadTimeout: 30s,WriteTimeout: 60s, andIdleTimeout: 120sadded to prevent Slowloris-style resource exhaustion and stalled response writers. --listen-addrIP validation — rejects hostnames and invalid values; only valid IP addresses accepted (net.ParseIP).--no-authloopback enforcement —--no-authis rejected when--listen-addris a non-loopback address.entrypoint.shsecurity — replacedevalwith a POSIXcasewhitelist;awkuses-v name=variable binding;apt-getarm validates package name; all expansions quoted.- Port range validation —
--portrejects values outside 1–65535. - Accurate HTTP bind log — reports actual bound address from
ln.Addr().String(). install.shCLAUDE.md sync — maintains a managed skills table in~/.claude/CLAUDE.mdbetween sentinel comments; auto-discovers skills from SKILL.md frontmatter.- Docker builds now trigger on release tags only; removed
:edgetag. - Moved
Dockerfile,Dockerfile.full,Dockerfile.lang, anddocker-compose.ymlintodocker/directory. - Removed
:baseas a user-facing tag (still used internally between CI jobs). - Surfaced quick install snippet at top of README after value proposition.
[0.1.2] - 2026-04-10¶
Added (2026-04-10) — Public pkg/ API¶
Exposed a stable importable Go API so other programs can use agent-lsp's LSP client and speculative execution engine without running the MCP server:
pkg/types— all 29 LSP data types, 5 constants, and 2 constructor vars re-exported as type aliases frominternal/typespkg/lsp—LSPClient,ServerManager,ClientResolverinterface, and all constructors;ServerEntryre-exported frominternal/configpkg/session—SessionManager,SessionExecutorinterface, all speculative execution types and constants
All pkg/ types are aliases (type X = internal.X) — values are interchangeable with internal types without conversion. pkg.go.dev now indexes and renders the full public API surface.
Added package-level doc comments to all 9 previously undocumented internal packages (internal/lsp, internal/session, internal/types, internal/logging, internal/uri, internal/extensions, internal/tools, internal/resources, cmd/agent-lsp).
Added Library Usage section to README.md with import examples for pkg/lsp, pkg/session, and pkg/types. Updated docs/architecture.md to document the new pkg/ layer.
Added (2026-04-10) — --version flag¶
agent-lsp --version prints the version and exits. Defaults to dev for local builds; GoReleaser injects the release tag at build time via -ldflags="-X main.Version=x.y.z". The MCP server's Implementation.Version field now reads from the same variable.
Fixed (2026-04-10) — Docker image build failures¶
go/gopls —apt golang-goinstalls Go 1.19, too old for gopls. Switched to fetching the latest Go tarball fromgo.dev/VERSIONat build time.ruby/solargraph — addedbuild-essentialfor native C extension compilation (prism).csharp—csharp-lsNuGet package lacksDotnetToolSettings.xml; moved toLSP_SERVERSruntime-only with a clear error message.dart— not in standard Debian bookworm repos; moved toLSP_SERVERSruntime-only.- combo images — inline Dockerfile assumed
npmandgowere in the base image; fixed to install nodejs/npm and Go fromgo.devwhen needed. - Per-language tag table in
DOCKER.mdcorrected: removed 12 tags that were never published; split into published tags andLSP_SERVERS-only languages with install notes.
Added (2026-04-10) — Docker image distribution (ghcr.io)¶
Tiered Docker image distribution published to ghcr.io/blackwell-systems/agent-lsp:
:latest(base) — binary only, no language servers, ~50MB. SupportsLSP_SERVERS=gopls,pyright,...env var for runtime install with/var/cache/lsp-serversvolume caching.- Per-language tags (
:go,:typescript,:python,:ruby,:cpp,:php) — extend base, one language server pre-installed. - Combo tags (
:web,:backend,:fullstack) — curated multi-language images for common stacks. :full— all package-manager-installable language servers (~2–3GB).Dockerfile,Dockerfile.lang,Dockerfile.full— multi-stage builds ondebian:bookworm-slim.docker/entrypoint.sh— POSIX sh runtime installer;docker/lsp-servers.yaml— registry of all 18 supported servers..github/workflows/docker.yml— separate workflow (not release.yml) building all tiers in parallel, pushing to ghcr.io onmainpush (:edge) and version tags.docker-compose.yml+.env.examplefor local development.DOCKER.mdrewritten with per-language one-liners,LSP_SERVERSusage, volume caching, MCP client config.README.mdgains a## Dockersection with the four most common one-liners.
Added (2026-04-10) — Architecture diagram¶
docs/architecture.drawio— draw.io diagram of the full system: MCP client → server.go (toolDeps) → 4 tool registration files → internal/tools handlers → internal/lsp client layer → gopls subprocess. Includes internal/session, leaf packages, and layer rule annotation.
Fixed (2026-04-10) — Inspector audit-7: 11 bugs and quality improvements¶
Security¶
- Path traversal in
HandleGetDiagnostics—HandleGetDiagnosticsaccepted a caller-suppliedfile_pathand passed it directly toCreateFileURIwithout validation. Every other handler validates withValidateFilePathfirst. A caller could supply../../etc/passwdand the handler would read it viaReopenDocument. Fixed by adding aValidateFilePath(filePath, client.RootDir())call beforeCreateFileURI; the sanitized path is used throughout the handler.
Fixed¶
- Context dropped in
StartForLanguageshutdown —StartForLanguage(ctx, ...)callede.client.Shutdown(context.Background())when replacing an existing client, discarding the caller's cancellation and deadline. Fixed to passctx. LanguageIDFromPathmissing C/C++/Java extensions — The exportedLanguageIDFromPathfunction (used byHandleGetChangeImpact) lacked.c,.cpp,.cc,.cxx, and.javaentries. Those file types were mapped to"plaintext", producing incorrect language IDs in impact reports. Added the missing cases.GetReferenceserrors silently discarded inHandleGetChangeImpact— Per-symbol reference lookup errors were swallowed (locs, _ := ...), causing affected symbols to appear with zero callers instead of surfacing a diagnostic. Errors now appear as awarningsfield in the tool response.writeRawerror missing context — Returned the rawstdin.Writeerror with no indication of which operation triggered it. Wrapped asfmt.Errorf("writeRaw: %w", err).sendNotificationmarshal error missing method name — Bothjson.Marshalerror paths insendNotificationreturned without the method name, making debug traces opaque. Wrapped asfmt.Errorf("sendNotification %s: marshal ...: %w", method, err).init()side effect ininternal/logging—init()readLOG_LEVELfrom the environment and mutated package-level state, coupling test setup to import order. Extracted toSetLevelFromEnv(), called explicitly frommain();init()is now a no-op.DirtyErraccessible on non-dirty sessions —SimulationSession.DirtyErrwas a public field readable in any state, givingnilwith no signal on non-dirty sessions. AddedDirtyError() erroraccessor that returnsDirtyErronly whenStatus == StatusDirty; updated the one internal call site insession/manager.go.
Test coverage¶
WaitForFileIndexedtimeout, cancellation, and stability-window-reset paths untested — Added three tests matching theWaitForDiagnosticspattern:TestWaitForFileIndexed_Timeout,TestWaitForFileIndexed_ContextCancelled, andTestWaitForFileIndexed_StabilityWindowReset.parseBuildErrorsmissing tests for TypeScript, Rust, and Python — AddedTestParseBuildErrors_TypeScript,TestParseBuildErrors_Rust, andTestParseBuildErrors_Pythonwith synthetic compiler output strings.
Fixed (2026-04-10) — Inspector-surfaced bugs and quality fixes¶
Errors fixed¶
- Panic recovery in long-lived goroutines —
readLoopandstartWatchergoroutines had norecover(). A panic indispatch()orfsnotifywould terminate the entire process;runWithRecoveryin main cannot catch goroutine panics. Both goroutines now have a deferred recovery that logs the panic and stack trace at error level and returns, keeping the server alive. Run()decomposed from 832 to 379 lines — The monolithicRun()function incmd/agent-lsp/server.goheld ~50 tool registrations, inline arg struct definitions, resource handlers, diagnostic subscription, and transport startup as a single untestable unit. Extracted into four themed registration files:tools_navigation.go(10 tools),tools_analysis.go(13 tools),tools_workspace.go(19 tools),tools_session.go(8 tools), each taking atoolDepsstruct.normalize_test.gowas asserting broken behavior —TestNormalizeDocumentSymbols_SymbolInformationVariantused_ = root.Childrento silence a failing assertion, masking the bug and preventing regression detection. Updated to assertlen(root.Children) == 1androot.Children[0].Name == "MyField".
Warnings fixed¶
- Duplicate extension→languageID mapping —
langIDFromPathinchange_impact.goandinferLanguageIDinmanager.goboth mapped file extensions to LSP language IDs with different coverage (.cs,.hs,.rbwere silently labeled"plaintext"in impact reports). Replaced with a single exportedlsp.LanguageIDFromPathfunction covering all extensions;langIDFromPathremoved. - Duplicate URI-to-path conversion —
tools.URIToFilePathduplicated the logic inuri.URIToPathwith different error behavior.URIToFilePathnow delegates touri.URIToPath, preserving the(string, error)signature. - Bare error returns in session manager —
DiscardandDestroyreturned bareerrfromGetSession, losing call-site context. Wrapped asfmt.Errorf("discard: %w", err)andfmt.Errorf("destroy: %w", err). waitForWorkspaceReadycould block indefinitely — The cond var refactor (audit-6 L2) introduced a bug: the 60s deadline was only checked aftercond.Wait()returned, but if gopls dropped a progress token without emitting the correspondingendnotification,Wait()never returned. Added a timer goroutine that broadcasts at the deadline, guaranteeing the wait unblocks.- gopls inherited shell
GOWORKenv var —exec.Commandinherits the full parent environment; aGOWORKvalue pointing at a different workspace caused gopls to fail package metadata loading for the target repo. The subprocess environment now hasGOWORKstripped viaremoveEnv, letting gopls discover the correct go.work naturally fromroot_dir.
Added (2026-04-10) — Three new MCP tools for code-impact analysis¶
get_change_impact¶
Answers "what breaks if I change this file?" without running tests. Given a list of changed files, it enumerates all exported symbols in those files via list_symbols, resolves every reference via find_references, and partitions the results into test callers (with enclosing test function names extracted) and non-test callers. Supports optional one-level transitive following to surface second-order impact. Useful before any refactor to understand blast radius and which tests will need updating.
get_cross_repo_references¶
First-class cross-repo caller analysis. Given a symbol (file + position) and a list of consumer repo roots, adds each consumer as a workspace folder and calls find_references across all of them. Results are partitioned by repo root prefix so callers in each consumer are reported separately. Designed for library authors who need to know which downstream consumers reference a symbol before changing its signature.
simulate_chain — refactor preview framing¶
simulate_chain is now documented and surfaced as a "refactor preview" tool: apply a rename/signature change speculatively, walk the chain of dependent edits, and read cumulative_delta + safe_to_apply_through_step before writing a single byte to disk. Added docs/refactor-preview.md with four worked examples (safe rename preview, change impact preview, multi-file refactor with checkpoint, key response fields reference). README updated with refactor-preview framing in the tools table.
Fixed (2026-04-09) — Audit-6 batch: 12 bugs and quality fixes¶
Critical¶
- C1 —
AddWorkspaceFolderwatcher regression — The audit-5 H2 fix (passingpathinstead ofc.rootDirtostartWatcher) madeAddWorkspaceFoldercallstartWatcher(path), which internally stopped the existing watcher goroutine before starting a new one watching only the new path. After adding a second workspace folder, file changes under the original root were no longer delivered to the LSP server; the index went stale silently. Fixed by adding awatcher *fsnotify.Watcherfield toLSPClientand a newaddWatcherRootmethod that callswatcher.Add(path)on the live watcher goroutine rather than restarting it.AddWorkspaceFoldernow callsaddWatcherRootinstead ofstartWatcher. - C2 — Exit-monitor goroutine did not clear
initializedon crash — After an unplanned LSP subprocess exit (OOM, segfault),rejectPendingwas called to unblock pending requests, butc.initializedwas lefttrue. All subsequent tool calls passedCheckInitializedand received opaque RPC errors instead of the clear "call start_lsp first" message. Fixed by addingc.mu.Lock(); c.initialized = false; c.mu.Unlock()in the exit-monitor goroutine immediately afterrejectPending.
High¶
- H1 —
NormalizeDocumentSymbolsname map was last-write-wins on duplicate names —nameMap[info.Name]overwrote earlier entries for symbols sharing a name (e.g., multipleString()orError()methods across types). Children were attached to the wrong parent node. Fixed by keying the name map withnameKey(name, kind)using\x00as separator; a separatenameByBaremap handlesContainerNamelookups. - H2 —
SerializedExecutorglobal semaphore serialized all sessions — A singlechan struct{}blocked all concurrent session operations regardless of which sessions were involved. Two independent speculative sessions were forced sequential. Fixed by replacing the global channel withmap[string]chan struct{}— one buffered channel per session ID — created on first access under a guard mutex. The per-session channel preserves the original cancellation semantics viaselect. - H3 — Column offsets were byte offsets, not UTF-16 code unit offsets —
ResolvePositionPatternandtextMatchApplycomputed thecharacterfield using raw byte subtraction. LSP spec §3.4 requires UTF-16 code unit offsets; gopls silently returns empty results when given positions past the line end. Fixed by adding autf16Offset(line string, byteOffset int) inthelper inposition_pattern.go(walks UTF-8 runes, counts surrogate pairs for U+10000+) and using it in both locations.
Medium¶
- M1 —
MarkServerInitialized()called before MCP session established — A premature call atserver.go:1016setserverInitialized = truebefore any MCP client had connected, making the initialization flag misleading and fragile to ordering changes. Removed; the canonical call insideInitializedHandler(which fires on MCP client connection) is the only remaining call site. - M2 —
DiffDiagnosticswas O(n×m) — Nested loop compared every current diagnostic against every baseline diagnostic. For files with hundreds of diagnostics, this compounded across URIs per evaluation. Fixed with a fingerprint-keyed counter map (map[string]int) for O(n+m) complexity; fingerprint uses Range, Message, and Severity (matchingDiagnosticsEqualsemantics); counts handle duplicate diagnostics correctly. - M3 —
textMatchApplybuilt file URIs via string concatenation —"file://" + filePathdoes not percent-encode spaces or special characters;CreateFileURI(usingurl.URL) was already the established pattern elsewhere. Fixed by replacing the concat with aCreateFileURI(filePath)call.
Low¶
- L1 —
NormalizeDocumentSymbolsPass 3 comment was misleading — Comment incorrectly implied the value-copy logic handled multi-level SymbolInformation hierarchies. Updated to accurately describe deferred pointer dereferencing, why it is correct for the 1-level depth that LSP SymbolInformation always produces, and the spec constraint. - L2 —
waitForWorkspaceReadypolled at 100ms intervals — Unnecessary latency of up to 100ms after workspace indexing completed. Replaced busy-poll withsync.Cond;handleProgressnow broadcasts whenprogressTokensbecomes empty;waitForWorkspaceReadyblocks onWait()with a context-deadline fallback. - L3 —
AddWorkspaceFolder/RemoveWorkspaceFolderdropped context — Methods had noctx context.Contextparameter; notification sends could not be cancelled. Addedctxas first parameter to both methods and updated the call sites inworkspace_folders.go. - L4 —
json.Marshalerrors discarded in three workspace folder handlers —HandleAddWorkspaceFolder,HandleRemoveWorkspaceFolder, andHandleListWorkspaceFoldersuseddata, _ := json.Marshal(...). Fixed by capturing the error and returningtypes.ErrorResulton failure, consistent with all other handlers.
Fixed (2026-04-09) — Audit-5 batch: 16 bugs and quality fixes¶
Critical¶
- C1 —
Restartdid not clear per-session state —openDocs,diags,legendTypes, andlegendModifierswere not reset on restart; after reconnecting to a fresh LSP server, stale open-document records caused the server to receivedidChangeinstead ofdidOpenfor already-open files, and stale diagnostics were served from the previous session. Fixed by adding explicit zeroing of all four maps/slices insideRestart, guarded by their respective mutexes, before callingInitialize. - C2 —
watcherStopdata race instartWatcher/stopWatcher— thewatcherStopchannel was read and written without synchronization, causing a race detectable bygo test -race. Fixed by addingwatcherMu sync.MutextoLSPClient;startWatcherandstopWatchernow hold the mutex around all reads and writes ofwatcherStop.
High¶
- H1 —
applyDocumentChangesswallowed filesystem errors — create, rename, and delete operations used_ = os.WriteFile(...)/_ = os.Rename(...)/_ = os.Remove(...); errors were silently discarded. Fixed by capturing and returning errors from all three cases. - H2 —
AddWorkspaceFolderstarted watcher on root dir instead of new path — calledc.startWatcher(c.rootDir)instead ofc.startWatcher(path); adding a second workspace folder would restart the watcher on the original root. Fixed by passingpath. - H3 —
HandleSimulateEditAtomicdiscardedDiscarderrors — cleanup calls used_ = mgr.Discard(...); if the session cleanup failed the error was lost. Fixed by capturing both errors and returning a combined message when both the evaluate-path and discard-path error. - H4 —
LogMessageusedcontext.Background()and discarded marshal error — the function created a detached context rather than using the caller's context, andjson.Marshalerrors were silently dropped, resulting in JSON null being sent to the client. Fixed by adding explicit error handling with a fallback encoded-error string; added comment explaining the intentionalcontext.Background()for the notification send path.
Medium¶
- M1 —
applyDocumentChangesreturned nil on array-unmarshal failure — when the changes JSON couldn't be unmarshalled into[]types.TextEdit, the function returned nil instead of an error, silently applying no edits. Fixed by returning the unmarshal error. - M2 —
StartAllrollback usedcontext.Background()for shutdown — rollback loops inStartAllcalledc.Shutdown(context.Background()), ignoring the caller's context and discarding shutdown errors. Fixed by passingctxand logging shutdown errors at debug level. - M3 —
uriToPathduplicated acrossinternal/lspandinternal/session— two near-identical implementations with a manual-sync comment. Extracted to newinternal/uripackage asuri.URIToPath; both packages now import and call the shared version. - M4 —
HandleRestartLspServeronly restarted the default client in multi-server mode — the handler restartedc.lspManager.GetClient(c.language)but did not address other configured servers. Fixed by adding a note to the success message indicating that only the default server for the current language is restarted in multi-server configurations. - M5 —
WaitForDiagnosticsquiet-window checked on 50 ms ticks only — when anotifyevent arrived just after a tick, the quiet-window exit condition wasn't evaluated until the next tick (up to 50 ms delay). Fixed by adding the same quiet-window check to thecase <-notify:arm so it's evaluated immediately on each notification.
Low¶
- L1 — Recovered panic exited 0 —
runWithRecovery's recover block logged the panic but did not set the named return error, so the process exited 0 instead of 1. Fixed by settingrunErr = fmt.Errorf("panic: %v", r). - L2 —
ValidateFilePathdid not resolve symlinks — the prefix check used the lexical path, so a symlink pointing outside the workspace root would pass validation. Fixed by callingfilepath.EvalSymlinkson both the file path and the root dir before the prefix check; non-existent paths fall back to lexical path. - L3 —
IsDocumentOpenexported but only used in tests — renamed toisDocumentOpen;client_test.gois inpackage lsp(same package) so the unexported name remains accessible. - L4 —
toolArgsToMapdiscardedUnmarshalerror — used_ = json.Unmarshal(...); failures were silent. Fixed by capturing the error, logging at debug level, and returning an empty map. - L5 — Line-splice algorithm duplicated with manual-sync comment —
applyRangeEditininternal/session/manager.goand the inline loop inapplyEditsToFileininternal/lsp/client.goimplemented the same line-splice logic independently. Extracted touri.ApplyRangeEditin the newinternal/uripackage; both sites now delegate to the shared implementation.
Fixed + Added (2026-04-09) — Speculative session test hardening¶
discard_pathbug fix — test was callingpreview_editwith asession_id, butpreview_editis a self-contained tool (creates its own session internally, requiresworkspace_root+language); the call was silently returningIsError: trueand logging it as "may be expected"; fixed to callsimulate_editwhich is the correct tool for applying edits to an existing sessionevaluate_sessionresponse assertions — existing subtests were only logging the response; now parse the JSON and assertnet_delta == 0for comment-only edits (withconfidence != "low"guard for CI timing);simulate_editresponse now assertsedit_applied == truesimulate_chainresponse assertions — parseChainResultJSON; assertcumulative_delta == 0for two-comment chain; assertsafe_to_apply_through_step == 2commit_pathimproved — now applies a comment edit viasimulate_editbefore committing, making the test more meaningful than committing a clean sessionpreview_edit_standalonesubtest — proper standalone usage ofpreview_editwithworkspace_root+languageparameters; asserts response is anEvaluationResultwithnet_delta == 0for a comment editerror_detectionsubtest — validates the core speculative session value proposition: applyreturn 42in afunc ... stringbody (type error), evaluate, assertnet_delta > 0anderrors_introducedis non-empty; CI-safe: accepts skip whenconfidence == "low"ortimeout == true(gopls indexing window)
Added (2026-04-09) — Full tool coverage (47/47 at time; total now 50)¶
testSetLogLevel— integration test forset_log_level; sets level to"debug", verifies confirmation message contains "debug", resets to"info"; no LSP required, runs for all 30 languagestestExecuteCommand— integration test forexecute_command; queriesget_server_capabilitiesforexecuteCommandProvider.commands, skips if server advertises none, callscommands[0]with a file URI argument; server-level errors treated as skip (dispatch path still exercised); Go-level transport errors are failures; tool coverage 32 → 34 (multi-language harness); 47/47 tools covered across all test suites (3 tools added later:get_change_impact,get_cross_repo_references, promotedsimulate_chain; see Unreleased entry above)
Added (2026-04-09) — Test coverage + CI cleanup¶
testGoToSymbolandtestRestartLspServertest functions — two previously untested tools now covered inTestMultiLanguage;testGoToSymbolcallsgo_to_symbolwithlang.workspaceSymboland verifies at least one result is returned;testRestartLspServerrestarts the server, waits 5 s for re-indexing, reopens the document, and confirms hover still works; both wired intotier2Resultswith skip guards; tool coverage 28 → 32 (accounting forgo_to_symbol,restart_lsp_server, and two tools added in prior waves)test/lang_configs_test.go—buildLanguageConfigs()extracted fromtest/multi_lang_test.gointo its own file (840 lines);multi_lang_test.goreduced from 2340 → 1573 lines; only additional import needed waspath/filepath; no behavior changesunit-and-smokeGHA job — renamed fromtestfor clarity, distinguishing it from themulti-lang-*integration jobs
Fixed (2026-04-09) — Nix CI¶
multi-lang-nixinstall —nilbuild script queriesnixat compile time to generate builtin completions; previouscargo install --git ... nilfailed with"Is nix accessible?: NotFound"; fix: install Nix viaDeterminateSystems/nix-installer-action@v16before installing nil, then usenix profile install github:oxalica/nilto pull from binary cache instead of compiling
Added (2026-04-09) — Language expansion (30 languages)¶
- MongoDB integration test —
mongodb-language-server(npm i -g @mongodb-js/mongodb-language-server); fixture attest/fixtures/mongodb/withquery.mongodb(14-line playground file,findat line 9 col 12,aggregateat line 11 col 12) andschema.mongodb(15-linecreateCollectionwith$jsonSchemavalidator forname/agefields); dedicatedmulti-lang-mongodbCI job withmongo:7service container on port 27017,mongoshhealth check, andTestMultiLanguage/^MongoDB$test;supportsFormatting: false; language count updated 29 → 30
Added (2026-04-09) — Language expansion (29 languages)¶
- Clojure integration test —
clojure-lsp; fixture attest/fixtures/clojure/withdeps.edn(empty map for project recognition) andsrc/fixture/core.clj(7-line file withgreetfunction at line 3 col 7, call site at line 7 col 13); dedicatedmulti-lang-clojureCI job installing clojure-lsp native binary - Nix integration test —
nil(Nix language server); fixture attest/fixtures/nix/flake.nix(9-line flake withhelperbinding at line 5 col 5, call site at line 7 col 21);supportsFormatting: false; dedicatedmulti-lang-nixCI job installing nil binary - Dart integration test —
dart language-server; fixture attest/fixtures/dart/withpubspec.yaml(SDK>=3.0.0 <4.0.0),lib/fixture.dart(Greeterclass at line 1 col 7,greetmethod at line 2 col 10),lib/caller.dart(imports and callsGreeter;Greeterat col 13,greetat col 11); dedicatedmulti-lang-dartCI job installing Dart SDK via apt; language count updated 26 → 29; see also MongoDB entry below
Added (2026-04-09) — Language expansion (26 languages)¶
- SQL integration test —
sqls(go install github.com/sqls-server/sqls@latest); fixture attest/fixtures/sql/withschema.sql(CREATE TABLE person + post),query.sql(two SELECT statements, 18 lines, calibrated hover/completion/reference positions),.sqls.yml(postgresql DSN);serverArgs: []string{"--config", filepath.Join(fixtureBase, "sql", ".sqls.yml")}— config path is resolved at test time, not hardcoded; dedicatedmulti-lang-sqlCI job withpostgres:16service container,pg_isreadyhealth check,psqlschema load step, andPGPASSWORDenv for the load command; supportsFormatting/rename/inlayHints all false (sqls does not implement them); language count updated 25 → 26 - JSON-RPC string ID support —
jsonrpcMsg.IDchanged from*inttojson.RawMessage; dispatch now handles both integer and string IDs per JSON-RPC 2.0 spec;sendResponseechoes the raw ID bytes verbatim;sendRequestmarshals integer IDs into RawMessage; fixes compatibility with servers that use string IDs (e.g.prisma-language-server)
Added (2026-04-09) — Language expansion (25 languages)¶
- Gleam integration test —
gleam lsp(built-in to the Gleam binary,serverArgs: ["lsp"]); fixture attest/fixtures/gleam/withgleam.toml,src/person.gleam,src/greeter.gleam; full Tier 2 coverage including rename, highlights, code actions, and inlay hints; dedicatedmulti-lang-gleamCI job (downloads binary from GitHub releases) - Elixir integration test —
elixir-ls(language_server.shsymlinked aselixir-ls); fixture attest/fixtures/elixir/withmix.exs,lib/person.ex,lib/greeter.ex; rename and inlay hints skipped (renameSymbolLine: 0,inlayHintEndLine: 0— ElixirLS does not implement those); dedicatedmulti-lang-elixirCI job usingerlef/setup-beam@v1(Elixir 1.16 / OTP 26),continue-on-error: truedue to ElixirLS cold-start variability - Prisma integration test —
prisma-language-server --stdio(npm i -g @prisma/language-server); fixture attest/fixtures/prisma/schema.prisma— two-model schema (Person,Post) with a relation; call site and definition both in the same file (schema is a single-file language); inlay hints skipped; dedicatedmulti-lang-prismaCI job - Language count updated 22 → 25 — README badge, prose, Tier 2 table, Language IDs list, comparison table,
docs/language-support.md,docs/tools.md
Added (2026-04-09) — Skills expansion (continued)¶
format_documentstep folded into/lsp-safe-editand/lsp-verify—format_document→apply_editis now an optional final step in both skills; in/lsp-safe-editit fires after diagnostics are clean (Step 8, before the report); in/lsp-verifyit fires after all three layers pass as a pre-commit cleanup; skipped when there are unresolved errors or the user did not request formatting;format_documentadded toallowed-toolsin both skills/lsp-format-codeskill — format a file or selection via the language server's formatter (gofmtvia gopls,prettiervia tsserver,rustfmtvia rust-analyzer, etc.);format_documentfor full file,format_rangefor selection; both returnTextEdit[]applied viaapply_edit; optionalget_server_capabilitiespre-check fordocumentFormattingProvider; post-applyget_diagnosticsguard; multi-file protocol runs format calls in parallel then applies per-file sequentially; language notes table covers Go/TypeScript/Rust/Python/C
Added (2026-04-09) — Skills expansion (continued)¶
/lsp-test-correlationskill — find and run only the tests covering an edited source file;get_tests_for_filemaps source → test files,find_symbolenumerates specific test functions within those files,run_testsexecutes the scoped set; fallback to workspace symbol search whenget_tests_for_filereturns no mapping; multi-file workflow deduplicates test files across all changed sources;[correlated / unrelated]classification guides where to investigate failures first/lsp-verifyget_tests_for_filepre-step — whenchanged_filesis known,get_tests_for_fileruns before the three parallel layers to build a source→test map; Layer 3 failure report now tags each failing test as correlated (covers changed code) or unrelated (collateral failure) to narrow debugging scope
Added (2026-04-09) — Skills expansion¶
/lsp-cross-reposkill — multi-root workspace analysis for library + consumer workflows; orchestratesadd_workspace_folder→list_workspace_folders(verify indexing) →find_symbol→find_references/find_callers/go_to_implementationacross both repos; solves the "agent doesn't know to add a second workspace folder" discoverability gap; output separates library-internal from consumer references/lsp-local-symbolsskill — file-scoped symbol analysis without workspace-wide search; composeslist_symbols(symbol tree for the file) →get_document_highlights(all usages within the file, classified as read/write/text) →inspect_symbol(type signature); faster thanfind_referencesfor local-scope questions; explicit "when NOT to use" guidance prevents misuse as a cross-file search/lsp-renameprepare_renamesafety gate —prepare_renamenow runs as Step 2 (after symbol location, before reference enumeration); validates that the language server can rename at the given position before doing any further work; catches built-ins, keywords, and imported external package names that cannot be renamed across module boundaries; fail-fast with actionable error message/lsp-safe-editpreview_editpre-flight —preview_editnow runs before any disk write (Step 3); returnsnet_delta(errors introduced minus resolved) without touching disk;net_delta > 0pauses and asks before proceeding; multi-file: run per-file independently and sum deltas/lsp-safe-editcode actions on introduced errors — if post-edit diagnostics introduce new errors,suggest_fixesis called at each error location and available quick fixes are surfaced to the user withy/n/select; accepted actions applied viaapply_edit, then re-diff/lsp-safe-editmulti-file workflow — explicit protocol for edits spanning multiple files: open all, collect BEFORE for all, simulate each file independently, apply file-by-file (stop on first failure), merge AFTER diagnostics, check code actions on any file with new errors
Changed (2026-04-09)¶
lsp-verifyskill corrected and hardened — three fixes from dogfooding: (1)get_diagnosticsparameter corrected fromworkspace_dir(invalid) tofile_path— call once per changed file; (2) large test output warning added —run_testson large repos can return 300k+ chars and overflow context; recovery options: grep saved output file forFAILlines, or scope tests to the changed package directly; (3) all three layers now explicitly instructed to run in parallel since they are fully independent.lsp-dead-codeskill hardened against false positives — four improvements from dogfooding a full-repo dead-code audit: (1) mandatory Step 0 indexing warm-up — verify a known-active symbol returns ≥1 reference before trusting any results; explicit retry/restart protocol if indexing stalls; (2)"no identifier found"recovery note — methods on receivers shift the name column rightward, added grep-for-column technique to recover without blind retrying; (3) zero-reference cross-check — before classifying any handler/constructor/type as dead, grep wiring files (main.go,server.go,cmd/) for the symbol name to catch registration patterns (server.AddTool(HandleFoo)) that are invisible to LSP; (4) new caveat #2 documenting why registration-pattern references produce zero LSP hits; Step 3 classification table adds "Zero LSP, found by grep → ACTIVE" as a distinct outcome.
Fixed (2026-04-09)¶
list_symbolscoordinates are now 1-based —rangeandselectionRangepositions in the output were previously 0-based (raw LSP passthrough), inconsistent with every other coordinate-accepting tool (find_references,inspect_symbol, etc.) which all use 1-based input. The handler now shifts all line/character values by +1 before returning, including in nestedchildrensymbols. Thelsp-dead-codeskill instruction to "add 1 to selectionRange before passing to find_references" is now unnecessary — coordinates flow directly between tools. Breaking: any hardcoded line offsets captured from previouslist_symbolsoutput will be off by one.
Added (2026-04-09)¶
lsp-implementskill — find all concrete implementations of an interface or abstract type; composesgo_to_implementation+type_hierarchy; includes capability pre-check, risk assessment table (0 implementors → likely unused, >10 → breaking API change), and language notes for Go/TypeScript/Java/Rust/C#lsp-verifycode action fix section — when Layer 1 diagnostics return errors, callsuggest_fixesat the error location to surface available quick fixes, apply withapply_edit, then re-verify;suggest_fixesandapply_editadded to skillallowed-toolslist_symbolsformat: "outline"parameter — whenformat: "outline", returns the symbol tree as compact markdown (name [Kind] :line, indented for children) instead of JSON; reduces token volume ~5x for large files; useful for quick structural surveys before targeted navigation. Default behavior (JSON) unchanged.start_lsplanguage_idparameter — optional field selects a specific configured server in multi-server mode (e.g.language_id: "go"targets gopls,language_id: "typescript"targets tsserver); routes via newServerManager.StartForLanguagewhich matches bylanguage_idfield or extension set; withoutlanguage_id, behavior is unchanged (StartAll). Fixes an agent usability gap where the wrong language server could be active in a mixed-language repo with no in-session override. Description updated to recommendget_server_capabilitiesfor diagnosing active-server mismatches.apply_edittext-match mode — newfile_path+old_text+new_textparameter mode; findsold_textin the file (exact byte match first, then whitespace-normalised line match that tolerates indentation differences) and applies the replacement without requiring line/column positions; positionalworkspace_editmode unchangedlsp-edit-symbolskill — edit a named symbol without knowing its file or position; composesfind_symbol→list_symbols→apply_editto resolve the symbol name to its definition range and apply the edit; decision guide covers signature-only edits, full-body replacements, and ambiguous symbol disambiguationget_symbol_sourcetool — returns the source code of the innermost symbol (function, method, struct, class, etc.) whose range contains a given cursor position; composestextDocument/documentSymbol+ file read;findInnermostSymbolwalks the symbol tree recursively to find the deepest enclosing symbol; acceptsline+character(1-based) orposition_pattern(@@-syntax);characteraliased tocolumnfor consistency with other tools; CI-verified intestGetSymbolSourceacross all 22 languages- MCP log notifications — internal log messages (LSP server start, tool dispatch errors, indexing events) now route as
notifications/messageto the connected MCP client viamcpSessionSender; wired throughInitializedHandlerinServerOptionsso the live*ServerSessionis captured per-connection; before session init and on send failure, falls back to stderr; level threshold controlled byset_log_level get_symbol_documentationtool — fetch authoritative documentation for a named symbol from local toolchain sources (go doc, pydoc, cargo doc) without requiring an LSP hover response. Works on transitive dependencies not indexed by the language server. Returns{ symbol, language, source, doc, signature }. Dispatches to per-language toolchain commands with a 10-second timeout; strips ANSI escape codes; returns a structured error (not MCP error) when the toolchain fails so callers can fall back to LSP hover.lsp-docsskill — three-tier documentation lookup: (1)inspect_symbol(hover, fast, live); (2)get_symbol_documentation(offline, authoritative, works on unindexed deps); (3)go_to_definition+get_symbol_source(source fallback). Use when hover text is absent or the symbol is in a transitive dependency.
Changed (2026-04-09)¶
- Skill descriptions updated with trigger conditions — all four skill
descriptionfields now include explicit "use when" clauses per the Claude Code skills spec, enabling automatic invocation when relevant. Descriptions trimmed to ≤250 chars (spec cap). Non-speccompatibilityfield moved to markdown body.argument-hintadded tolsp-renameandlsp-edit-exportfor autocomplete UX. - Skills migrated to Agent Skills directory format — each skill is now a self-contained directory (
lsp-rename/SKILL.md,lsp-safe-edit/SKILL.md,lsp-edit-export/SKILL.md,lsp-verify/SKILL.md) conforming to the Agent Skills open spec. Flat.mdfiles and sharedPATTERNS.mdremoved.patterns.mdduplicated into each skill'sreferences/directory (spec requires self-contained skills). Frontmatter updated:user-invocableremoved (not in spec),allowed-toolsfixed to space-delimited,compatibilityfield added.install.shupdated to symlink skill directories to~/.claude/skills/instead of flat files.
Added (2026-04-08) — LSP Skills wave¶
go_to_symbolMCP tool — navigate to any symbol by dot-notation path (e.g."MyClass.method","pkg.Function") without needing a file path or line/column; usesGetWorkspaceSymbolsto find candidates and resolves to the definition location; supports optionalworkspace_rootandlanguagefilters- Position-pattern parameter (
position_pattern) —@@cursor marker syntax for position-based tools;ResolvePositionPatternsearches file content for the pattern and returns the 1-indexed line/col of the character immediately after@@;ExtractPositionWithPatternintegrates with existingextractPositionfallback; field added toGetInfoOnLocationArgs,GetReferencesArgs,GoToDefinitionArgs, andRenameSymbolArgs - Dry-run preview mode for
rename_symbol—dry_run: truereturns a preview envelope{ "workspace_edit": {...}, "preview": { "note": "..." } }without writing to disk; existing behavior unchanged whendry_runis omitted or false - Four agent-native skills —
lsp-safe-edit,lsp-edit-export,lsp-rename,lsp-verify; compose agent-lsp tools into single-command workflows for safe editing, exported-symbol refactoring, two-phase rename, and full diagnostic+build+test verification skills/install.sh— executable install script for registering skills with MCP clients
Fixed (2026-04-08)¶
run_buildandrun_testsin Go workspaces — both tools now unconditionally setGOWORK=offwhen runninggo buildandgo test; Go searches upward through parent directories forgo.workfiles, and when found,./...patterns only match modules listed in the workspace file; settingGOWORK=offforces Go to build/test all modules in the directory, matching the tool's intent
Added (2026-04-08)¶
run_build,run_tests, andget_tests_for_fileMCP tools — three new build-tool integration tools that do not requirestart_lsp; language-specific dispatch:go build ./.../cargo build/tsc --noEmit/mypy .(run_build),go test -json ./.../cargo test --message-format=json/pytest --tb=json/npm test(run_tests); test failurelocationfields are LSP-normalized (file URI- zero-based range) — paste directly into
go_to_definitionorfind_references;get_tests_for_filereturns test files for a source file via static lookup (no test execution); shared runner abstraction ininternal/tools/runner.go; tool count 42 → 45 - Build tool dispatch expanded to 9 languages —
run_buildandrun_testsnow dispatch for csharp (dotnet build/dotnet test), swift (swift build/swift test), zig (zig build/zig build test), kotlin (gradle build --quiet/gradle test --quiet) in addition to the original 5 (go, typescript, javascript, python, rust);get_tests_for_fileupdated with patterns for all new languages apply_editreal file-write test — replaced no-op empty WorkspaceEdit with a full format→apply→re-format cycle; Go, TypeScript, and Rust fixtures each have a blank line with deliberate trailing whitespace that their formatters strip; secondformat_documentcall returning empty edits proves the write persisted to disk; skip message when fixture already clean (subsequent runs on same checkout)detect_lsp_serversextended to 22 languages — addedknownServersentries and file extension mappings for C#, Kotlin, Lua, Swift, Zig, CSS/SCSS/Less, HTML, Terraform, Scala; fixed.kt/.ktsextensions which were incorrectly mapped tojavainstead ofkotlin- Zig language support —
zlsadded as 19th CI-verified language; dedicatedmulti-lang-zigCI job; fixture withperson.zig,greeter.zig,main.zig,build.zig - CSS language support —
vscode-css-language-serveradded as 20th CI-verified language; zero new CI install cost (vscode-langservers-extractedalready present); fixture:styles.css - HTML language support —
vscode-html-language-serveradded as 21st CI-verified language; zero new CI install cost; fixture:index.html - Terraform language support —
terraform-ls(HashiCorp) added as 22nd CI-verified language; dedicatedmulti-lang-terraformCI job; fixture:main.tf,variables.tf - Lua language support —
lua-language-serveradded as 17th CI-verified language; fixture withperson.lua,greeter.lua,main.lua(EmmyDoc annotations for type-aware hover); dedicatedmulti-lang-luaCI job; binary installed from GitHub releases - Swift language support —
sourcekit-lspadded as 18th CI-verified language; fixture withPerson.swift,Greeter.swift,main.swift,Package.swift; dedicatedmulti-lang-swiftCI job onmacos-latest(sourcekit-lsp ships with Xcode, zero install cost) - Scala language support —
metalsadded as 16th CI-verified language; fixture withPerson.scala,Greeter.scala,Main.scala,build.sbt; dedicatedmulti-lang-scalaCI job withcontinue-on-error: trueand 30-minute timeout (metals requires sbt compilation on cold start) - Kotlin language support —
kotlin-language-serveradded as 15th CI-verified language; fixture withPerson.kt,Greeter.kt,main.kt,build.gradle.kts; added tomulti-lang-coreCI job (reuses Java setup); full Tier 1 + Tier 2 coverage - C# language support —
csharp-lsadded as 14th CI-verified language; fixture withPerson.cs,Greeter.cs,Program.cs; full Tier 1 + Tier 2 coverage including hover, definition, references, completions, formatting, rename, highlights - CI workflow split into 4 parallel jobs —
test(unit + binary smoke),multi-lang-core(Go/TypeScript/Python/Rust/Java),multi-lang-extended(C/C++/JS/PHP/Ruby/YAML/JSON/Dockerfile/CSharp),speculative-test(gopls +TestSpeculativeSessions); unit tests now correctly run./internal/... ./cmd/...instead of-run TestBinary;TestSpeculativeSessionsnow in CI - Integration test coverage expanded to 26 tools — multi-language Tier 2 matrix grown from 12 → 26 tools per language: added
testGetDocumentHighlights,testGetInlayHints,testGetCodeActions,testPrepareRename,testRenameSymbol,testGetServerCapabilities,testWorkspaceFolders,testGoToTypeDefinition,testGoToImplementation,testFormatRange,testApplyEdit,testDetectLspServers,testCloseDocument,testDidChangeWatchedFiles;TestSpeculativeSessionsintest/speculative_test.gocovers full lifecycle: create,simulate_edit(non-atomic),preview_edit,simulate_chain, evaluate, discard, commit, destroy rename_symbolfuzzy position fallback — when the direct position lookup returns an emptyWorkspaceEdit, falls back to workspace symbol search by hover name and retries at each candidate position; mirrors the fuzzy fallback already ingo_to_definitionandfind_references; handles AI position imprecision without correctness regression- Multi-root workspace support —
add_workspace_folder,remove_workspace_folder,list_workspace_folderstools;workspace/didChangeWorkspaceFoldersnotifications; enables cross-repo references, definitions, and diagnostics across library + consumer repos in one session; workspace folder list persisted on client and initialized fromstart_lsproot get_document_highlights— file-scoped symbol occurrence search (textDocument/documentHighlight); returns ranges with read/write/text kinds; instant, no workspace scan;DocumentHighlightandDocumentHighlightKindtypes added tointernal/types- Auto-watch workspace —
fsnotifywatcher starts automatically afterstart_lsp; forwards file changes to the LSP server viaworkspace/didChangeWatchedFiles; debounced 150ms; skips.git/,node_modules/, etc.;did_change_watched_filestool no longer required for normal editing workflows get_server_capabilities— returns server identity (name,versionfromserverInfo), full LSP capability map, and classified tool lists (supported_tools/unsupported_tools) based on what the server advertised at initialization; lets AI pre-filter capability-gated tools before calling them;GetCapabilities()andGetServerInfo()methods added toLSPClient;serverName/serverVersionnow captured from initialize responseget_inlay_hints— new MCP tool (textDocument/inlayHint); returns inline type annotations and parameter name labels for a range; capability-guarded (returns empty array when server does not supportinlayHintProvider);InlayHint,InlayHintLabelPart,InlayHintKindtypes added tointernal/typesdetect_lsp_servers— new MCP tool; scans workspace for source languages (file extensions + root markers, scored by prevalence), checks PATH for corresponding LSP server binaries, returnssuggested_configentries ready to paste into MCP config; deduplicates shared binaries (c+cpp → one clangd entry)find_symbolenrichment — newdetail_level,limit,offsetparams;detail_level=hoverenriches a paginated window of results with hover info (type signature + docs);symbols[]always returns full result set;enriched[]+paginationreturned for the window; mirrors mcp-lsp-bridge's ToC + detail-window patterntype_hierarchy— MCP tool fortextDocument/typeHierarchy;direction: supertypes/subtypes/both;TypeHierarchyItemtype (LSP 3.17); CI-verified for Java (jdtls) and TypeScript- LSP response normalization —
GetDocumentSymbols,GetCompletion,GetCodeActionsnow return concrete typed Go structs;NormalizeDocumentSymbols(two-passSymbolInformation[]→DocumentSymbol[]tree reconstruction),NormalizeCompletion,NormalizeCodeActionsininternal/lsp/normalize.go
Added¶
- Auto-infer workspace root from file path — all per-file
mcp__lsp__*tools now automatically walk up from the file path to find a workspace root marker (go.mod,package.json,Cargo.toml,pyproject.toml,setup.py,.git) and initialize the correct LSP client if none is active;start_lspis no longer required before first use internal/config.InferWorkspaceRoot(filePath)— exported helper, walks directory tree upward checking markers in priority order-
cmd/agent-lsp/server.go— all 17 per-file tool handlers wrapped withclientForFileWithAutoInit; double-checked locking ensures thread-safe single initialization per workspace root -
Tests for
Destroy(session removal + not-found error),ApplyEditterminal and dirty guards, andlanguageToExtension(all 10 named cases + default fallback) — previously only the"go"case was exercised
Changed¶
Commitusesmaps.Copyinstead of a manual loop to build the workspace edit patch
Fixed¶
logging.Logdata race oninitWarningeliminated — read and write now holdmu.Lock()before accessing the field; previously two concurrentLog()calls could both observe the non-empty warning and race to zero itServerManager.StartAllnow shuts down all previously-initialized clients before returning on failure — previously leaked LSP subprocesses and open pipes when any server in a multi-server config failed to initializeresources.ResourceEntrytype deleted — had zero production callersmcp__lsp__*tool routing fixed:settings.jsonnow passes explicitgo:goplsargs so gopls is always the default client and entry[0]; previously alphabetical ordering made clangd the default, causing all.gofile queries to be answered by clangd with invalid AST errorsEvaluateno longer permanently breaks a session when context cancellation races the semaphore acquire —SetStatus(StatusEvaluating)is now set only afterAcquiresucceeds, so a cancelled acquire leaves the session inStatusMutatedand allows retrysession.Statusreads inEvaluateandCommitnow holdsession.mubefore comparison, eliminating a data race with concurrentSetStatuswrites detected by the Go race detectorHandleSimulateEditAtomicnow callsmgr.Discardbefore returning early onEvaluatefailure — previously the LSP client retained stale in-memory document content until the nextopen_documentcallworkspace/applyEditdispatch now usescontext.WithTimeout(context.Background(), defaultTimeout)instead of a plaincontext.Background()— prevents indefinite blocking on large workspace edits in the read loopReopenDocumentuntracked-URI fallback now infers language ID from file extension vialanguageIDFromURIinstead of hardcoding"plaintext"— gopls previously ignored these files silently, returning zero diagnosticsdeactivatemethod andTestRegistry_Deactivatedeleted frominternal/extensions— method had no production callers after being unexported in audit-2SerializedExecutor.Acquirenow respects context cancellation — replacedsync.Mutexwith a buffered-channel semaphore; callers that pass a cancelled or deadline-exceeded context toApplyEdit,Evaluate, orDiscardnow receivectx.Err()instead of blocking indefinitelygenerateResourceListdead function removed;resourceTemplatesexported asResourceTemplatesand wired intoserver.goviaAddResourceTemplate— MCP clients can now discover per-filelsp-diagnostics://,lsp-hover://, andlsp-completions://URIs viaresources/listExtensionRegistry.Deactivateunexported todeactivate— method had no external callers; was test-onlyapplyRangeEditcross-reference comment updated to point toLSPClient.applyEditsToFileto prevent independent bug-fix divergenceRootDir()doc comment corrected — previously carried theInitializedoc comment verbatim due to copy-pasteworkspace/configurationparams unmarshal error now logged at debug level instead of silently discarded with_ =; fallback empty-array response preservedapplyDocumentChangesdiscriminator unmarshal failure now logs at debug level and skips the malformed entry instead of falling through to theTextDocumentEditbranch-
init()ininternal/loggingno longer writes to stderr at import time — invalidLOG_LEVELvalue is stored and flushed on the firstLog()call instead -
ApplyEditArgs.Edittype changed frominterface{}tomap[string]interface{}— Claude Code's MCP schema validator rejected the empty schema produced byinterface{}and silently dropped all 34 tools silently;map[string]interface{}produces a valid"type": "object"schema preview_editnow callsDiscardbeforeDestroy— without Discard, gopls retained the modified document between atomic calls; the next call's baseline captured stale (modified) diagnostics, producing incorrectnet_deltavaluesstart_lspin multi-server/auto-detect mode now callsServerManager.StartAll— previously only restarted the first detected server (clangd), leaving gopls and other language servers uninitialized; simulation sessions for Go files now correctly use goplscsResolverwrapper added toserver.gosoSessionManagersees clients set bystart_lspat runtime; previously the original resolver held a nil client untilstart_lspwas called, causing "no LSP client available" errorsSessionManager.CreateSessionroutes by language extension viaClientForFile— in multi-server modeDefaultClient()returned clangd; routing by.go/.py/.tsextension now picks the correct language server per sessionlanguageToExtensionhelper added tointernal/session/manager.go— maps language IDs (go,python,typescript,javascript,rust,c,cpp,java,ruby) to file extensions for client routing
Added¶
- Speculative code sessions — simulate edits without committing to disk; create sessions with baseline diagnostics, apply edits in-memory, evaluate diagnostic changes (errors introduced/resolved), and commit or discard atomically; implemented via
internal/sessionpackage with SessionManager (lifecycle), SerializedExecutor (LSP access serialization), and diagnostic differ (baseline vs current comparison); 8 new MCP tools:create_simulation_session,simulate_edit,evaluate_session,simulate_chain,commit_session,discard_session,destroy_session,preview_edit; tool count 26 → 34; enables safe what-if analysis and multi-step edit planning before execution; useful for AI assistants to verify edits won't introduce errors before applying - Tier 2 language expansion — CI-verified language count 7 → 13: C++ (clangd), JavaScript (typescript-language-server), Ruby (solargraph), YAML (yaml-language-server), JSON (vscode-json-language-server), Dockerfile (dockerfile-language-server-nodejs); C++ and JavaScript reuse existing CI binaries (zero new install cost); Ruby/YAML/JSON/Dockerfile each add one install line
- Integration test harness updated to 13 langConfig entries with correct fixture positions, cross-file coverage, and per-language capability flags (
supportsFormatting,supportsDeclaration) - GitHub Actions
multi-lang-testjob extended with 4 new language server install steps
Fixed¶
clientForFilenow usescs.get()as the authoritative client afterstart_lsp— multi-server routing changes causedstart_lspto updatecsbut leaveresolver's stale client reference in place, causing all tools to return "LSP client not started" after a successfulstart_lsp;cs.get()is now always used for single-server mode- Test error logging for
open_documentandget_diagnosticsnow extracts text fromContent[0]instead of printing the raw slice address
Added¶
- Multi-server routing — single
agent-lspprocess manages multiple language servers; routes tool calls to the correct server by file extension. Supports inline arg-pairs (go:gopls typescript:tsserver,--stdio) and--config agent-lsp.json; backward-compatible with existing single-server invocation find_callerstool — single tool withdirection: "incoming" | "outgoing" | "both"(default: both); hides the two-step LSP prepare/query protocol behind one call; returns typed JSON withitems,incoming,outgoing- Fuzzy position fallback for
go_to_definitionandfind_references— when a direct position lookup returns empty, falls back to workspace symbol search by hover name and retries at each candidate; handles AI assistant position imprecision without correctness regression - Path traversal prevention —
ValidateFilePathinWithDocumentresolves all..components and verifies the result is within the workspace root; storesrootDironLSPClient(set duringInitialize) types.CallHierarchyItem,types.CallHierarchyIncomingCall,types.CallHierarchyOutgoingCall— typed protocol structs for call hierarchy responsestypes.TextEdit,types.SymbolInformation,types.SemanticToken— typed protocol structs;FormatDocument/FormatRangeandGetWorkspaceSymbolsmigrated frominterface{}to typed returnstypes.SymbolKind,types.SymbolTag— integer enum types used across call hierarchy and symbol structsget_semantic_tokenstool — classifies each token in a range as function/parameter/variable/type/keyword/etc usingtextDocument/semanticTokens/range(falls back to full); decodes LSP's delta-encoded 5-integer tuple format into absolute 1-based positions with human-readable type and modifier names from the server's legend; only MCP-LSP server to expose this- Semantic token legend captured during
initialize—legendTypes/legendModifiersstored onLSPClientunder dedicated mutex;GetSemanticTokenLegend()accessor added types.SemanticToken— typed struct for decoded token output- Tool count: 24 → 26
Added (LSP 3.17 spec compliance)¶
workspace/applyEditserver-initiated request handler — client now respondsApplyWorkspaceEditResult{applied:true}instead of null; servers using this for code actions (e.g. file creation/rename) no longer silently faildocumentChangesresource operations:CreateFile,RenameFile,DeleteFileentries now executed (discriminated bykindfield); previously onlyTextDocumentEditwas processed$/progress reportkind handled — intermediate progress notifications are now logged at debug level instead of silently discardedPrepareRenameboolcapability case —renameProvider: true(no options object) no longer incorrectly sendstextDocument/prepareRename; correctly returns nil whenprepareProvidernot declareduriToPathnow usesurl.Parsefor RFC 3986-correct percent-decoding — fixes file reads/writes for workspaces with spaces or special characters in path (was using raw string slicing, leaving%20literal)- Removed deprecated
rootPathfrominitializeparams — superseded byrootUriandworkspaceFolders
Added¶
- Multi-language integration test harness — Go port of
multi-lang.test.jsusingmcp.CommandTransport+ClientSession.CallToolfrom the official Go MCP SDK - Tier 1 tests (start_lsp, open_document, get_diagnostics, inspect_symbol) for all 7 languages: TypeScript, Python, Go, Rust, Java, C, PHP
- Tier 2 tests (list_symbols, go_to_definition, find_references, get_completions, find_symbol, format_document, go_to_declaration) for all 7 languages
- Test fixtures for all 7 languages with cross-file greeter files for
find_referencescoverage - GitHub Actions CI:
testjob (unit tests, every PR) andmulti-lang-testjob (full 7-language matrix) WaitForDiagnosticsinitial-snapshot skip — matches TypeScriptsawInitialSnapshotbehavior; prevents early exit when URIs are already cachedInitializenow sendsclientInfo,workspace.didChangeConfiguration, andworkspace.didChangeWatchedFilescapabilities to match TypeScript reference- Initial Go port of LSP-MCP — full 1:1 implementation with TypeScript reference
- All 24 tools: session (4), analysis (7), navigation (5), refactoring (6), utilities (2)
WithDocument[T]generic helper — Go equivalent of the TypeScriptwithDocumentpattern- Single binary distribution via
go install github.com/blackwell-systems/agent-lsp/cmd/agent-lsp@latest - Buffer-based LSP message framing with byte-accurate
Content-Lengthparsing (no UTF-8/UTF-16 mismatch) WaitForDiagnosticswith 500ms stabilisation windowWaitForFileIndexedwith 1500ms stability window — lets gopls finish cross-package indexing before issuingfind_references- Extension registry with compile-time factory registration via
init() SubscriptionHandlersandPromptHandlerson theExtensioninterface- Full 14-method LSP request timeout table matching the TypeScript reference
$/progresstracking for workspace-ready detection- Server-initiated request handling:
window/workDoneProgress/create,workspace/configuration,client/registerCapability - Graceful SIGINT/SIGTERM shutdown with LSP
shutdown+exitsequence GetCodeActionspasses overlapping diagnostics in context per LSP 3.17 §3.16.8SubscribeToDiagnosticsreplays current diagnostic snapshot to new subscribersReopenDocumentfallback to disk read on untracked URI
Fixed¶
FormattedLocationJSON field names match TypeScript response shape (file,line,column,end_line,end_column)apply_editargument field isworkspace_editin both handler and server registration (waseditinApplyEditArgsstruct, causing every call to fail silently)execute_commandargument field isargs(matches TypeScript schema)find_referencesinclude_declarationdefaults tofalse(matches TypeScript schema)GetInfoOnLocationhover parsing handles all four LSPMarkupContentshapes (string, MarkupContent, MarkedString, MarkedString array)WaitForDiagnosticstimeout 25,000ms (matches TypeScript reference)applyEditsToFilesends correct incremented version number intextDocument/didChangeformat_documentandformat_rangedefaulttab_sizeis 2 (matches TypeScript schema)format_documentandformat_rangenow surface invalidtab_sizeargument errors to callers instead of silently using the defaultdid_change_watched_filesaccepts emptychangesarray per LSP specrestart_lsp_serverrejects missingroot_dirwith a clear error instead of sending malformedrootURI = "file://"to the LSP serverGetSignatureHelp,RenameSymbol,PrepareRename,ExecuteCommandnow propagate JSON unmarshal errors instead of returningnil, nilon malformed LSP responsesLSPDiagnostic.Codechanged fromstringtointerface{}— integer codes from rust-analyzer, clangd, etc. are no longer silently dropped- Removed dead
docVersfield fromLSPClient(version tracking usesdocMeta.version) Shutdownerror now wrapped with operation contextGenerateResourceListandResourceTemplatesmade unexported — they had no external callers and were not wired to the MCP serverWaitForDiagnosticserrors in resource handlers now propagate instead of being logged and suppressed- Removed dead
sepvariable inframing.go(tryParseallocated[]byte("\r\n\r\n")then immediately blanked it)