Thanks for helping out. This is a standalone LSP server for mruby whose source of truth is the live, compiled mruby VM — not RBS, not CRuby conventions, not static file indexing. Keep that premise in mind; it drives most design decisions.
lib/mruby_lsp/— the server (Ruby): LSP transport, the VM-reflection index, and every feature (completion, hover, definition, …). The buffer overlay (buffer_harvester.rb) parses open/unsaved files with Prism so edits count before a rebuild.ext/mruby_reflect/+vendor/value_bridge/— the C bridge that reflects a builtlibmrubyinto the host Ruby process. It is a dumb FFI conduit: all logic lives in Ruby; the VM decides, C never dispatches.editors/vscode/— the VS Code / VSCodium extension (language client + themrubydebug adapter overmrdb).test/overlay/— fast, prism-only unit tests (no VM needed).test/conformance/— replays ruby-lsp's own expectation vectors against our server;test/parity/— drives the real ruby-lsp as an oracle.
# run the server (host Ruby) from the checkout, via the CLI dispatcher:
ruby -Ilib -r mruby_lsp/cli -e 'MrubyLsp::CLI.run(ARGV.shift, ARGV)' -- server /path/to/project
cc -O2 -o /tmp/mruby-lsp ext/mruby_lsp_launcher/launcher.c # build the sandbox launcher
cd editors/vscode && npm install && npm run compile # build the extensionFull editor install and per-project setup are in the README
(rake vscode:install / rake install + mruby-lsp-setup).
Everything transient builds under build/ — nothing lands in the source
tree:
build/gems/— the two gems we author (mruby-lsp,value_bridge), built once byrake gem:buildat the current version.build/stage/— the.vsixassembly area (staged extension source + the fetched external-gem closure).build/mruby-lsp-<v>.vsix— the packaged extension.
rake clobber removes build/ (and mruby/build/) entirely — the single
cleanup command.
Versioning is deliberate. lib/mruby_lsp/version.rb is the single,
hand-set SemVer source of truth. No build, install, or package task bumps it; a
same-version rake install reinstalls cleanly (gem install --force) so you can
iterate without bumping. To cut a release, run an explicit bump (each writes
version.rb + the extension package.json and pins value_bridge in lockstep):
rake bump:patch # 0.1.120 -> 0.1.121
rake bump:minor # 0.1.120 -> 0.2.0
rake bump:major # 0.1.120 -> 1.0.0External runtime deps (prism, rbs, language_server-protocol, …) are not kept
in the repo: rake vscode:package fetches the full source-gem closure into the
.vsix stage at package time (needs network), so the shipped .vsix is
self-contained but the repo stays source-only.
Packaging the extension (rake vscode:package / vscode:install) stages the
sources with rsync when present (it ships by default on most Linux/macOS but is
NOT in the FreeBSD base system — pkg install rsync there); without rsync it
falls back to an equivalent in-Rakefile copy that preserves mtimes.
Setup first — the suite does not run on a bare Ruby. The overlay tests need
the runtime gems mruby-lsp itself uses (current prism, rbs, …), which a
distro/default Ruby is usually missing or ships too old (a default prism
predates node classes we depend on and fails with uninitialized constant).
Install them the same way a user would, and put the gem bindir on PATH:
rake install # builds + installs the gems (fetches deps)
# rake install is a --user-install: binstubs land in Gem.user_dir/bin
# (Gem.bindir on setups where user and system dirs coincide) — put both on PATH:
export PATH="$(ruby -e 'print File.join(Gem.user_dir, "bin")'):$(ruby -e 'print Gem.bindir'):$PATH"test/overlay/mruby_semantics_test.rb additionally needs mruby head built
somewhere (git clone https://github.com/mruby/mruby && cd mruby && rake);
point the test at the binary with MRUBY=/path/to/mruby/build/host/bin/mruby
(default: /tmp/mruby/build/host/bin/mruby).
cd test/overlay && for t in *_test.rb; do ruby "$t"; done # unit (prism-only)
cd editors/vscode && npm test # extension, in a real VS Code host
cd editors/vscode && npm run test:debug-adapter # DAP session logic (plain node)npm test uses VS Code's official extension-test runner (@vscode/test-cli,
config in .vscode-test.mjs): it downloads a VS Code build into .vscode-test/
on first run and drives the compiled extension in a live extension host against
the fixture workspaces under test/fixtures/ — nothing about the vscode API
is mocked. On a machine without a display, run it under xvfb:
xvfb-run -a npm test.
The conformance + parity suites (test/conformance/README.md,
test/parity/README.md) need a built reflection VM and, for parity, ruby-lsp
built from source; both READMEs document the procedure.
One workflow (.github/workflows/ci.yml), and it runs the WHOLE suite against
the real server on every pull request and every push to main (plus manual
dispatch) — there is no reduced/fast variant:
- server job: installs the gems from the checkout (
rake install), builds mruby HEAD plus the reflection VM viamruby-lsp-setup, then runs all oftest/overlay(including the live-VM pinmruby_semantics_test.rb), the conformance replays over real LSP stdio (each script exits non-zero on any FAIL — see the scorecard intest/conformance/README.md), and thetest/consistencysuite driven by a real LSP client (headless Neovim + clangd). - editor job: the extension suite in a real VS Code extension host
(
xvfb-run -a npm test) and the debug-adapter protocol tests.
An upstream mruby behavior change, a conformance regression, or an endpoint-consistency drift turns the run red.
test/consistency/ asserts that the SAME question answered through different
endpoints (completion / hover / signatureHelp / definition / references /
documentHighlight / rename) yields the SAME logical fact — formatting differs by
design, the facts must not. It is driven by a REAL LSP client (Neovim's built-in
vim.lsp) against the real server, so it needs a built reflection VM + clangd;
see test/consistency/README.md.
- Verify by running. Drive the server over real LSP stdio; static inspection doesn't count as done.
- No
mrb_load_string/ eval in any mruby context. Reading the already-loaded image is fine; executing buffer code is the security line. - No regex on structured input (Ruby, JSON, HTML, compiler command lines, structured machine output). Ruby → Prism; C locations → addr2line/nm; reflection → consumed structurally. Keep regex out of hot paths.
- C is a dumb bridge, and it contains nothing fuzzable — all parsing and logic is host Ruby.
- Degrade, don't crash. Optional subsystems (e.g. clangd) are BYO and may be absent or die mid-session — switch the feature off and keep the server alive.
For the working conventions (commit author/trailers, patch delivery) see
AGENTS.md; for hard-won pitfalls before touching native/build code
read docs/GOTCHAS.md; the ruby-lsp conformance contract is in
docs/CONFORMANCE.md. Project state, open work, and the
roadmap live in the GitHub issue tracker.