This file provides context for Claude Code when working with this codebase.
claudecode.nvim - A Neovim plugin that implements the same WebSocket-based MCP protocol as Anthropic's official IDE extensions. Built with pure Lua and zero dependencies.
make test- Run all tests using busted with coveragebusted tests/unit/specific_spec.lua- Run specific test filebusted --coverage -v- Run tests with coverage
make check- Check Lua syntax and run luacheckmake format- Format code with stylua (or nix fmt if available)luacheck lua/ tests/ --no-unused-args --no-max-line-length- Direct linting
make- RECOMMENDED: Run formatting, linting, and testing (complete validation)make all- Run check and format (default target)make test- Run all tests using busted with coveragemake check- Check Lua syntax and run luacheckmake format- Format code with stylua (or nix fmt if available)make clean- Remove generated test filesmake help- Show available commands
Best Practice: Always use make at the end of editing sessions for complete validation.
nix develop- Enter development shell with all dependenciesnix fmt- Format all files using nix formatter
The fixtures/ directory contains test Neovim configurations for verifying plugin integrations:
vv <config>- Start Neovim with a specific fixture configurationvve <config>- Start Neovim with a fixture config in edit modelist-configs- Show available fixture configurations- Source
fixtures/nvim-aliases.shto enable these commands
Available Fixtures:
netrw- Tests with Neovim's built-in file explorernvim-tree- Tests with nvim-tree.lua file exploreroil- Tests with oil.nvim file explorermini-files- Tests with mini.files file explorer
Usage: source fixtures/nvim-aliases.sh && vv oil starts Neovim with oil.nvim configuration
- WebSocket Server (
lua/claudecode/server/) - Pure Neovim implementation using vim.loop, RFC 6455 compliant - MCP Tool System (
lua/claudecode/tools/) - Implements tools that Claude can execute (openFile, getCurrentSelection, etc.) - Lock File System (
lua/claudecode/lockfile.lua) - Creates discovery files for Claude CLI at~/.claude/ide/ - Selection Tracking (
lua/claudecode/selection.lua) - Monitors text selections and sends updates to Claude - Diff Integration (
lua/claudecode/diff.lua) - Native Neovim diff support for Claude's file comparisons - Terminal Integration (
lua/claudecode/terminal.lua) - Manages Claude CLI terminal sessions with support for internal Neovim terminals and external terminal applications
- TCP Server:
server/tcp.luahandles port binding and connections - Handshake:
server/handshake.luaprocesses HTTP upgrade requests with authentication - Frame Processing:
server/frame.luaimplements RFC 6455 WebSocket frames - Client Management:
server/client.luamanages individual connections - Utils:
server/utils.luaprovides base64, SHA-1, XOR operations in pure Lua
The WebSocket server implements secure authentication using:
- UUID v4 Tokens: Generated per session with enhanced entropy
- Header-based Auth: Uses
x-claude-code-ide-authorizationheader - Lock File Discovery: Tokens stored in
~/.claude/ide/[port].lockfor Claude CLI - MCP Compliance: Follows official Claude Code IDE authentication protocol
Complete VS Code Extension Compatibility: All tools now implement identical behavior and output formats as the official VS Code extension.
MCP-Exposed Tools (with JSON schemas):
openFile- Opens files with optional line/text selection (startLine/endLine), preview mode, text pattern matching, and makeFrontmost flaggetCurrentSelection- Gets current text selection from active editorgetLatestSelection- Gets most recent text selection (even from inactive editors)getOpenEditors- Lists currently open files with VS Code-compatibletabsstructureopenDiff- Opens native Neovim diff viewscheckDocumentDirty- Checks if document has unsaved changessaveDocument- Saves document with detailed success/failure reportinggetWorkspaceFolders- Gets workspace folder informationcloseAllDiffTabs- Closes all diff-related tabs and windowsgetDiagnostics- Gets language diagnostics (errors, warnings) from the editor
Internal Tools (not exposed via MCP):
close_tab- Internal-only tool for tab management (hardcoded in Claude Code)
Format Compliance: All tools return MCP-compliant format: {content: [{type: "text", text: "JSON-stringified-data"}]}
Internal Terminals (within Neovim):
- Snacks.nvim:
terminal/snacks.lua- Advanced terminal with floating windows - Native:
terminal/native.lua- Built-in Neovim terminal as fallback
External Terminals (separate applications):
- External Provider:
terminal/external.lua- Launches Claude in external terminal apps
Configuration Example:
opts = {
terminal = {
provider = "external", -- "auto", "snacks", "native", or "external"
external_terminal_cmd = "alacritty -e %s" -- Required for external provider
}
}lua/claudecode/init.lua- Main entry point and setuplua/claudecode/config.lua- Configuration managementplugin/claudecode.lua- Plugin loader with version checkstests/- Comprehensive test suite with unit, component, and integration tests
- ✅ WebSocket Server: RFC 6455 compliant with MCP message format
- ✅ Tool Registration: JSON Schema-based tool definitions
- ✅ Authentication: UUID v4 token-based secure handshake
- ✅ Message Format: JSON-RPC 2.0 with MCP content structure
- ✅ Error Handling: Comprehensive JSON-RPC error responses
claudecode.nvim implements 100% feature parity with Anthropic's official VS Code extension:
- Identical Tool Set: All 10 VS Code tools implemented
- Compatible Formats: Output structures match VS Code extension exactly
- Behavioral Consistency: Same parameter handling and response patterns
- Error Compatibility: Matching error codes and messages
Run make test to verify MCP compliance:
- Tool Format Validation: All tools return proper MCP structure
- Schema Compliance: JSON schemas validated against VS Code specs
- Integration Testing: End-to-end MCP message flow verification
Tests are organized in three layers:
- Unit tests (
tests/unit/) - Test individual functions in isolation - Component tests (
tests/component/) - Test subsystems with controlled environment - Integration tests (
tests/integration/) - End-to-end functionality with mock Claude client
Test files follow the pattern *_spec.lua or *_test.lua and use the busted framework.
JSON Handling: Custom JSON encoder/decoder with support for:
- Nested objects and arrays
- Special Lua keywords as object keys (
["end"]) - MCP message format validation
- VS Code extension output compatibility
Test Pattern: Run specific test files during development:
# Run specific tool tests with proper LUA_PATH
export LUA_PATH="./lua/?.lua;./lua/?/init.lua;./?.lua;./?/init.lua;$LUA_PATH"
busted tests/unit/tools/specific_tool_spec.lua --verbose
# Or use make for full validation
make test # Recommended for complete validationCoverage Metrics:
- 320+ tests covering all MCP tools and core functionality
- Unit Tests: Individual tool behavior and error cases
- Integration Tests: End-to-end MCP protocol flow
- Format Tests: MCP compliance and VS Code compatibility
- Isolation: Each test should be independent and not rely on external state
- Mocking: Use comprehensive mocking for vim APIs and external dependencies
- Coverage: Aim for both positive and negative test cases, edge cases included
- Performance: Tests should run quickly to encourage frequent execution
- Clarity: Test names should clearly describe what behavior is being verified
The plugin implements authentication using UUID v4 tokens that are generated for each server session and stored in lock files. This ensures secure connections between Claude CLI and the Neovim WebSocket server.
Lock File Authentication Tests (tests/lockfile_test.lua):
- Auth token generation and uniqueness validation
- Lock file creation with authentication tokens
- Reading auth tokens from existing lock files
- Error handling for missing or invalid tokens
WebSocket Handshake Authentication Tests (tests/unit/server/handshake_spec.lua):
- Valid authentication token acceptance
- Invalid/missing token rejection
- Edge cases (empty tokens, malformed headers, length limits)
- Case-insensitive header handling
Server Integration Tests (tests/unit/server_spec.lua):
- Server startup with authentication tokens
- Auth token state management during server lifecycle
- Token validation throughout server operations
End-to-End Authentication Tests (tests/integration/mcp_tools_spec.lua):
- Complete authentication flow from server start to tool execution
- Authentication state persistence across operations
- Concurrent operations with authentication enabled
Test Script Authentication Support:
# Test scripts automatically detect and use authentication tokens
cd scripts/
./claude_interactive.sh # Automatically reads auth token from lock fileAuthentication Flow Testing:
- Start the plugin:
:ClaudeCodeStart - Check lock file contains
authToken:cat ~/.claude/ide/*.lock | jq .authToken - Test WebSocket connection with auth: Use test scripts in
scripts/directory - Verify authentication in logs: Set
log_level = "debug"in config
Testing Authentication Failures:
# Test invalid auth token (should fail)
websocat ws://localhost:PORT --header "x-claude-code-ide-authorization: invalid-token"
# Test missing auth header (should fail)
websocat ws://localhost:PORT
# Test valid auth token (should succeed)
websocat ws://localhost:PORT --header "x-claude-code-ide-authorization: $(cat ~/.claude/ide/*.lock | jq -r .authToken)"Enable detailed authentication logging by setting:
require("claudecode").setup({
log_level = "debug", -- Shows auth token generation, validation, and failures
diff_opts = {
keep_terminal_focus = true, -- If true, moves focus back to terminal after diff opens
},
})The diff_opts configuration allows you to customize diff behavior:
layout("vertical"|"horizontal", default:"vertical") - Whether the diff panes open in a vertical or horizontal split.keep_terminal_focus(boolean, default:false) - When enabled, keeps focus in the Claude Code terminal when a diff opens instead of moving focus to the diff buffer. This allows you to continue using terminal keybindings like<CR>for accepting/rejecting diffs without accidentally triggering other mappings.open_in_new_tab(boolean, default:false) - Open diffs in a new tab instead of the current tab.hide_terminal_in_new_tab(boolean, default:false) - When opening diffs in a new tab, do not show the Claude terminal split in that new tab. The terminal remains in the original tab, giving maximum screen estate for reviewing the diff.on_new_file_reject("keep_empty"|"close_window", default:"keep_empty") - Behavior when rejecting a diff for a new file (where the old file did not exist).- Legacy aliases (still supported):
vertical_split(maps tolayout) andopen_in_current_tab(inverse ofopen_in_new_tab).
Example use case: If you frequently use <CR> or arrow keys in the Claude Code terminal to accept/reject diffs, enable this option to prevent focus from moving to the diff buffer where <CR> might trigger unintended actions.
require("claudecode").setup({
diff_opts = {
layout = "vertical", -- "vertical" or "horizontal"
keep_terminal_focus = true, -- If true, moves focus back to terminal after diff opens
open_in_new_tab = true, -- Open diff in a separate tab
hide_terminal_in_new_tab = true, -- In the new tab, do not show Claude terminal
on_new_file_reject = "keep_empty", -- "keep_empty" or "close_window"
-- Legacy aliases (still supported):
-- vertical_split = true,
-- open_in_current_tab = true,
},
})Log levels for authentication events:
- DEBUG: Server startup authentication state, client connections, handshake processing, auth token details
- WARN: Authentication failures during handshake
- ERROR: Auth token generation failures, handshake response errors
- Connection Events: Use DEBUG level for routine connection establishment/teardown
- Authentication Flow: Use DEBUG for successful auth, WARN for failures
- User-Facing Events: Use INFO sparingly for events users need to know about
- System Errors: Use ERROR for failures that require user attention
- Plugin requires Neovim >= 0.8.0
- Uses only Neovim built-ins for WebSocket implementation (vim.loop, vim.json, vim.schedule)
- Zero external dependencies for core functionality
- WebSocket server only accepts local connections (127.0.0.1) for security
- Authentication tokens are UUID v4 with enhanced entropy
- Lock files created at
~/.claude/ide/[port].lockfor Claude CLI discovery - All authentication events are logged for security auditing
- Selection tracking is debounced to reduce overhead
- WebSocket frame processing optimized for JSON-RPC payload sizes
- Connection pooling and cleanup to prevent resource leaks
- Terminal integration supports both snacks.nvim and native Neovim terminal
- Compatible with popular file explorers (nvim-tree, oil.nvim, neo-tree, mini.files)
- Visual selection tracking across different selection modes
When updating the version number for a new release, you must update ALL of these files:
-
lua/claudecode/init.lua- Main version table:M.version = { major = 0, minor = 2, -- Update this patch = 0, -- Update this prerelease = nil, -- Remove for stable releases }
-
scripts/claude_interactive.sh- Multiple client version references:- Line ~52:
"version": "0.2.0"(handshake) - Line ~223:
"version": "0.2.0"(initialize) - Line ~309:
"version": "0.2.0"(reconnect)
- Line ~52:
-
scripts/lib_claude.sh- ClaudeCodeNvim version:- Line ~120:
"version": "0.2.0"(init message)
- Line ~120:
-
CHANGELOG.md- Add new release section with:- Release date
- Features with PR references
- Bug fixes with PR references
- Development improvements
# Get merged PRs since last version
gh pr list --state merged --base main --json number,title,mergedAt,url --jq 'sort_by(.mergedAt) | reverse'
# Get commit history
git log --oneline v0.1.0..HEAD
# Always run before committing
make
# Verify no old version references remain
rg "0\.1\.0" . # Should only show CHANGELOG.md historical entriesALWAYS run make before committing any changes. This runs code quality checks and formatting that must pass for CI to succeed. Never skip this step - many PRs fail CI because contributors don't run the build commands before committing.
- Start Development: Use existing tests and documentation to understand the system
- Make Changes: Follow existing patterns and conventions in the codebase
- Validate Work: Run
maketo ensure formatting, linting, and tests pass - Document Changes: Update relevant documentation (this file, PROTOCOL.md, etc.)
- Commit: Only commit after successful
makeexecution
Adding New Integrations (file explorers, terminals, etc.):
- Implement Integration: Add support in relevant modules (e.g.,
lua/claudecode/tools/) - Create Fixture Configuration: REQUIRED - Add a complete Neovim config in
fixtures/[integration-name]/ - Test Integration: Use fixture to verify functionality with
vv [integration-name] - Update Documentation: Add integration to fixtures list and relevant tool documentation
- Run Full Test Suite: Ensure
makepasses with new integration
Fixture Requirements:
- Complete Neovim configuration with plugin dependencies
- Include
dev-claudecode.luawith development keybindings - Test all relevant claudecode.nvim features with the integration
- Document any integration-specific behaviors or limitations
Adding New Tools:
- Study Existing Patterns: Review
lua/claudecode/tools/for consistent structure - Implement Handler: Return MCP format:
{content: [{type: "text", text: JSON}]} - Add JSON Schema: Define parameters and expose via MCP (if needed)
- Create Tests: Both unit tests and integration tests required
- Update Documentation: Add to this file's MCP tools list
Tool Testing Pattern:
-- All tools should return MCP-compliant format
local result = tool_handler(params)
expect(result).to_be_table()
expect(result.content).to_be_table()
expect(result.content[1].type).to_be("text")
local parsed = json_decode(result.content[1].text)
-- Validate parsed structure matches VS Code extensionError Handling Standard:
-- Use consistent JSON-RPC error format
error({
code = -32602, -- Invalid params
message = "Description of the issue",
data = "Additional context"
})- Test Coverage: Maintain comprehensive test coverage (currently 320+ tests, 100% success rate)
- Zero Warnings: All code must pass luacheck with 0 warnings/errors
- MCP Compliance: All tools must return proper MCP format with JSON-stringified content
- VS Code Compatibility: New tools must match VS Code extension behavior exactly
- Consistent Formatting: Use
nix fmtorstyluafor consistent code style - Documentation: Update CLAUDE.md for architectural changes, PROTOCOL.md for protocol changes
make check- Syntax and linting (0 warnings required)make test- All tests passing (320/320 success rate required)make format- Consistent code formatting- MCP Validation - Tools return proper format structure
- Integration Test - End-to-end protocol flow verification
Test Failures with LUA_PATH:
# Tests can't find modules - use proper LUA_PATH
export LUA_PATH="./lua/?.lua;./lua/?/init.lua;./?.lua;./?/init.lua;$LUA_PATH"
busted tests/unit/specific_test.luaJSON Format Issues:
- Ensure all tools return:
{content: [{type: "text", text: "JSON-string"}]} - Use
vim.json.encode()for proper JSON stringification - Test JSON parsing with custom test decoder in
tests/busted_setup.lua
MCP Tool Registration:
- Tools with
schema = nilare internal-only - Tools with schema are exposed via MCP
- Check
lua/claudecode/tools/init.luafor registration patterns
Authentication Testing:
# Verify auth token generation
cat ~/.claude/ide/*.lock | jq .authToken
# Test WebSocket connection
websocat ws://localhost:PORT --header "x-claude-code-ide-authorization: $(cat ~/.claude/ide/*.lock | jq -r .authToken)"