# Ragex Usage Guide **Version**: 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 1. [Quick Start](#quick-start) 2. [MCP Integration](#mcp-integration) 3. [How Do I Benefit From Ragex When Editing Elixir Project in VIM](#how-do-i-benefit-from-ragex-when-editing-elixir-project-in-vim) 4. [How Do I Benefit From Ragex When Editing Elixir Project in LunarVim (NeoVim)](#how-do-i-benefit-from-ragex-when-editing-elixir-project-in-lunarvim-neovim) 5. [Core Workflows](#core-workflows) 6. [Code Analysis](#code-analysis) 7. [Semantic Search](#semantic-search) 8. [Code Editing](#code-editing) 9. [Refactoring](#refactoring) 10. [Configuration](#configuration) 11. [Best Practices](#best-practices) 12. [Troubleshooting](#troubleshooting) --- ## Quick Start ### Installation ```bash # 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 ```bash # 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 ```elixir # In another terminal, use the MCP protocol: echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | mix run --no-halt ``` --- ## MCP 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): ```json { "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 files - `analyze_directory` - Batch analyze entire projects - `query_graph` - Search for code entities - `list_nodes` - Browse indexed entities - `watch_directory` - Auto-reindex on file changes #### Search Tools (4) - `semantic_search` - Natural language code search - `hybrid_search` - Combined symbolic + semantic search - `get_embeddings_stats` - ML model statistics - `find_paths` - Call chain discovery #### Graph Tools (2) - `graph_stats` - Comprehensive analysis - `list_watched` - View watched directories #### Editing Tools (6) - `edit_file` - Safe single-file editing - `validate_edit` - Preview validation - `rollback_edit` - Undo edits - `edit_history` - Query backups - `edit_files` - Multi-file transactions - `refactor_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. ```vim " 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) endfunction ``` ### 2. Code Navigation **Find definitions and usages semantically:** ```vim " Add to your VIM config nnoremap rf :call RagexFindFunction() nnoremap rc :call RagexFindCallers() nnoremap rs :call RagexSearch(expand('')) function! RagexFindFunction() " Get function under cursor and find its definition let l:word = expand('') " Query Ragex graph for function definition " Open result in quickfix window endfunction ``` **Benefits:** - 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:** ```vim " Rename function across entire project command! -nargs=1 RagexRename call RagexRenameFunction() function! RagexRenameFunction(new_name) let l:module = RagexGetCurrentModule() let l:func = expand('') 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 endfunction ``` **What 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:** ```vim " 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 endfunction ``` ### 5. Quick Documentation Lookup ```vim " Show function details from knowledge graph nnoremap K :call RagexShowDoc() function! RagexShowDoc() let l:word = expand('') " Query Ragex for function details " Display in preview window with: " - Function signature " - File location " - Callers and callees " - Semantically similar functions endfunction ``` ### 6. Automated Workflows **Watch and auto-analyze on save:** ```vim " 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 endfunction ``` ### 7. Project Setup **Initial Ragex setup for VIM workflow:** ```bash # 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:** ```vim " Project-specific Ragex configuration let g:ragex_project_root = getcwd() let g:ragex_enabled = 1 " Load Ragex VIM integration source ~/.vim/ragex.vim ``` ### 8. 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:** ```vim " Semantic code search with fzf command! -nargs=1 RagexFzf call fzf#run(fzf#wrap({ \ 'source': RagexSearchSource(), \ 'sink': function('RagexOpenResult'), \ 'options': '--preview "bat --color=always {1}"' \ })) function! RagexSearchSource(query) " Query Ragex semantic search " Return list of file:line:content endfunction ``` ### 9. Performance Tips **Optimize for VIM workflow:** 1. **Cache embeddings** - Enable persistent cache for instant results 2. **Use async** - Make Ragex calls asynchronous to avoid blocking VIM 3. **Limit results** - Keep limit=10 for interactive use 4. **Filter by file scope** - Search only relevant directories ```vim " 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') \ }) endfunction ``` ### 10. Sample VIM Plugin Structure Create `~/.vim/ragex.vim`: ```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() command! RagexAnalyze call ragex#analyze_file(expand('%:p')) command! RagexCallers call ragex#find_callers() " Mappings nnoremap (ragex-search) :call ragex#search(expand('')) nnoremap (ragex-callers) :call ragex#find_callers() ``` ### 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:** ```bash cd /path/to/ragex ./start_mcp.sh # Starts server on /tmp/ragex_mcp.sock ``` **Create `~/.config/lvim/lua/user/ragex.lua`:** ```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("") 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("") 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 M ``` ### 2. Telescope Integration **Semantic code search with Telescope:** Add to `~/.config/lvim/config.lua`: ```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:** ```lua lvim.builtin.which_key.mappings["r"] = { name = "Ragex", s = { "RagexSearch", "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 = { "Telescope ragex_functions", "Find Functions" }, m = { "Telescope ragex_modules", "Find Modules" }, } ``` ### 4. Custom Telescope Pickers **Create specialized Telescope pickers:** ```lua 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:** ```lua -- 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", "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", "ls", function() require("user.ragex").semantic_search(vim.fn.expand("")) end, { buffer = bufnr, desc = "Semantic Search" }) end end end setup_lsp_overrides() ``` ### 6. Auto-Analysis on Save **Automatically analyze files when saving:** ```lua 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:** ```lua 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:** ```lua 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:** ```lua 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", "close", { noremap = true }) vim.api.nvim_buf_set_keymap(buf, "n", "", "close", { 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) end ``` ### 10. Full Configuration Example **Complete `~/.config/lvim/config.lua` setup:** ```lua -- 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 = { "RagexSearch", "Semantic Search" }, f = { "RagexFunctions", "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) end ``` ### 11. Project-Specific Configuration **Create `.lvimrc` or `.exrc` in your Elixir project:** ```lua -- .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 ```bash # 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 ```bash # 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 ```bash # 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:** 1. Ragex finds function definition via graph 2. Discovers all call sites 3. Creates atomic transaction 4. Updates all files 5. Validates syntax in all files 6. Formats code automatically 7. Creates backups 8. Commits changes atomically **If anything fails:** - Automatic rollback - All files restored - No partial changes ### Workflow 4: Multi-File Editing ```bash { "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:** ```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 # 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 ```bash # 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 ```bash # 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 ```bash # Find authentication-related code { "name": "semantic_search", "arguments": { "query": "user login and session management", "limit": 10, "threshold": 0.7, "node_type": "function" } } ``` **Response:** ```json { "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) ```bash { "name": "hybrid_search", "arguments": { "query": "validate email format", "limit": 10, "strategy": "fusion" # or "semantic_first", "graph_first" } } ``` **Strategies:** - `fusion` - Combines semantic + graph (RRF algorithm) - **Recommended** - `semantic_first` - Prioritizes ML similarity - `graph_first` - Prioritizes graph structure **Performance:** - < 100ms typical query time - < 50ms for vector search - Scales to 10,000+ entities ### Finding Call Chains ```bash # 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 ```bash { "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:** 1. **Replace:** ```json { "type": "replace", "start_line": 10, "end_line": 15, "new_content": "new code here" } ``` 2. **Insert:** ```json { "type": "insert", "line": 20, "content": "new line\n" } ``` 3. **Delete:** ```json { "type": "delete", "start_line": 10, "end_line": 12 } ``` ### Validation ```bash # 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 ```bash # 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//` - 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**: `black` or `autopep8` - **JavaScript**: `prettier` or `eslint --fix` **Configuration:** ```elixir # In edit request "format": true, "formatter": "mix format" # optional, auto-detected ``` --- ## Refactoring ### Rename Function ```bash { "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 file - `project` - Across entire codebase ### Rename Module ```bash { "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 1. **Discovery**: Graph finds all affected files 2. **Planning**: Builds transaction with all changes 3. **Validation**: Pre-validates all files 4. **Backup**: Creates backups 5. **Execution**: Applies changes atomically 6. **Format**: Runs formatters 7. **Verification**: Post-validates all files 8. **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:** ```elixir # 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 dims ``` **Or via environment variable:** ```bash export RAGEX_EMBEDDING_MODEL=all_mpnet_base_v2 mix run --no-halt ``` ### Cache Configuration **Enable persistent cache:** ```elixir # 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 ```elixir config :ragex, :editor, backup_dir: "~/.ragex/backups", max_backups_per_file: 10, compress_old_backups: true ``` ### Performance Tuning ```elixir 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() * 2 ``` --- ## Best Practices ### 1. Project Setup ```bash # 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 ```bash # 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_search` with `fusion` strategy - Start with lower threshold (0.5) - Increase limit to see more results **For precise lookup:** - Use `query_graph` for exact symbols - Use `semantic_search` with high threshold (0.8+) - Filter by `node_type` ### 4. Safe Refactoring **Before refactoring:** 1. Ensure codebase is analyzed 2. Check graph stats 3. 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: true` in 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:** ```bash # 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:** ```elixir # 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: 50 ``` ### Slow Analysis **Problem**: Directory analysis is slow **Solutions:** ```bash # 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:** ```bash # 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:** 1. Check syntax of new content 2. Verify language is correct 3. Test validation separately 4. Check validator is installed (Python, Node.js) ### Backup/Rollback Issues **Problem**: Cannot rollback **Solutions:** ```bash # Check backup directory ls ~/.ragex/backups// # Verify backup exists { "name": "edit_history", "arguments": {"file": "lib/my_module.ex"} } # Manual restore if needed cp ~/.ragex/backups//my_module.ex. lib/my_module.ex ``` --- ## Advanced Usage ### Custom MCP Clients ```python # 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 ```elixir # 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