Skip to content

Releases: mario/drey

drey 0.1.9

Choose a tag to compare

@github-actions github-actions released this 06 Sep 03:39

Fixed

  • The daemon's socket, lock and log lived under dirs::cache_dir()
    (~/Library/Caches/drey on macOS) when neither DREY_RUNTIME_DIR nor
    XDG_RUNTIME_DIR was set. That directory is fair game for the OS and for
    cache-cleaning tools to prune at any time, including while the daemon is
    still running: once daemon.sock is gone, the daemon keeps listening on a
    path nothing can reach, and the next client, finding no socket there,
    starts a second daemon rather than reusing the first. Repeated over days
    this produced a pile of idle, unreachable drey daemon processes that
    never exited on their own. The fallback now prefers the system temp
    directory ($TMPDIR on macOS) over the cache directory, and the daemon
    also checks every 60 seconds that its socket path still points at its own
    listener, exiting if it doesn't, so an orphaned daemon cleans itself up
    instead of accumulating indefinitely.

Full Changelog: v0.1.8...v0.1.9

drey 0.1.8

Choose a tag to compare

@github-actions github-actions released this 06 Aug 00:14

Fixed

  • serve now gives up on a client that never writes anything, instead of
    parking on stdin forever. The daemon side already bounded the reverse
    handshake (0.1.5's INIT_TIMEOUT: a backend that doesn't answer
    initialize within 60 seconds is given up on), but nothing bounded a
    client that opens the shim's stdin and never sends its own initialize.
    That left an orphaned drey serve process per abandoned attempt, sitting
    outside drey status (which lists backends, not shims) and outside process
    accounting until something went looking. A client that writes nothing
    within 60 seconds now gets the same treatment: the shim exits instead of
    hanging.

Full Changelog: v0.1.7...v0.1.8

drey 0.1.7

Choose a tag to compare

@github-actions github-actions released this 31 Jul 14:14

Fixed

  • The guard against a server config pointing back at drey itself now checks
    the exact executable path instead of banning its whole parent directory.
    Homebrew installs drey at /usr/local/bin/drey, the same directory as
    asdf and every other server binary it manages; drey install correctly
    points asdf-managed servers at /usr/local/bin/asdf
    (command = "/usr/local/bin/asdf", args = ["exec", "rust-analyzer"]), but
    the guard read that as drey pointing at itself and refused to spawn it. Six
    of the twelve built-in servers route through asdf, so this hung silently for
    anyone with drey installed next to a version manager in the same directory,
    the same "attached to nothing, no error" symptom as 0.1.5's shim loop.

Full Changelog: v0.1.6...v0.1.7

drey 0.1.6

Choose a tag to compare

@github-actions github-actions released this 31 Jul 06:09

Fixed

  • Wrapper scripts written by drey install now keep the path they were
    invoked through instead of resolving it. Homebrew runs drey via
    /usr/local/bin/drey, a symlink it repoints at every upgrade to point at
    the newly installed version's Cellar path; canonicalizing that symlink baked
    the old, versioned Cellar path into every wrapper. The next upgrade deleted
    that path, so every wrapper drey had installed, not just rust-analyzer's,
    called a binary that no longer existed, and the daemon that serve is
    supposed to autostart never got to run.

Full Changelog: v0.1.5...v0.1.6

drey 0.1.5

Choose a tag to compare

@github-actions github-actions released this 28 Jul 18:29

Fixed

  • The shim directory of the version manager drey launches through is now
    removed from the server's environment, alongside drey's own. Launching
    rust-analyzer as asdf exec rust-analyzer reaches a rustup proxy inside the
    asdf installation; when that toolchain has no rust-analyzer component, the
    proxy falls back to the next rust-analyzer on PATH, which is asdf's shim,
    which execs asdf exec rust-analyzer again. The two proxies handed the name
    back and forth and never reached a language server. What you saw was a client
    attached to nothing: no diagnostics, no completion, no error, and a drey serve process that sat there for days. With the shims gone from the child's
    PATH, rustup says which component is missing instead.

  • A server that does not answer initialize within 60 seconds is now given up
    on, with its stderr attached. Every real server replies in well under a
    second and indexes afterwards, so a slow handshake is a wedged one. There was
    no bound before, so a single stuck spawn kept every client on that workspace
    waiting for as long as the daemon lived.

Full Changelog: v0.1.4...v0.1.5

drey 0.1.4

Choose a tag to compare

@github-actions github-actions released this 25 Jul 12:42

Fixed

  • Servers managed by asdf are launched through asdf exec again, instead of
    the path asdf which reports. That path is often not a program: for
    asdf-managed Rust it is a rustup proxy that needs RUSTUP_HOME pointing
    inside the asdf installation, and only asdf exec sets it. Unwrapped, the
    proxy searched ~/.rustup, found no default toolchain there, and exited
    during initialize, so every Rust client silently had no language server at
    all. gopls, ruff, pyright and ruby-lsp resolve through the same shims and
    were affected wherever their runtime needed the environment too.

    Unwrapping was meant to stop every workspace from pinning to whatever
    version asdf picked at daemon start. Recording asdf exec fixes that
    properly rather than sidestepping it: the daemon already spawns servers with
    the workspace root as the working directory, which is what asdf dispatches
    on, so each workspace now gets the version its .tool-versions asks for.

  • A server that dies during initialize now reports what it printed on stderr.
    That output went to the log at debug level, so the usual causes (a missing
    toolchain, an unknown flag, a broken shim) produced exited during initialize and nothing else. Diagnosing the bug above meant turning up
    DREY_LOG and reproducing it; the message now carries the reason.

Full Changelog: v0.1.3...v0.1.4

drey 0.1.3

Choose a tag to compare

@github-actions github-actions released this 21 Jul 15:10

Fixed

  • Content-Length is matched without regard to case, as header names are. Two
    spellings were accepted and CONTENT-LENGTH: was not, so a client using it
    got "LSP message without Content-Length header", a confusing way to report
    unexpected capitalisation. A field whose name merely ends in content-length
    is still ignored rather than parsed as the length.
  • The test suites leaked daemons. A daemon detaches and only drey stop ends
    it, and the CLI suite never stopped what it started, so 8 survived every run.
    Dozens accumulated here over a day, the oldest at 18 hours, and the suite
    began timing out under the process pressure. The real-server smoke test
    leaked a daemon and a rust-analyzer too. Both now clean up.

Full Changelog: v0.1.2...v0.1.3

drey 0.1.2

Choose a tag to compare

@github-actions github-actions released this 21 Jul 08:17

Fixed

  • Root widening could climb to $HOME. A stray Cargo.toml, package.json or
    go.mod in your home directory merged every project underneath it into one
    backend indexing the entire home directory. The walk now stops below home in
    both directions: it will not adopt home as a root, and a client that opened
    $HOME itself will not climb into /Users or /.
  • A duplicate Content-Length header let the last value win, which
    desynchronised the stream: the sender and drey then disagreed about where the
    message ended, and every message after it was read at the wrong offset with no
    error to point at the cause. It is now refused, naming both values.

Changed

  • Hitting the 64-entry cap on server-initiated requests held for an absent
    client now logs at warn rather than passing silently. A server whose requests
    are being refused is wedged or misconfigured, and the log was the only place
    that would have shown it.

Note for anyone upgrading

If you had a marker file directly in $HOME, projects that previously collapsed
into a single backend now get one each. That is more memory and more indexing on
first use, and it is the correct behaviour: those projects were never one
workspace. Nothing else changes, and no configuration is affected.

Full Changelog: v0.1.1...v0.1.2

drey 0.1.1

Choose a tag to compare

@github-actions github-actions released this 21 Jul 05:01

Added

  • drey install and drey uninstall, so a binary from Homebrew or
    cargo install can set up the PATH interposition without cloning the
    repository and rebuilding. The wrappers exec whichever drey binary you ran
    install from, rather than assuming ~/.drey/bin/drey. Both take
    --dry-run; install takes --force to replace files in ~/.drey/bin that
    drey did not write.
  • Apache License 2.0, replacing the proprietary licence.
  • CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, and a NOTICE file.
  • docs/architecture.md explaining the three ideas the design rests on, and
    docs/releasing.md covering crates.io and the Homebrew tap.
  • CI on Linux and macOS: fmt, clippy with warnings as errors, unit tests, the
    Python end-to-end suite, an MSRV 1.85 check, and cargo deny for licences and
    advisories.
  • Release workflow publishing to crates.io from a tag push, via trusted
    publishing rather than a stored token.
  • Property tests (proptest) over frame round-trips, UTF-16 position maths, and
    incremental edit application against a reference implementation.

Changed

  • scripts/install.sh and scripts/uninstall.sh are thin wrappers now: they
    build or remove the binary and delegate the rest to the new subcommands. The
    discovery, config, wrapper and PATH logic is no longer duplicated in shell.
  • drey uninstall leaves ~/.config/drey/config.toml in place and says so,
    where the old script deleted it. It also removes only files carrying drey's
    marker comment from ~/.drey/bin.

Full Changelog: v0.1.0...v0.1.1

drey 0.1.0

Choose a tag to compare

@mario mario released this 20 Jul 20:21

One language server per workspace, shared by every editor and agent.

Every LSP client starts its own language server. Two Claude Code sessions on one
Rust workspace means two rust-analyzer processes, each holding a complete salsa
database, and 7.4 GB of memory to answer questions about one copy of one
codebase. drey makes them share.

The same applies to gopls, tsserver, clangd, pyright and jdtls. Rust is the
example because it is the one with the measurement.

Install

cargo install drey
# or
brew install mario/drey/drey

Then ./scripts/install.sh puts wrapper scripts on your PATH, so existing
editor and agent configs pick up drey without being changed.

What it does

The shim looks exactly like a language server on stdio. The daemon owns one real
server per workspace and autostarts on first use.

Clients attach by root containment rather than equality, so opening a single
crate inside a Cargo workspace costs nothing: it joins the server that already
indexed it. Git worktrees and branches deliberately stay separate, because
merging them would produce wrong answers, which is worse than using memory.

Two clients with different unsaved edits to the same file still share one
process. Each document keeps a base text and a loaded text, and the daemon swaps
the loaded one rather than forking a second server. Verified against a real
rust-analyzer: with one client holding pub size: u32 and another holding
u64, the same process reports size = 4, align = 0x4 to the first and
size = 8, align = 0x8 to the second. That is a type layout recomputed per
client, not an echo of the text they sent.

Server-initiated requests (workspace/configuration,
client/registerCapability, workspace/applyEdit) are answered by one attached
client on behalf of all. A proxy that drops those silently runs rust-analyzer on
defaults, which is the opposite of what you want when you adopted a proxy to
control memory.

Builtins

rust-analyzer, gopls, typescript, pyright, ruff, clangd, zls, lua, elixir,
jdtls. Anything in ~/.config/drey/config.toml overrides them.

Testing

150 unit tests, 11 CLI integration tests, an end-to-end suite driving the real
binary over stdio against a mock server, and a smoke test against a real
rust-analyzer. CI runs fmt, clippy with warnings as errors, tests on Linux and
macOS, an MSRV 1.85 check, and cargo-deny for licences and advisories.

Status

First release. The daemon has been running my own sessions for a while, but it
has not been through anyone else's setup yet, so expect rough edges and please
report them. Sharing policy is the part where a bug is silent, so bug reports
about clients sharing when they should not are the most valuable kind.