From 851bb0bebe96d4328cd4fff946d8ad403fa6bde1 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sat, 4 Apr 2026 10:42:59 -0500 Subject: [PATCH 1/2] fix(pi-coding-agent): upgrade Kotlin LSP to official Kotlin/kotlin-lsp Closes #3493 --- packages/pi-coding-agent/src/core/lsp/defaults.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pi-coding-agent/src/core/lsp/defaults.json b/packages/pi-coding-agent/src/core/lsp/defaults.json index dbea73b6c..6bc16ba82 100644 --- a/packages/pi-coding-agent/src/core/lsp/defaults.json +++ b/packages/pi-coding-agent/src/core/lsp/defaults.json @@ -189,8 +189,8 @@ "fileTypes": [".java"], "rootMarkers": ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", ".project"] }, - "kotlin-language-server": { - "command": "kotlin-language-server", + "kotlin-lsp": { + "command": "kotlin-lsp", "args": [], "fileTypes": [".kt", ".kts"], "rootMarkers": ["build.gradle", "build.gradle.kts", "pom.xml", "settings.gradle", "settings.gradle.kts"] From 4df55a51c8c7a786a916f5389a3ff0982e3989d8 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sat, 4 Apr 2026 11:45:58 -0500 Subject: [PATCH 2/2] fix(lsp): add legacy alias for renamed kotlin-language-server key Users with existing lsp.json overrides referencing the old "kotlin-language-server" key would silently lose their Kotlin LSP config after the rename to "kotlin-lsp". LEGACY_ALIASES map remaps old keys during mergeServers() so overrides still merge correctly. --- .../pi-coding-agent/src/core/lsp/config.ts | 8 ++- .../src/core/lsp/lsp-legacy-alias.test.ts | 70 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts diff --git a/packages/pi-coding-agent/src/core/lsp/config.ts b/packages/pi-coding-agent/src/core/lsp/config.ts index cc104be21..758657856 100644 --- a/packages/pi-coding-agent/src/core/lsp/config.ts +++ b/packages/pi-coding-agent/src/core/lsp/config.ts @@ -12,6 +12,11 @@ import type { ServerConfig } from "./types.js"; const require = createRequire(import.meta.url); const DEFAULTS = require("./defaults.json") as Record>; +/** Map legacy server keys to their current names so user overrides still merge. */ +const LEGACY_ALIASES: Record = { + "kotlin-language-server": "kotlin-lsp", +}; + export interface LspConfig { servers: Record; /** Idle timeout in milliseconds. If set, LSP clients will be shutdown after this period of inactivity. Disabled by default. */ @@ -109,7 +114,8 @@ function mergeServers( overrides: Record>, ): Record { const merged: Record = { ...base }; - for (const [name, config] of Object.entries(overrides)) { + for (const [rawName, config] of Object.entries(overrides)) { + const name = LEGACY_ALIASES[rawName] ?? rawName; if (merged[name]) { const candidate = { ...merged[name], ...config }; const normalized = normalizeServerConfig(name, candidate); diff --git a/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts b/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts new file mode 100644 index 000000000..c1d4d99ec --- /dev/null +++ b/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts @@ -0,0 +1,70 @@ +// GSD2 — Regression test for LSP legacy server key aliases +// Copyright (c) 2026 Jeremy McSpadden + +/** + * When a default server key is renamed (e.g., kotlin-language-server → kotlin-lsp), + * user overrides referencing the old key must still merge correctly via LEGACY_ALIASES. + * + * This test exercises the merge path through loadConfig() with a temp project + * containing an lsp.json that uses the legacy key. + */ + +import { describe, it, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as os from "node:os"; +import { loadConfig } from "./config.js"; + +describe("LSP legacy server key aliases", () => { + let tmpDir: string; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "lsp-alias-test-")); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it("merges user override with legacy key 'kotlin-language-server' into 'kotlin-lsp'", () => { + // Write an lsp.json that uses the old key name with a command that exists (node) + // so resolveCommand doesn't filter it out. + const overrideConfig = { + servers: { + "kotlin-language-server": { + command: "node", + }, + }, + }; + fs.writeFileSync( + path.join(tmpDir, "lsp.json"), + JSON.stringify(overrideConfig), + ); + + // Also add root markers so the server is detected + fs.writeFileSync(path.join(tmpDir, "build.gradle.kts"), ""); + + const config = loadConfig(tmpDir); + + // The merged config should have kotlin-lsp (new key) with the user's command override + const kotlinServer = config.servers["kotlin-lsp"]; + assert.ok(kotlinServer, "kotlin-lsp should exist in merged config"); + assert.equal( + kotlinServer.command, + "node", + "command should be overridden from user config via legacy alias", + ); + assert.ok( + kotlinServer.fileTypes.includes(".kt"), + "fileTypes should be inherited from defaults", + ); + + // The old key should NOT appear as a separate entry + assert.equal( + config.servers["kotlin-language-server"], + undefined, + "legacy key should not appear as separate server", + ); + }); +});