Priorität: 3 — das aufwendigste Einzelfeature der Roadmap und das mit dem unsichersten Nutzen-Verhältnis. Bewusst als letztes eingeordnet. Vor der Umsetzung sollte ein Spike klären, ob sich der Aufwand gegenüber grep tatsächlich auszahlt.
Referenz: Claude Code v2.0.74 (LSP-Tool für Go-to-Definition, Find-References, Hover), v2.1.0 (Tool wurde fälschlich aktiviert, wenn keine Server konfiguriert waren), v2.1.153 (Race Condition beim Start).
Motivation
smith findet Code heute ausschließlich über grep und glob. Das ist textuell und kennt keine Semantik:
- „Wo wird
Registry#execute_single_call aufgerufen?" → grep findet auch Kommentare, Strings und gleichnamige Methoden anderer Klassen
- „Was ist der Typ hier?" → gar nicht beantwortbar
- „Alle Implementierungen von
Approver" → nur mit Glück über Namenskonventionen
Ein LSP-Client macht daraus präzise Anfragen und spart Kontext: Statt 200 Grep-Treffer zu lesen und zu filtern, kommen fünf exakte Referenzen zurück. Bei Crystal ist das besonders reizvoll, weil crystalline als Language Server existiert.
Realistische Einschätzung (bitte vor der Umsetzung lesen)
Dieses Feature hat ein schlechteres Aufwand/Nutzen-Verhältnis als jedes andere in dieser Roadmap:
- LSP ist ein umfangreiches Protokoll mit Initialisierung, Dokument-Synchronisation, Positionskodierung (UTF-16!) und servereigenen Eigenheiten
- Jede Sprache braucht einen eigenen Server, der installiert sein muss
- Der Nutzen hängt stark von der Serverqualität ab;
crystalline ist gut, aber nicht mit rust-analyzer oder gopls vergleichbar
- Ein großer Teil des Nutzens lässt sich mit gutem
grep plus einem präzisen Prompt erreichen
Empfehlung: Erst einen Spike mit einer Sprache (Crystal via crystalline) und einer Operation (find references) bauen, im Alltag messen, ob es tatsächlich Kontext spart — und erst danach entscheiden, ob ausgebaut wird. Das Ergebnis dieses Spikes gehört als Kommentar in dieses Issue.
Spezifikation (nach positivem Spike)
Konfiguration
[[lsp.servers]]
name = "crystal"
command = "crystalline"
args = ["--stdio"]
extensions = [".cr"]
[[lsp.servers]]
name = "typescript"
command = "typescript-language-server"
args = ["--stdio"]
extensions = [".ts", ".tsx"]
Ohne konfigurierte Server wird das Tool nicht registriert — genau der Bug, den Claude Code in v2.1.0 fixen musste. Kein Tool anbieten, das nichts kann.
Tool lsp
{ "type": "object",
"properties": {
"operation": { "enum": ["definition", "references", "hover", "symbols", "diagnostics"] },
"path": { "type": "string" },
"symbol": { "type": "string", "description": "Symbol name; alternative to line/character" },
"line": { "type": "integer" },
"character": { "type": "integer" }
},
"required": ["operation", "path"] }
Wichtig für die Brauchbarkeit: Modelle denken in Symbolnamen, nicht in Zeilen/Spalten. Das Tool sollte symbol unterstützen und intern über documentSymbol die Position auflösen. Ein Tool, das exakte Koordinaten verlangt, wird vom Modell kaum korrekt bedient.
Rückgabeformat
Kompakt und für ein Modell lesbar, nicht rohes LSP-JSON:
References to Registry#execute_single_call (3):
src/smith/tools/registry.cr:77 res = execute_single_call(call)
src/smith/tools/registry.cr:129 res = execute_single_call(call)
spec/tools/registry_spec.cr:44 registry.execute_single_call(...)
Fallstricke
- Positionskodierung: LSP verwendet standardmäßig UTF-16-Codeunits für Spalten. Bei ASCII-Quelltext fällt das nie auf und bricht dann bei der ersten Datei mit Umlauten oder Emoji. Von Anfang an korrekt umrechnen.
- Dokument-Synchronisation: Der Server muss den aktuellen Dateiinhalt kennen. Nach jedem
write_file/edit_file ist ein didChange/didSave nötig, sonst arbeitet der Server auf veraltetem Stand und liefert falsche Positionen — was schlimmer ist als keine Antwort.
- Start-Latenz: Große Projekte indizieren mehrere Sekunden bis Minuten. Server lazy beim ersten
lsp-Call starten, nicht beim Sessionstart, und Anfragen bis zur Bereitschaft zurückstellen (CC v2.1.153).
- Lebenszyklus: wie bei MCP — Shutdown inklusive
Signal::INT-Handler, keine Waisenprozesse.
Umsetzung
src/smith/lsp/protocol.cr # JSON-RPC über stdio, Content-Length-Framing (nicht zeilenbasiert wie MCP!)
src/smith/lsp/client.cr # initialize, didOpen/didChange, Requests
src/smith/lsp/manager.cr # Server pro Sprache, Lebenszyklus
src/smith/tools/lsp_tool.cr
Synergie: Wird das MCP-Issue zuerst umgesetzt, ist die JSON-RPC-Basis dort bereits vorhanden und sollte geteilt werden. Achtung auf den Unterschied im Framing: MCP über stdio nutzt zeilenbasiertes JSON, LSP nutzt Content-Length-Header. Die Request/Response-Zuordnung und der Lese-Fiber sind aber identisch.
Tests (spec/lsp/)
Gegen einen Fake-LSP-Server in Crystal, keine externe Toolchain in der Testsuite:
Content-Length-Framing beim Senden und Empfangen, auch bei Nachrichten über mehrere Reads
- UTF-16-Positionsumrechnung für eine Zeile mit Umlauten und Emoji
symbol → Position über documentSymbol aufgelöst
- Anfrage vor Server-Bereitschaft wird zurückgestellt, nicht verworfen
- Server nicht installiert → klare Meldung, Session läuft weiter
- Keine Server konfiguriert → Tool fehlt in
registry.specs
- Shutdown hinterlässt keine Prozesse
Akzeptanzkriterien
Nicht-Ziele
- Rename/Refactoring über LSP (Schreiboperationen bleiben bei
edit_file)
- Code-Completion
- Automatische Installation von Language Servern
Priorität: 3 — das aufwendigste Einzelfeature der Roadmap und das mit dem unsichersten Nutzen-Verhältnis. Bewusst als letztes eingeordnet. Vor der Umsetzung sollte ein Spike klären, ob sich der Aufwand gegenüber
greptatsächlich auszahlt.Referenz: Claude Code v2.0.74 (LSP-Tool für Go-to-Definition, Find-References, Hover), v2.1.0 (Tool wurde fälschlich aktiviert, wenn keine Server konfiguriert waren), v2.1.153 (Race Condition beim Start).
Motivation
smith findet Code heute ausschließlich über
grepundglob. Das ist textuell und kennt keine Semantik:Registry#execute_single_callaufgerufen?" →grepfindet auch Kommentare, Strings und gleichnamige Methoden anderer KlassenApprover" → nur mit Glück über NamenskonventionenEin LSP-Client macht daraus präzise Anfragen und spart Kontext: Statt 200 Grep-Treffer zu lesen und zu filtern, kommen fünf exakte Referenzen zurück. Bei Crystal ist das besonders reizvoll, weil
crystallineals Language Server existiert.Realistische Einschätzung (bitte vor der Umsetzung lesen)
Dieses Feature hat ein schlechteres Aufwand/Nutzen-Verhältnis als jedes andere in dieser Roadmap:
crystallineist gut, aber nicht mitrust-analyzerodergoplsvergleichbargrepplus einem präzisen Prompt erreichenEmpfehlung: Erst einen Spike mit einer Sprache (Crystal via
crystalline) und einer Operation (find references) bauen, im Alltag messen, ob es tatsächlich Kontext spart — und erst danach entscheiden, ob ausgebaut wird. Das Ergebnis dieses Spikes gehört als Kommentar in dieses Issue.Spezifikation (nach positivem Spike)
Konfiguration
Ohne konfigurierte Server wird das Tool nicht registriert — genau der Bug, den Claude Code in v2.1.0 fixen musste. Kein Tool anbieten, das nichts kann.
Tool
lsp{ "type": "object", "properties": { "operation": { "enum": ["definition", "references", "hover", "symbols", "diagnostics"] }, "path": { "type": "string" }, "symbol": { "type": "string", "description": "Symbol name; alternative to line/character" }, "line": { "type": "integer" }, "character": { "type": "integer" } }, "required": ["operation", "path"] }Wichtig für die Brauchbarkeit: Modelle denken in Symbolnamen, nicht in Zeilen/Spalten. Das Tool sollte
symbolunterstützen und intern überdocumentSymboldie Position auflösen. Ein Tool, das exakte Koordinaten verlangt, wird vom Modell kaum korrekt bedient.Rückgabeformat
Kompakt und für ein Modell lesbar, nicht rohes LSP-JSON:
Fallstricke
write_file/edit_fileist eindidChange/didSavenötig, sonst arbeitet der Server auf veraltetem Stand und liefert falsche Positionen — was schlimmer ist als keine Antwort.lsp-Call starten, nicht beim Sessionstart, und Anfragen bis zur Bereitschaft zurückstellen (CC v2.1.153).Signal::INT-Handler, keine Waisenprozesse.Umsetzung
Synergie: Wird das MCP-Issue zuerst umgesetzt, ist die JSON-RPC-Basis dort bereits vorhanden und sollte geteilt werden. Achtung auf den Unterschied im Framing: MCP über stdio nutzt zeilenbasiertes JSON, LSP nutzt
Content-Length-Header. Die Request/Response-Zuordnung und der Lese-Fiber sind aber identisch.Tests (
spec/lsp/)Gegen einen Fake-LSP-Server in Crystal, keine externe Toolchain in der Testsuite:
Content-Length-Framing beim Senden und Empfangen, auch bei Nachrichten über mehrere Readssymbol→ Position überdocumentSymbolaufgelöstregistry.specsAkzeptanzkriterien
crystal specgrün, Format sauber, README-Abschnitt „LSP"Nicht-Ziele
edit_file)