Ragex Usage Guide
View SourceVersion: 0.2.0
Date: December 30, 2025
Complete guide to using Ragex, the Hybrid RAG system for intelligent codebase analysis and manipulation.
Table of Contents
- Quick Start
- MCP Integration
- How Do I Benefit From Ragex When Editing Elixir Project in VIM
- How Do I Benefit From Ragex When Editing Elixir Project in LunarVim (NeoVim)
- Core Workflows
- Code Analysis
- Semantic Search
- Code Editing
- Refactoring
- Configuration
- Best Practices
- Troubleshooting
Quick Start
Installation
# Clone the repository
git clone https://github.com/your-org/ragex.git
cd ragex
# Install dependencies
mix deps.get
# Compile the project
mix compile
First Run
# Start the Ragex MCP server
mix run --no-halt
# The server will:
# - Initialize the knowledge graph
# - Load the embedding model (first run downloads ~90MB)
# - Start listening on stdin/stdout for MCP commands
Quick Test
# In another terminal, use the MCP protocol:
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | mix run --no-haltMCP Integration
Ragex implements the Model Context Protocol (MCP) for seamless integration with AI assistants.
Connecting to Claude Desktop
Add to your Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"ragex": {
"command": "mix",
"args": ["run", "--no-halt"],
"cwd": "/path/to/ragex"
}
}
}Available Tools
Ragex exposes 17 MCP tools across 4 categories:
Analysis Tools (5)
analyze_file- Parse and index source filesanalyze_directory- Batch analyze entire projectsquery_graph- Search for code entitieslist_nodes- Browse indexed entitieswatch_directory- Auto-reindex on file changes
Search Tools (4)
semantic_search- Natural language code searchhybrid_search- Combined symbolic + semantic searchget_embeddings_stats- ML model statisticsfind_paths- Call chain discovery
Graph Tools (2)
graph_stats- Comprehensive analysislist_watched- View watched directories
Editing Tools (6)
edit_file- Safe single-file editingvalidate_edit- Preview validationrollback_edit- Undo editsedit_history- Query backupsedit_files- Multi-file transactionsrefactor_code- Semantic refactoring
How Do I Benefit From Ragex When Editing Elixir Project in VIM
Ragex can significantly enhance your VIM workflow for Elixir development through several integration approaches.
1. Language Server Integration
Via ElixirLS + MCP Bridge:
Ragex complements ElixirLS by providing semantic code understanding that goes beyond traditional LSP capabilities.
" In your .vimrc or init.vim
" Configure ElixirLS first, then add Ragex integration
" Example function to query Ragex from VIM
function! RagexSemanticSearch(query)
let l:cmd = 'echo ' . shellescape(json_encode({
\ 'jsonrpc': '2.0',
\ 'method': 'tools/call',
\ 'params': {'name': 'semantic_search', 'arguments': {'query': a:query, 'limit': 10}},
\ 'id': 1
\ })) . ' | mix run --no-halt'
let l:result = system(l:cmd)
return json_decode(l:result)
endfunction2. Code Navigation
Find definitions and usages semantically:
" Add to your VIM config
nnoremap <leader>rf :call RagexFindFunction()<CR>
nnoremap <leader>rc :call RagexFindCallers()<CR>
nnoremap <leader>rs :call RagexSearch(expand('<cword>'))<CR>
function! RagexFindFunction()
" Get function under cursor and find its definition
let l:word = expand('<cword>')
" Query Ragex graph for function definition
" Open result in quickfix window
endfunctionBenefits:
- Semantic search beyond grep/ctags
- Find functions by behavior description
- Discover call chains between functions
- Navigate complex codebases faster
3. Refactoring Support
Safe rename operations:
" Rename function across entire project
command! -nargs=1 RagexRename call RagexRenameFunction(<f-args>)
function! RagexRenameFunction(new_name)
let l:module = RagexGetCurrentModule()
let l:func = expand('<cword>')
let l:arity = RagexGetFunctionArity()
" Call Ragex refactor_code tool
let l:request = {
\ 'operation': 'rename_function',
\ 'module': l:module,
\ 'old_name': l:func,
\ 'new_name': a:new_name,
\ 'arity': l:arity,
\ 'scope': 'project',
\ 'validate': v:true,
\ 'format': v:true
\ }
" Execute and reload affected buffers
endfunctionWhat you get:
- Project-wide refactoring from VIM
- Automatic validation and formatting
- Rollback capability if issues occur
- No partial updates (atomic transactions)
4. Intelligent Code Completion
Enhance completion with semantic context:
" Use with VIM's omnifunc or completion plugins
setlocal omnifunc=RagexComplete
function! RagexComplete(findstart, base)
if a:findstart
" Find start of completion
return col('.') - 1
else
" Query Ragex for semantically relevant functions
let l:context = getline('.')
let l:results = RagexSemanticSearch('functions related to ' . a:base)
return l:results
endif
endfunction5. Quick Documentation Lookup
" Show function details from knowledge graph
nnoremap K :call RagexShowDoc()<CR>
function! RagexShowDoc()
let l:word = expand('<cword>')
" Query Ragex for function details
" Display in preview window with:
" - Function signature
" - File location
" - Callers and callees
" - Semantically similar functions
endfunction6. Automated Workflows
Watch and auto-analyze on save:
" Auto-analyze file on save
autocmd BufWritePost *.ex,*.exs call RagexAnalyzeFile(expand('%:p'))
function! RagexAnalyzeFile(filepath)
" Trigger Ragex analysis
" Update knowledge graph
" Regenerate embeddings for changed code
endfunction7. Project Setup
Initial Ragex setup for VIM workflow:
# 1. Start Ragex in background (in project root)
cd /path/to/elixir/project
mix run --no-halt > /tmp/ragex.log 2>&1 &
echo $! > /tmp/ragex.pid
# 2. Initial analysis
echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"analyze_directory","arguments":{"path":".","recursive":true,"extensions":[".ex",".exs"]}},"id":1}' | nc localhost 5555
# 3. Enable watching
echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"watch_directory","arguments":{"path":".","extensions":[".ex",".exs"]}},"id":2}' | nc localhost 5555
Add to your project's .vimrc.local:
" Project-specific Ragex configuration
let g:ragex_project_root = getcwd()
let g:ragex_enabled = 1
" Load Ragex VIM integration
source ~/.vim/ragex.vim8. Integration with Existing Plugins
Works alongside:
- ElixirLS: Traditional LSP features (diagnostics, basic completion)
- vim-test: Run tests, Ragex finds related test files
- fzf.vim: Combine fuzzy finding with semantic search
- ALE/Syntastic: Linting + Ragex validation
- vim-projectionist: Navigation + Ragex call graph
Example fzf integration:
" Semantic code search with fzf
command! -nargs=1 RagexFzf call fzf#run(fzf#wrap({
\ 'source': RagexSearchSource(<q-args>),
\ 'sink': function('RagexOpenResult'),
\ 'options': '--preview "bat --color=always {1}"'
\ }))
function! RagexSearchSource(query)
" Query Ragex semantic search
" Return list of file:line:content
endfunction9. Performance Tips
Optimize for VIM workflow:
- Cache embeddings - Enable persistent cache for instant results
- Use async - Make Ragex calls asynchronous to avoid blocking VIM
- Limit results - Keep limit=10 for interactive use
- Filter by file scope - Search only relevant directories
" Async Ragex call example (with vim-dispatch or ALE)
function! RagexSearchAsync(query)
call job_start(['mix', 'run', '--no-halt'], {
\ 'in_io': 'pipe',
\ 'out_cb': function('RagexHandleResults')
\ })
endfunction10. Sample VIM Plugin Structure
Create ~/.vim/ragex.vim:
" Ragex VIM Integration
" Author: Your Name
" Version: 0.1.0
if exists('g:loaded_ragex')
finish
endif
let g:loaded_ragex = 1
" Configuration
let g:ragex_project_root = get(g:, 'ragex_project_root', getcwd())
let g:ragex_cmd = get(g:, 'ragex_cmd', 'mix run --no-halt')
" Core functions
function! ragex#search(query) abort
" Implementation
endfunction
function! ragex#analyze_file(filepath) abort
" Implementation
endfunction
function! ragex#find_callers() abort
" Implementation
endfunction
" Commands
command! -nargs=1 RagexSearch call ragex#search(<f-args>)
command! RagexAnalyze call ragex#analyze_file(expand('%:p'))
command! RagexCallers call ragex#find_callers()
" Mappings
nnoremap <silent> <Plug>(ragex-search) :call ragex#search(expand('<cword>'))<CR>
nnoremap <silent> <Plug>(ragex-callers) :call ragex#find_callers()<CR>Benefits Summary
Without Ragex:
- Limited to grep, ctags, ElixirLS
- No semantic understanding
- Manual refactoring across files
- Difficult to discover related code
With Ragex:
- Natural language code search from VIM
- Semantic navigation beyond syntax
- Safe project-wide refactoring
- Discover functions by behavior
- Understand call chains instantly
- Auto-updated knowledge graph
- Rollback capability for edits
How Do I Benefit From Ragex When Editing Elixir Project in LunarVim (NeoVim)
LunarVim provides a modern NeoVim configuration with LSP, Telescope, and Lua integration, making Ragex integration even more powerful than traditional VIM.
1. Socket-Based Integration
Important: Ragex uses a Unix socket server for better performance and stability.
Start the server:
cd /path/to/ragex
./start_mcp.sh # Starts server on /tmp/ragex_mcp.sock
Create ~/.config/lvim/lua/user/ragex.lua:
local M = {}
-- Configuration
M.config = {
project_root = vim.fn.getcwd(),
ragex_path = vim.fn.expand("~/Proyectos/Ammotion/ragex"),
enabled = true,
debug = false, -- Set to true to see request/response logs
}
-- Log debug messages
local function debug_log(msg)
if M.config.debug then
vim.notify("[Ragex] " .. msg, vim.log.levels.INFO)
end
end
-- Execute Ragex MCP command via Unix socket
function M.execute(method, params, callback)
if not M.config.enabled then
debug_log("Ragex is disabled")
return
end
local request = vim.fn.json_encode({
jsonrpc = "2.0",
method = "tools/call",
params = {
name = method,
arguments = params,
},
id = vim.fn.rand(),
})
debug_log("Request: " .. method)
-- Use socat to communicate with Unix socket
local cmd = string.format(
"printf '%%s\\n' %s | socat - UNIX-CONNECT:/tmp/ragex_mcp.sock",
vim.fn.shellescape(request)
)
if callback then
-- Async execution
vim.fn.jobstart(cmd, {
stdout_buffered = true,
on_stdout = function(_, data)
if data and #data > 0 then
local result_str = table.concat(data, "\n"):gsub("^%s+", ""):gsub("%s+$", "")
if result_str ~= "" then
debug_log("Response received")
local ok, result = pcall(vim.fn.json_decode, result_str)
if ok and result then
callback(result)
else
vim.notify("Ragex: Invalid response format", vim.log.levels.WARN)
end
end
end
end,
on_exit = function(_, exit_code)
if exit_code ~= 0 then
vim.notify("Ragex: Command failed (check if server is running)", vim.log.levels.WARN)
end
end,
})
else
-- Synchronous execution
local handle = io.popen(cmd)
local result_str = ""
if handle then
result_str = handle:read("*a")
handle:close()
end
if result_str and result_str ~= "" then
result_str = result_str:gsub("^%s+", ""):gsub("%s+$", "")
local ok, result = pcall(vim.fn.json_decode, result_str)
if ok and result then
return result
end
end
return nil
end
end
-- Semantic search
function M.semantic_search(query, opts)
opts = opts or {}
local params = {
query = query,
limit = opts.limit or 10,
threshold = opts.threshold or 0.3, -- Lower threshold for better recall
node_type = opts.node_type,
}
return M.execute("semantic_search", params)
end
-- Hybrid search (best results)
function M.hybrid_search(query, opts)
opts = opts or {}
local params = {
query = query,
limit = opts.limit or 10,
threshold = 0.3, -- Lower threshold for better recall
strategy = opts.strategy or "fusion",
}
return M.execute("hybrid_search", params)
end
-- Analyze current file
function M.analyze_current_file()
local filepath = vim.fn.expand("%:p")
return M.execute("analyze_file", { path = filepath })
end
-- Find callers of function under cursor
function M.find_callers()
local module = M.get_current_module()
local func = vim.fn.expand("<cword>")
local arity = M.get_function_arity()
return M.execute("query_graph", {
query = string.format("calls %s.%s/%d", module, func, arity),
})
end
-- Analyze directory
function M.analyze_directory(path, opts)
opts = opts or {}
path = path or vim.fn.getcwd()
local params = {
path = path,
recursive = opts.recursive ~= false,
parallel = opts.parallel ~= false,
extensions = opts.extensions or { ".ex", ".exs" },
}
vim.notify("Analyzing directory: " .. path, vim.log.levels.INFO)
M.execute("analyze_directory", params, function(result)
if result and result.result then
-- Unwrap MCP response
local actual_result = result.result
if actual_result.content and actual_result.content[1] and actual_result.content[1].text then
local ok, parsed = pcall(vim.fn.json_decode, actual_result.content[1].text)
if ok then
actual_result = parsed
end
end
local count = actual_result.analyzed or actual_result.success or 0
local total = actual_result.total or 0
vim.notify(string.format("Analyzed %d/%d files", count, total), vim.log.levels.INFO)
else
vim.notify("Failed to analyze directory", vim.log.levels.ERROR)
end
end)
end
-- Refactor: rename function
function M.rename_function(new_name, scope)
local module = M.get_current_module()
if not module then
vim.notify("Could not determine current module", vim.log.levels.WARN)
return
end
local old_name = vim.fn.expand("<cword>")
local arity = M.get_function_arity()
local params = {
operation = "rename_function",
params = { -- Nested params object for MCP
module = module,
old_name = old_name,
new_name = new_name,
arity = arity,
},
scope = scope or "project",
validate = true,
format = true,
}
vim.notify(
string.format("Renaming %s.%s/%d -> %s", module, old_name, arity, new_name),
vim.log.levels.INFO
)
M.execute("refactor_code", params, function(result)
if not result or not result.result then
vim.notify("Refactoring failed: no response", vim.log.levels.ERROR)
return
end
-- Unwrap MCP response
local data = result.result
if data.content and data.content[1] and data.content[1].text then
local ok, parsed = pcall(vim.fn.json_decode, data.content[1].text)
if ok then
data = parsed
end
end
if data.success then
vim.cmd("checktime") -- Reload buffers
vim.notify("Function renamed successfully", vim.log.levels.INFO)
else
local err = data.error or result.error or "unknown error"
vim.notify("Refactoring failed: " .. vim.inspect(err), vim.log.levels.ERROR)
end
end)
end
-- Helper: Get current module name
function M.get_current_module()
local lines = vim.api.nvim_buf_get_lines(0, 0, 50, false)
for _, line in ipairs(lines) do
local module = line:match("defmodule%s+([%w%.]+)")
if module then
return module
end
end
return nil
end
-- Helper: Get function arity (simplified)
function M.get_function_arity()
local line = vim.fn.getline(".")
local args = line:match("def%s+%w+%((.-)%)")
if not args or args == "" then
return 0
end
local _, count = args:gsub(",", "")
return count + 1
end
return M2. Telescope Integration
Semantic code search with Telescope:
Add to ~/.config/lvim/config.lua:
local ragex = require("user.ragex")
-- Telescope picker for Ragex semantic search
local function ragex_search()
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
vim.ui.input({ prompt = "Ragex Search: " }, function(query)
if not query then return end
local results = ragex.hybrid_search(query)
if not results.result or not results.result.results then
vim.notify("No results found", vim.log.levels.WARN)
return
end
local entries = {}
for _, item in ipairs(results.result.results) do
table.insert(entries, {
value = item,
display = string.format(
"%s:%d [%.2f] %s",
item.file or "unknown",
item.line or 0,
item.score or 0,
item.text or ""
),
ordinal = item.text or "",
filename = item.file,
lnum = item.line,
})
end
pickers.new({}, {
prompt_title = "Ragex Search Results",
finder = finders.new_table({
results = entries,
entry_maker = function(entry)
return entry
end,
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_bufnr, map)
actions.select_default:replace(function()
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
if selection.filename then
vim.cmd(string.format("edit +%d %s", selection.lnum, selection.filename))
end
end)
return true
end,
}):find()
end)
end
-- Register command
vim.api.nvim_create_user_command("RagexSearch", ragex_search, {})3. Which-Key Integration
Add keybindings to LunarVim's which-key:
lvim.builtin.which_key.mappings["r"] = {
name = "Ragex",
s = { "<cmd>RagexSearch<cr>", "Semantic Search" },
a = { function() require("user.ragex").analyze_current_file() end, "Analyze File" },
c = { function() require("user.ragex").find_callers() end, "Find Callers" },
r = {
function()
vim.ui.input({ prompt = "New name: " }, function(name)
if name then
require("user.ragex").rename_function(name)
end
end)
end,
"Rename Function",
},
f = { "<cmd>Telescope ragex_functions<cr>", "Find Functions" },
m = { "<cmd>Telescope ragex_modules<cr>", "Find Modules" },
}4. Custom Telescope Pickers
Create specialized Telescope pickers:
local ragex = require("user.ragex")
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
-- Find functions by semantic similarity
local function ragex_functions()
vim.ui.input({ prompt = "Find functions: " }, function(query)
if not query then return end
local results = ragex.semantic_search(query, { node_type = "function" })
if not results.result or not results.result.results then
return
end
local entries = vim.tbl_map(function(item)
return {
value = item,
display = string.format(
"%s.%s/%d [%.2f]",
item.node_id[1] or "?",
item.node_id[2] or "?",
item.node_id[3] or 0,
item.score or 0
),
ordinal = table.concat(item.node_id or {}, "."),
filename = item.file,
lnum = item.line,
}
end, results.result.results)
pickers.new({}, {
prompt_title = "Ragex Functions",
finder = finders.new_table({ results = entries, entry_maker = function(e) return e end }),
sorter = conf.generic_sorter({}),
previewer = conf.qflist_previewer({}),
}):find()
end)
end
vim.api.nvim_create_user_command("RagexFunctions", ragex_functions, {})5. LSP Integration
Enhance ElixirLS with Ragex:
-- Override LSP rename with Ragex refactoring
local function setup_lsp_overrides()
lvim.lsp.on_attach_callback = function(client, bufnr)
if client.name == "elixirls" then
-- Use Ragex for rename instead of LSP
vim.keymap.set("n", "<leader>lr", function()
vim.ui.input({ prompt = "New name: " }, function(new_name)
if new_name then
require("user.ragex").rename_function(new_name)
end
end)
end, { buffer = bufnr, desc = "Rename (Ragex)" })
-- Add semantic search to LSP menu
vim.keymap.set("n", "<leader>ls", function()
require("user.ragex").semantic_search(vim.fn.expand("<cword>"))
end, { buffer = bufnr, desc = "Semantic Search" })
end
end
end
setup_lsp_overrides()6. Auto-Analysis on Save
Automatically analyze files when saving:
local ragex_group = vim.api.nvim_create_augroup("RagexAnalysis", { clear = true })
vim.api.nvim_create_autocmd({ "BufWritePost" }, {
group = ragex_group,
pattern = { "*.ex", "*.exs" },
callback = function()
-- Async analysis to avoid blocking
vim.fn.jobstart(
string.format(
"cd %s && echo '%s' | mix run --no-halt",
require("user.ragex").config.ragex_path,
vim.fn.json_encode({
jsonrpc = "2.0",
method = "tools/call",
params = {
name = "analyze_file",
arguments = { path = vim.fn.expand("%:p") },
},
id = vim.fn.rand(),
})
),
{
on_exit = function(_, code)
if code == 0 then
vim.notify("File analyzed", vim.log.levels.DEBUG)
end
end,
}
)
end,
})7. Completion Integration
Add Ragex as a completion source:
local cmp = require("cmp")
local ragex_source = {}
function ragex_source:new()
return setmetatable({}, { __index = self })
end
function ragex_source:get_trigger_characters()
return { ".", ":" }
end
function ragex_source:complete(params, callback)
local input = string.sub(params.context.cursor_before_line, params.offset)
-- Query Ragex for semantic completions
local results = require("user.ragex").semantic_search(
"functions related to " .. input,
{ limit = 20, node_type = "function" }
)
if not results.result or not results.result.results then
callback({})
return
end
local items = vim.tbl_map(function(item)
return {
label = string.format("%s/%d", item.node_id[2], item.node_id[3]),
kind = cmp.lsp.CompletionItemKind.Function,
detail = item.text,
documentation = string.format(
"Module: %s\nFile: %s:%d\nScore: %.2f",
item.node_id[1],
item.file,
item.line,
item.score
),
}
end, results.result.results)
callback(items)
end
-- Register the source
cmp.register_source("ragex", ragex_source:new())
-- Add to sources
table.insert(lvim.builtin.cmp.sources, { name = "ragex", priority = 750 })8. Status Line Integration
Show Ragex status in LunarVim status line:
local function ragex_status()
if not require("user.ragex").config.enabled then
return ""
end
return " Ragex"
end
-- Add to lualine
lvim.builtin.lualine.sections.lualine_x = {
ragex_status,
"encoding",
"fileformat",
"filetype",
}9. Float Window for Results
Display search results in floating window:
local function show_in_float(title, lines)
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
vim.api.nvim_buf_set_option(buf, "modifiable", false)
local width = math.min(100, vim.o.columns - 4)
local height = math.min(30, vim.o.lines - 4)
local win = vim.api.nvim_open_win(buf, true, {
relative = "editor",
width = width,
height = height,
col = math.floor((vim.o.columns - width) / 2),
row = math.floor((vim.o.lines - height) / 2),
style = "minimal",
border = "rounded",
title = title,
title_pos = "center",
})
vim.api.nvim_buf_set_keymap(buf, "n", "q", "<cmd>close<cr>", { noremap = true })
vim.api.nvim_buf_set_keymap(buf, "n", "<Esc>", "<cmd>close<cr>", { noremap = true })
end
-- Show callers in float
function M.show_callers()
local results = require("user.ragex").find_callers()
if not results.result then
vim.notify("No callers found", vim.log.levels.WARN)
return
end
local lines = { "Callers:", "" }
for _, caller in ipairs(results.result) do
table.insert(lines, string.format(" %s:%d", caller.file, caller.line))
end
show_in_float("Ragex Callers", lines)
end10. Full Configuration Example
Complete ~/.config/lvim/config.lua setup:
-- Ragex integration
local ragex = require("user.ragex")
-- Configure Ragex
ragex.config.ragex_path = vim.fn.expand("~/Proyectos/Ammotion/ragex")
ragex.config.enabled = true
-- Keybindings
lvim.builtin.which_key.mappings["r"] = {
name = "Ragex",
s = { "<cmd>RagexSearch<cr>", "Semantic Search" },
f = { "<cmd>RagexFunctions<cr>", "Find Functions" },
a = { function() ragex.analyze_current_file() end, "Analyze File" },
c = { function() ragex.find_callers() end, "Find Callers" },
r = {
function()
vim.ui.input({ prompt = "New name: " }, function(name)
if name then ragex.rename_function(name) end
end)
end,
"Rename Function",
},
}
-- Auto-analysis
vim.api.nvim_create_autocmd({ "BufWritePost" }, {
pattern = { "*.ex", "*.exs" },
callback = function()
ragex.analyze_current_file()
end,
})
-- Status line (with safe initialization)
local function ragex_status()
if ragex.config.enabled then
return " Ragex"
end
return ""
end
if lvim.builtin.lualine and lvim.builtin.lualine.sections and lvim.builtin.lualine.sections.lualine_x then
table.insert(lvim.builtin.lualine.sections.lualine_x, 1, ragex_status)
else
lvim.builtin.lualine = lvim.builtin.lualine or {}
lvim.builtin.lualine.sections = lvim.builtin.lualine.sections or {}
lvim.builtin.lualine.sections.lualine_x = lvim.builtin.lualine.sections.lualine_x or {}
table.insert(lvim.builtin.lualine.sections.lualine_x, ragex_status)
end11. Project-Specific Configuration
Create .lvimrc or .exrc in your Elixir project:
-- .lvimrc (requires exrc option)
local ragex = require("user.ragex")
-- Project-specific Ragex path
ragex.config.ragex_path = vim.fn.getcwd() .. "/../ragex"
-- Auto-analyze entire project on startup
vim.defer_fn(function()
local result = ragex.execute("analyze_directory", {
path = vim.fn.getcwd(),
recursive = true,
extensions = { ".ex", ".exs" },
})
if result.result then
vim.notify(
string.format("Analyzed %d files", result.result.files_analyzed or 0),
vim.log.levels.INFO
)
end
end, 1000)
-- Enable watching
ragex.execute("watch_directory", {
path = vim.fn.getcwd(),
extensions = { ".ex", ".exs" },
})Benefits Over Standard VIM
LunarVim/NeoVim advantages:
- Lua API: Faster, more maintainable than VimScript
- Async/Jobs: Non-blocking Ragex calls
- Telescope: Beautiful search UI with previews
- LSP Integration: Seamless with ElixirLS
- Which-Key: Discoverable keybindings
- Modern UI: Floating windows, notifications
- Tree-sitter: Better syntax understanding
- Built-in completion: Easy to extend with Ragex
Performance:
- Async operations don't block editor
- Telescope caching improves repeated searches
- Lua is faster than VimScript
- Job control for background analysis
Core Workflows
Workflow 1: Analyze a New Project
# Step 1: Start Ragex
mix run --no-halt
# Step 2: Analyze the project (via MCP)
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "analyze_directory",
"arguments": {
"path": "/path/to/project",
"recursive": true,
"extensions": [".ex", ".exs", ".erl", ".py", ".js", ".ts"]
}
},
"id": 1
}
Result:
- All source files parsed
- Functions, modules, calls indexed
- Knowledge graph populated
- Embeddings generated
- Ready for search and analysis
Workflow 2: Find Related Code
# Natural language search
{
"name": "semantic_search",
"arguments": {
"query": "function that validates user authentication",
"limit": 10,
"threshold": 0.7
}
}
# Hybrid search (best results)
{
"name": "hybrid_search",
"arguments": {
"query": "authentication validation",
"limit": 10,
"strategy": "fusion"
}
}
Result:
- Ranked list of relevant functions
- Similarity scores
- File locations
- Function signatures
Workflow 3: Safe Code Refactoring
# Step 1: Rename a function across entire codebase
{
"name": "refactor_code",
"arguments": {
"operation": "rename_function",
"module": "MyApp.Auth",
"old_name": "check_user",
"new_name": "validate_user",
"arity": 2,
"scope": "project",
"validate": true,
"format": true
}
}
What happens:
- Ragex finds function definition via graph
- Discovers all call sites
- Creates atomic transaction
- Updates all files
- Validates syntax in all files
- Formats code automatically
- Creates backups
- Commits changes atomically
If anything fails:
- Automatic rollback
- All files restored
- No partial changes
Workflow 4: Multi-File Editing
{
"name": "edit_files",
"arguments": {
"edits": [
{
"file": "lib/app/user.ex",
"changes": [
{
"type": "replace",
"start_line": 10,
"end_line": 15,
"new_content": "def new_implementation do\n :ok\nend"
}
],
"validate": true,
"format": true
},
{
"file": "lib/app/admin.ex",
"changes": [
{
"type": "insert",
"line": 20,
"content": "# New admin feature\n"
}
]
}
],
"create_backup": true
}
}
Safety guarantees:
- All-or-nothing atomicity
- Pre-validation of all files
- Automatic rollback on any error
- Backups created before changes
- Format integration
Code Analysis
Analyzing Individual Files
Elixir:
# Via Elixir API
{:ok, analysis} = Ragex.Analyzers.Elixir.analyze(source_code, "lib/my_module.ex")
# Returns:
%{
modules: [%{name: :MyModule, file: "lib/my_module.ex", line: 1}],
functions: [
%{module: :MyModule, name: :func, arity: 2, line: 10, file: "lib/my_module.ex"}
],
calls: [
%{from_module: :MyModule, from_function: :func, from_arity: 2,
to_module: :OtherModule, to_function: :helper, to_arity: 1}
]
}Python:
# Python analysis
content = """
def calculate_sum(a, b):
return a + b
def main():
result = calculate_sum(1, 2)
"""
{:ok, analysis} = Ragex.Analyzers.Python.analyze(content, "script.py")Supported Languages:
- Elixir (.ex, .exs) - Full AST parsing
- Erlang (.erl, .hrl) - Full AST parsing
- Python (.py) - AST via subprocess
- JavaScript/TypeScript (.js, .jsx, .ts, .tsx, .mjs) - Regex-based
Batch Analysis
# Analyze entire directory
{
"name": "analyze_directory",
"arguments": {
"path": "/path/to/project",
"recursive": true,
"parallel": true,
"extensions": [".ex", ".exs"]
}
}
Performance:
- Parallel processing
- ~100 files/second (depends on file size)
- Progress reporting
- Error handling per file
Watching for Changes
# Enable auto-reindex
{
"name": "watch_directory",
"arguments": {
"path": "/path/to/project",
"extensions": [".ex", ".exs"]
}
}
Features:
- Automatic reanalysis on file changes
- Incremental updates (only changed files)
- Embedding regeneration for modified entities
- Real-time graph updates
Semantic Search
Natural Language Queries
# Find authentication-related code
{
"name": "semantic_search",
"arguments": {
"query": "user login and session management",
"limit": 10,
"threshold": 0.7,
"node_type": "function"
}
}
Response:
{
"results": [
{
"node_type": "function",
"node_id": ["MyApp.Auth", "authenticate_user", 2],
"score": 0.92,
"text": "Authenticates user credentials and creates session",
"file": "lib/myapp/auth.ex",
"line": 45
}
]
}Hybrid Search (Best Results)
{
"name": "hybrid_search",
"arguments": {
"query": "validate email format",
"limit": 10,
"strategy": "fusion" # or "semantic_first", "graph_first"
}
}
Strategies:
fusion- Combines semantic + graph (RRF algorithm) - Recommendedsemantic_first- Prioritizes ML similaritygraph_first- Prioritizes graph structure
Performance:
- < 100ms typical query time
- < 50ms for vector search
- Scales to 10,000+ entities
Finding Call Chains
# Discover how functions are connected
{
"name": "find_paths",
"arguments": {
"from": ["MyApp.Web", "handle_request", 2],
"to": ["MyApp.DB", "save_record", 1],
"max_depth": 5,
"max_paths": 10
}
}
Result:
- All paths from function A to function B
- Call chain depth
- Intermediate functions
- Useful for understanding data flow
Code Editing
Single File Editing
{
"name": "edit_file",
"arguments": {
"file": "lib/my_module.ex",
"changes": [
{
"type": "replace",
"start_line": 10,
"end_line": 12,
"new_content": "def new_function do\n :ok\nend"
}
],
"validate": true,
"format": true,
"create_backup": true
}
}
Change Types:
Replace:
{ "type": "replace", "start_line": 10, "end_line": 15, "new_content": "new code here" }Insert:
{ "type": "insert", "line": 20, "content": "new line\n" }Delete:
{ "type": "delete", "start_line": 10, "end_line": 12 }
Validation
# Preview validation without editing
{
"name": "validate_edit",
"arguments": {
"file": "lib/my_module.ex",
"changes": [/* ... */],
"language": "elixir" # optional, auto-detected
}
}
Validators:
- Elixir:
Code.string_to_quoted/2 - Erlang:
:erl_scan+:erl_parse - Python:
ast.parse() - JavaScript:
vm.Script(Node.js)
Rollback
# Undo recent changes
{
"name": "rollback_edit",
"arguments": {
"file": "lib/my_module.ex",
"version": 2 # optional, defaults to previous
}
}
# View backup history
{
"name": "edit_history",
"arguments": {
"file": "lib/my_module.ex"
}
}
Backup System:
- Location:
~/.ragex/backups/<project_hash>/ - Retention: 10 backups per file (configurable)
- Compression: Automatic for older backups
- Metadata: Timestamps, checksums
Formatting
Automatic formatting after edits:
- Elixir:
mix format - Erlang:
rebar3 fmt - Python:
blackorautopep8 - JavaScript:
prettieroreslint --fix
Configuration:
# In edit request
"format": true,
"formatter": "mix format" # optional, auto-detectedRefactoring
Rename Function
{
"name": "refactor_code",
"arguments": {
"operation": "rename_function",
"module": "MyApp.User",
"old_name": "get_user",
"new_name": "fetch_user",
"arity": 1,
"scope": "project", # or "module"
"validate": true,
"format": true
}
}
What gets updated:
- Function definition
- All direct calls
- Module-qualified calls (
MyApp.User.get_user(id)) - Function references (
&get_user/1) - Calls in other modules (if scope: "project")
Scope Options:
module- Only within the same fileproject- Across entire codebase
Rename Module
{
"name": "refactor_code",
"arguments": {
"operation": "rename_module",
"old_name": "MyApp.OldModule",
"new_name": "MyApp.NewModule",
"validate": true,
"format": true
}
}
What gets updated:
- Module definition
- All aliases
- All imports
- All qualified calls
- All references
Refactoring Workflow
- Discovery: Graph finds all affected files
- Planning: Builds transaction with all changes
- Validation: Pre-validates all files
- Backup: Creates backups
- Execution: Applies changes atomically
- Format: Runs formatters
- Verification: Post-validates all files
- Commit: Finalizes transaction
If any step fails:
- Automatic rollback
- All files restored
- Error reported
- No partial state
Current Limitations
- Elixir only for AST-based refactoring
- Erlang/Python/JavaScript support planned
- No cross-language refactoring yet
Configuration
Embedding Models
Default: all-MiniLM-L6-v2 (384 dimensions)
Available Models:
# config/config.exs
config :ragex,
embedding_model: :all_minilm_l6_v2 # Default, fast
# embedding_model: :all_mpnet_base_v2 # Higher quality, 768 dims
# embedding_model: :codebert_base # Code-specific, 768 dims
# embedding_model: :paraphrase_multilingual # Multilingual, 384 dimsOr via environment variable:
export RAGEX_EMBEDDING_MODEL=all_mpnet_base_v2
mix run --no-halt
Cache Configuration
Enable persistent cache:
# config/config.exs
config :ragex,
cache_embeddings: true, # Default: false
cache_dir: "~/.ragex/cache"Benefits:
- 10x faster cold start
- ~15MB storage per 1,000 entities
- Automatic invalidation on model change
Backup Configuration
config :ragex, :editor,
backup_dir: "~/.ragex/backups",
max_backups_per_file: 10,
compress_old_backups: truePerformance Tuning
config :ragex,
# Graph query limits
max_paths: 100,
max_path_depth: 10,
# Search limits
max_search_results: 100,
# Parallel processing
max_parallel_analyzers: System.schedulers_online() * 2Best Practices
1. Project Setup
# Initial setup for a new project
1. Start Ragex
2. Analyze entire codebase
3. Enable directory watching
4. Let embeddings cache build
2. Incremental Development
# As you develop:
1. Keep directory watching enabled
2. Ragex auto-updates on file changes
3. Embeddings regenerate only for changed code
4. No manual reindexing needed
3. Search Strategies
For exploratory search:
- Use
hybrid_searchwithfusionstrategy - Start with lower threshold (0.5)
- Increase limit to see more results
For precise lookup:
- Use
query_graphfor exact symbols - Use
semantic_searchwith high threshold (0.8+) - Filter by
node_type
4. Safe Refactoring
Before refactoring:
- Ensure codebase is analyzed
- Check graph stats
- Verify function/module exists
During refactoring:
- Always use
validate: true - Always use
format: true - Use
scope: "module"for testing - Use
scope: "project"for production
After refactoring:
- Verify files changed as expected
- Run your test suite
- Check edit history if needed
- Rollback if issues found
5. Performance
For large codebases (>10,000 files):
- Use
parallel: truein analyze_directory - Enable caching
- Consider incremental analysis only
- Use path depth limits
For embedded systems:
- Use smaller model (all-MiniLM-L6-v2)
- Reduce max_search_results
- Disable caching if storage limited
6. Error Handling
If analysis fails:
- Check file is valid source code
- Verify language is supported
- Check file permissions
- Look for syntax errors
If search returns no results:
- Lower threshold
- Try different query phrasing
- Check embeddings are generated
- Verify codebase is analyzed
If refactoring fails:
- Check function/module exists in graph
- Verify all files are accessible
- Check validation errors
- Use rollback if needed
Troubleshooting
Model Download Issues
Problem: Model fails to download
Solution:
# Manual download
cd ~/.cache/huggingface/
wget https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/model.safetensors
# Or use different model
export RAGEX_EMBEDDING_MODEL=all_mpnet_base_v2
Memory Issues
Problem: High memory usage
Solutions:
# Use smaller model
config :ragex, embedding_model: :all_minilm_l6_v2 # 384 dims vs 768
# Reduce batch size
config :ragex, embedding_batch_size: 16 # default: 32
# Limit search results
config :ragex, max_search_results: 50Slow Analysis
Problem: Directory analysis is slow
Solutions:
# Enable parallel processing
{"parallel": true}
# Analyze incrementally
{"recursive": false} # Analyze one directory at a time
# Filter extensions
{"extensions": [".ex"]} # Only Elixir files
Graph State Issues
Problem: Entities not found in graph
Solutions:
# Clear and rebuild
1. Stop Ragex
2. rm -rf ~/.ragex/cache
3. Start Ragex
4. Re-analyze project
Validation Errors
Problem: Edits fail validation
Solutions:
- Check syntax of new content
- Verify language is correct
- Test validation separately
- Check validator is installed (Python, Node.js)
Backup/Rollback Issues
Problem: Cannot rollback
Solutions:
# Check backup directory
ls ~/.ragex/backups/<project_hash>/
# Verify backup exists
{
"name": "edit_history",
"arguments": {"file": "lib/my_module.ex"}
}
# Manual restore if needed
cp ~/.ragex/backups/<project_hash>/my_module.ex.<timestamp> lib/my_module.ex
Advanced Usage
Custom MCP Clients
# Python MCP client example
import json
import subprocess
def call_ragex(method, params):
request = {
"jsonrpc": "2.0",
"method": f"tools/call",
"params": {
"name": method,
"arguments": params
},
"id": 1
}
proc = subprocess.Popen(
["mix", "run", "--no-halt"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
cwd="/path/to/ragex"
)
proc.stdin.write(json.dumps(request).encode())
proc.stdin.close()
response = json.loads(proc.stdout.readline())
return response
# Usage
result = call_ragex("semantic_search", {
"query": "authentication",
"limit": 5
})Programmatic API
# Direct Elixir API usage
alias Ragex.{Graph.Store, VectorStore, Editor}
# Analyze
{:ok, analysis} = Ragex.Analyzers.Elixir.analyze(code, file)
Ragex.store_analysis(analysis)
# Search
{:ok, embedding} = Ragex.Embeddings.Bumblebee.embed("find auth code")
results = VectorStore.search(embedding, limit: 10)
# Edit
transaction = Editor.Transaction.new(validate: true, format: true)
|> Editor.Transaction.add(file, changes)
{:ok, result} = Editor.Transaction.commit(transaction)Support
- Issues: https://github.com/your-org/ragex/issues
- Discussions: https://github.com/your-org/ragex/discussions
- Documentation: https://ragex.dev/docs
Last Updated: December 30, 2025
Version: 0.2.0
License: MIT