Rubydex
This project is a high-performance static analysis toolkit for the Ruby language. The goal is to be a solid foundation to power a variety of tools, such as type checkers, linters, language servers and more.
Usage
Both Ruby and Rust APIs are made available through a gem and a crate, respectively. Hereโs a simple example of using the Ruby API:
# Create a new graph representing the current workspace graph = Rubydex::Graph.new # Configuring graph LSP encoding graph.encoding = "utf16" # Index the entire workspace with all dependencies graph.index_workspace # Or index specific file paths graph.index_all(["path/to/file.rb"]) # Transform the initially collected information into its semantic understanding by running resolution graph.resolve # Get all diagnostics acquired during the analysis graph.diagnostics # Iterating over graph nodes graph.declarations graph.documents graph.constant_references graph.method_references # Analyzing require paths graph.resolve_require_path("rails/engine", load_paths) # => document pointed by `rails/engine` graph.require_paths(load_paths) # => array of all indexed require paths # Querying graph["Foo"] # Get declaration by fully qualified name graph.search("Foo#b") # Name search graph.resolve_constant("Bar", ["Foo", "Baz::Qux"]) # Resolve constant reference based on nesting # Declarations declaration = graph["Foo"] # All declarations include declaration.name declaration.unqualified_name declaration.definitions declaration.owner # Namespace declarations include declaration.member("bar()") declaration.member("@ivar") declaration.singleton_class declaration.ancestors declaration.descendants # Documents document = graph.documents.first document.uri document.definitions # => list of definitions discovered in this document # Definitions definition = declaration.definitions.first definition.location definition.comments definition.name definition.deprecated? definition.name_location # Locations location = definition.location location.path # Diagnostics diagnostic = graph.diagnostics.first diagnostic.rule diagnostic.message diagnostic.location
Ractor Safety
A resolved Rubydex::Graph can be shared across Ractors without copying:
graph = Rubydex::Graph.new graph.index_workspace graph.resolve Ractor.make_shareable(graph) # Worker Ractors can now read the graph in parallel ractor = Ractor.new(graph) { |g| g["Foo"]&.name } ractor.value
Thread safety comes from an RwLock on the Rust side, not from Rubyโs freeze. This is a deliberate, but potentially surprising, choice:
-
A frozen (or
make_shareableโd) graph can still be mutated โ methods likeindex_source,resolve,exclude_patterns, andencoding=work on a frozen graph. This supports interactive use cases (LSP/MCP) that need incremental edits while worker Ractors read concurrently. -
Because the same underlying allocation is shared, callers are responsible for ordering concurrent writes and reads, as with any shared mutable state across Ractors.
-
Graphs cannot be
dupโd orcloneโd; both raiseRuntimeErrorto avoid aliasing the Rust allocation (which would double-free on GC) or ballooning memory with a deep copy.
Querying with Cypher
Rubydex exposes the indexed graph through a read-only subset of the Cypher query language. Only read clauses (MATCH, WHERE, RETURN, โฆ) are supported; there is no way to mutate the graph.
From the command line:
# Run a query against the current workspace bundle exec rdx query "MATCH (c:Class)-[:DEFINES]->(m:Method) RETURN c.name, m.name" # Render results as JSON instead of a table bundle exec rdx query "MATCH (c:Class) RETURN c.name" --format json # Describe the queryable schema (node labels and relationship types) without indexing bundle exec rdx query --schema
From Ruby:
graph = Rubydex::Graph.new graph.index_workspace graph.resolve # Parse once, render against a graph as a table or JSON string query = Rubydex::Query.parse("MATCH (c:Class) RETURN c.name") puts query.render(graph, "table") puts query.render(graph, "json") # Describe the schema puts Rubydex::Query.schema("table")
MCP Server (Experimental)
Rubydex can run as an MCP (Model Context Protocol) server, enabling AI assistants like Claude to semantically query your Ruby codebase.
Setup
-
Add
Rubydexto the Ruby project you want to index:ruby gem "rubydex" -
Install the bundle:
bash bundle install -
Configure your MCP client to run
bundle exec rdx mcp.
Using Claude Code as an example: bash claude mcp add --scope project rubydex -- bundle exec rdx mcp
Using Codex as an example: bash codex mcp add rubydex -- bundle exec rdx mcp
Start your MCP client from that project directory. The MCP server indexes the project at startup and provides semantic code intelligence tools through the tools below.
Available MCP Tools
| Tool | Description |
|---|---|
search_declarations |
Fuzzy search for classes, modules, methods, constants |
get_declaration |
Full details by fully qualified name with docs, ancestors, members |
get_descendants |
What classes/modules inherit from or include this one |
find_constant_references |
All precise, resolved constant references across the codebase |
get_file_declarations |
List declarations defined in a specific file |
codebase_stats |
High-level statistics about the indexed codebase |