All notable changes to this project will be documented here.
Format: Keep a Changelog. Versioning: SemVer.
bin/sbt-direct—SBT_DIRECT_MODE=auto(new default). Coordinator probes<workspace>/.bsp/sbt.jsonat start; picks bsp mode when present (warm ~130ms), otherwise falls back to oneshot with a log line telling the user to runsbt bspConfig. Explicitbspandoneshotremain supported for force-mode use.bin/tool-server-proxy.js—/healthnow reportschildrenAliveso external monitors can distinguish "coordinator up" from "coordinator up but backing child died".README.md— single-wrapper install guide covering all LSP + opt-in wrappers; verified before/after benchmark tables for all 5 opt-in wrappers.fixtures/node-formatter/eslint.config.js+package.json"type":"module"— eslint v10 flat-config so the fixture probe passes. Enableseslint-directto participate inscripts/verify.sh.
scripts/install.sh+scripts/uninstall.sh— corrected SessionStart hook entry schema so the prewarm hook actually registers in~/.claude/settings.json(previous entry silently ignored).docs/per-language/sbt.md— documents that bothbspandoneshotrequiredangerouslyDisableSandboxon macOS (sbt's BootServerSocket path not covered by a static allowlist)..github/workflows/ci.yml— runsinstall.shbefore the fixture-probe + opt-in-probe jobs; relaxes sha gate to shape-only so downstream CI on varying tool versions doesn't false-fail.
1.2.0 — 2026-04-22
bin/adapters/sbt-thin-client.js— persistent-JVM sbt adapter. Activated viaSBT_DIRECT_MODE=thin-clientor--mode thin-client. Coordinator spawnssbtin server mode once per workspace and proxies each/callthroughsbt --client "<task>"; warm calls land in 200-500ms vs 20-40s one-shot cold. Adoption: attaches to an externally-runningsbt shelliftarget/active.json+ live socket detected. Requiresinstall.shallowlist for ipcsocket paths (pre-merged intosandbox.filesystem.allowWrite).hooks/prewarm-direct-wrappers.py— SessionStart hook. Iterates~/.cache/*-direct/*/slots, probes each withGET /health, fires<wrapper>-direct start <workspace>in the background for any dead slot. Firstcallin a new session is warm.install.sh— wires the prewarm hook into~/.claude/settings.json'shooks.SessionStart(idempotent,unique_by(.command)).uninstall.shremoves it symmetrically.scripts/verify.sh VERIFY_STRICT_SHA=1— env-gated strict mode for CI. Default (unset/0) treats sha256-body mismatch as a warning so downstream users on different pyright/tsserver/csharp-ls versions don't fail the gate; structural-shape match (top_level + total_nodes) remains hard either way. CI workflow exportsVERIFY_STRICT_SHA=1.bin/tool-harness.js— shared coordinator primitives:resolveWorkspace,stateDir,freePort,serveHttp,invalidationLoop,callLog, plusframingreaders/writers (contentLength, jsonLine, tsserverMixed) and ajsonRpcClientcorrelation helperbin/tool-server-proxy.js— external-child coordinator; adapters declarechildren[],init,onChildMessage,call,triggersbin/node-formatter-daemon.js— in-process Node-library coordinator (sibling of tool-server-proxy); adapters declarepreload(workspace)+call(req, {pkg, state})bin/adapters/lsp-stdio.js— LSP adapter (py/ts/cs/java); extracted from monolithiclsp-stdio-proxy.jsbin/adapters/vue-hybrid.js— Vue LS v3 + tsserver hybrid adapter; extracted fromvue-direct-coordinator.jsbin/adapters/sbt-oneshot.js+bin/sbt-direct+bin/sbt-direct-coordinator.js+fixtures/scala-sbt/— per-call sbt coordinator (task,reload,version)bin/adapters/dotnet-cli.js+bin/dotnet-direct+bin/dotnet-direct-coordinator.js+fixtures/dotnet-csproj/— per-call dotnet coordinator (11 methods: build/test/restore/publish/run/pack/…); MSBuild build-server handles warm persistence transparentlybin/adapters/prettier.js+bin/prettier-direct+bin/prettier-direct-daemon.js+fixtures/node-formatter/— in-process prettier daemon (format,check,format-file,resolve-config,version)bin/adapters/eslint.js+bin/eslint-direct+bin/eslint-direct-daemon.js— in-process eslint daemon (lint-text,lint-files,fix-text,format-results,version)bin/adapters/scalafmt-cli.js+bin/scalafmt-direct+bin/scalafmt-direct-coordinator.js— per-call scalafmt coordinator (format-stdin,format-files,check-files,version)scripts/capture-baseline.sh+fixtures/baselines/*.json— per-wrapper JSON baselines (cold/warm timings + response sha256 + shape summary) for 5 LSP wrappersscripts/verify.sh --jsonandscripts/verify.sh --diff-baselinesmodesdocs/per-language/sbt.md,docs/per-language/dotnet.md,docs/per-language/node-formatters.md,docs/per-language/scalafmt.mdMIGRATION.md— describes the refactor, back-compat guarantees, rollback tagsCONTRIBUTING.md— new "Architecture overview" + rewritten "Hybrid servers" + "Non-LSP tools" sections covering the adapter contract for both module families
bin/lsp-stdio-proxy.jsbody replaced with composition oftool-harness+tool-server-proxy+adapters/lsp-stdio. Steady-state response shape + state-dir layout byte-identical; CLI unchanged. External Node importers keep working — the file name and argv contract are preserved.bin/vue-direct-coordinator.jsbody similarly replaced, now composingtool-harness+tool-server-proxy+adapters/vue-hybrid. Vue LS v3 + tsserver bridging preserved verbatim (configurePlugin → warmup → init order, tsserver/request↔response tuple unwrap + double-wrap).bin/py-direct,bin/ts-direct,bin/cs-direct,bin/java-directnow pass--tool-name <wrapper>so the harness'sstateDirresolves to the wrapper's existing slot instead of drifting to~/.cache/lsp-stdio-proxy-direct/….scripts/install.shsymlinks the new shared modules + adapters dir + 5 opt-in wrappers + their coordinators. Merges new permission entries + sandbox-write allowlist entries for the new cache dirs.README.mdlists the new opt-in wrappers; architecture section restructured (layout vs behavior) to describe the three-module split.
- Prettier/eslint adapter preload now consults
npm root -gso globally-installed packages are picked up when the workspace has no local install. - Stale-config bugs on existing LSP wrappers: touching
tsconfig.json/pyrightconfig.json/*.csproj/pom.xml/package.jsontriggersworkspace/didChangeConfiguration+workspace/didChangeWatchedFileswithout a stop/start cycle. Hard-trigger files (.env,.jvmopts,global.json,pnpm-lock.yaml,.python-version,.java-version,dotnet-tools.json) force coordinator restart on next call.
<stateDir>/calls.log— per-call JSON lines:{ts, method, ms, adopted, invalidation_fired, outcome}. Disable viaTOOL_DIRECT_CALLLOG=0.<stateDir>/triggers.json— mtime baseline for invalidation.
scripts/verify.sh --diff-baselinesclean on py/ts/cs/java/vue (response-body sha + shape match pre-refactor baselines; timings within machine noise).- Invalidation smoke on all 5 LSP wrappers: soft trigger preserves PID, hard trigger restarts coordinator.
dotnet-direct call version {}returns{exit: 0, stdout: "10.0.103\n"}.prettier-direct call format-file {...}returns{formatted, changed};eslint-direct call version {}returns{version: "10.2.1"}.sbt-direct call version {}reads a real Play 3 / Scala 3 project'sbuild.sbt→{exit: 0, stdout: "sbt version in this project: 1.11.6\nsbt runner version: 1.10.11"}.sbt-direct call task {"task":"scalafmtCheckAll"}against the same project runs the sbt-scalafmt plugin end-to-end, correctly surfaces per-file formatting diagnostics. Under Claude Bash sandbox,install.shpre-allows/private/var/folders/**/.sbt/**+~/.sbt/**+~/.ivy2/**+~/.coursier/**so sbt's BootServerSocket + dependency caches write withoutdangerouslyDisableSandbox.scalafmt-direct call check-files {...}against a version-matched fixture →{exit: 0, stdout: "All files are formatted with scalafmt :)"}.scalafmt-direct call format-stdin {source: "object A{def x=1}", filepath: "A.scala"}→{exit: 0, stdout: "object A { def x = 1 }\n"}.dotnet-direct call version {}→{exit: 0, stdout: "10.0.103\n"}.prettier-direct call format-file {filepath}→{formatted, changed}.eslint-direct call version {}→{version: "10.2.1"}.hooks/tests/run.sh→ 97/97 pass.- 19/19 harness + proxy smoke tests (
node --test bin/*.test.js).
1.1.0 — 2026-04-21
bin/java-direct— Java via jdtls (Eclipse JDT.LS) proxy; per-workspace-datadir under wrapper state hash; 180s start timeout for JVM + Equinox bootfixtures/java/— minimal Maven project (pom.xml+src/main/java/com/example/Hello.java) for CI + verifydocs/per-language/java.md— install (brew install jdtls), workspace markers, op surface, jdtls quirks (build-job latency,~/.eclipsewrite requirement)docs/convention.md— java row added to language tablehooks/enforce-lsp-over-grep.py— extendedCODE_EXT/EXT_LANG/RG_TYPE_LANG/POS_CODE_FILE_RE/LANG_DIRECT_WRAPPER/PLUGIN_BINARY_MAPto cover.java; reuses python/typescript/csharp suggestion branchhooks/tests/test_enforce_lsp_over_grep.py— java cases for bash grep/rg/find, nativeGreptool (type/glob/path), positional code-file detectionscripts/install.sh+scripts/verify.sh—java-directsymlinked + java fixture probe added.github/workflows/ci.yml—brew install jdtlsstep on macos-latest (linux skipped — no first-class jdtls package)README.md— java row in benchmarks table + per-language link list
- functional probe:
documentSymbol(2 symbols),workspace/symbol "Hello"(1 result after build settle),referencesongreetmethod (2 refs) - timing: cold start 2.16s, cold call 907ms, warm avg ~85ms (
documentSymbol/workspace/symbol/references) - hook tests: 97/97 pass
1.0.0 — 2026-04-21
bin/metals-direct— Scala via metals-mcp over HTTP (17 semantic tools)bin/vue-direct+bin/vue-direct-coordinator.js— Vue LS v3 hybrid bridge (Vue LS + tsserver +@vue/typescript-plugin)bin/py-direct— pyright-langserver proxybin/ts-direct— typescript-language-server proxybin/cs-direct— csharp-ls proxy; fixes rootUri-at-init binding via per-workspace spawnbin/lsp-stdio-proxy.js— shared generic coordinator for standalone stdio LSPshooks/enforce-lsp-over-grep.py— Claude Code hook redirecting source-code grep to direct wrappershooks/enforce-lsp-workspace-root.py— C# workspace root enforcement; bypasses when cs-direct is installedscripts/install.sh,scripts/uninstall.sh,scripts/verify.shdocs/convention.md,docs/architecture.md,docs/troubleshooting.md, and per-language pages for all 5 supported languagesfixtures/— minimal sample projects for CI + local verification- GitHub Actions CI on macOS + Ubuntu