Skip to content

Latest commit

 

History

History
347 lines (274 loc) · 15.5 KB

File metadata and controls

347 lines (274 loc) · 15.5 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

diffusion.nvim is a unified Neovim plugin that integrates modern AI coding assistants (Claude Code, OpenCode, and Codex) with first-class diff review and an optional immersive in-editor chat overlay. Claude/Pi use MCP over WebSocket, OpenCode uses HTTP/SSE, and Codex uses codex app-server over stdio JSON-RPC.

Focus: The primary integration target is Claude Code via MCP over WebSocket.

Critical Performance Reference: This codebase has undergone extensive performance analysis documented in DIFFUSION_PERFORMANCE_OPTIMIZATION.md. Key findings show potential for 40-60% performance improvement through fixing double navigation issues, optimizing event scheduling, and implementing async operations. Always reference this document when working on performance-critical code.

Common Commands

Plugin Development Commands

  • Run tests: cd diffusion-v1 && lua tests/run_tests.lua - Execute complete test suite
  • Single test: lua tests/unit/specific_test.lua - Run individual test files
  • Component tests: Run tests in tests/component/, tests/integration/, tests/unit/
  • Format Lua: Use stylua or your editor's Lua formatter
  • Debug logging: Check ~/.local/share/nvim/diffusion.log (user's config sets logging.file = vim.fn.stdpath("data") .. "/diffusion.log")
  • Performance debugging: Enable logging.level = "DEBUG" for timing data

Plugin User Commands

  • Setup and start: require('diffusion').setup(config) then :DiffusionStart
  • Send message: :CC <message> or :Diffusion <message>
  • Send selection: :DiffusionSendSelection or <leader>ds
  • Check status: :DiffusionStatus
  • Test diff system: :DiffusionTestDiff

Debug Commands

  • System status: :lua print(vim.inspect(require('diffusion').status()))
  • Claude handler status: :lua print(vim.inspect(require('diffusion.protocol.claude'):get_status()))
  • Active diffs: :lua print(vim.inspect(require('diffusion.diff'):get_active_diffs()))
  • Force reconnection: :lua require('diffusion.protocol.claude'):trigger_reconnection()

Architecture Overview

High-Level System Design

The plugin follows a provider-agnostic event-driven architecture with clear separation of concerns:

┌─────────────────────────────────────────────────────┐
│                Plugin Entry Layer                   │  plugin/diffusion.lua
│                 (Commands & Keymaps)                │
├─────────────────────────────────────────────────────┤
│              Protocol Abstraction                   │  protocol/init.lua  
│            (Claude, OpenCode, Codex)                │  protocol/claude.lua
├─────────────────────────────────────────────────────┤
│                  Core Diff System                   │  diff/manager.lua
│         (Provider-Agnostic Logic & State)           │  diff/display/, diff/navigation.lua
├─────────────────────────────────────────────────────┤
│          WebSocket MCP Server & Tools               │  server/, tools/
├─────────────────────────────────────────────────────┤
│              Event System & Utilities               │  utils/events.lua, utils/logger.lua
└─────────────────────────────────────────────────────┘

Key Architectural Principles

  1. Provider Agnosticism: Core functionality in diff/manager.lua is provider-independent
  2. Event-Driven Communication: Components communicate via pub/sub system in utils/events.lua
  3. Thin Protocol Adapters: Protocol handlers (like protocol/claude.lua) only translate between provider formats and core API
  4. Reactive Architecture: UI responds to events rather than direct control flow
  5. Resource Management: Automatic cleanup and lifecycle management throughout

Critical Diff Flow Understanding

Essential: Claude Code handles ALL file editing. Diffusion's role is purely reactive UI display:

The 4-Step Diff Process

  1. 🚀 Claude calls toolshowDiff or openDiff via WebSocket MCP
  2. 📋 Diffusion displays → Creates diff UI (diff/manager.lua:show_diff())
  3. 🛑 Claude calls closeclose_tab tool (regardless of accept/reject)
  4. 🔚 Diffusion dismisses & navigates → Closes UI and navigates to file (diff/manager.lua:dismiss_diff())

Optimistic Buffer Navigation (CRITICAL — DO NOT REMOVE)

When a diff is accepted, the file buffer is populated from in-memory new_content instantly in the diff:user_response handler — BEFORE dismiss_diff runs. This is the optimistic navigation call in manager.lua (external ops path):

-- In diff:user_response handler, external ops, accepted branch:
self._navigation:navigate_to_file(
  diff_entry.file_path,
  diff_entry.navigation_line,
  diff_entry.changed_lines,
  nil,
  diff_entry.new_content  -- from memory, not disk
)

This call must NEVER be removed. Without it, the buffer only updates after dismiss_diff completes its cleanup, causing a visible delay where old content flashes before new content appears. This has been accidentally removed multiple times during debugging and each time caused regressions.

See docs/optimistic-buffer-system.md for full documentation.

Key constraints:

  • The optimistic call handles buffer content only — NOT positioning or highlighting
  • dismiss_diffdiff:dismissed → navigation handles positioning and highlighting
  • Both passes use the same new_content — the second pass is idempotent
  • The IDE must NOT write to disk — Claude Code monitors file hashes and will break

Performance-Critical Flow Details

From DIFFUSION_PERFORMANCE_OPTIMIZATION.md analysis:

  • Step 1-2: Navigation data collection (15-25ms) happens AFTER buffer creation to avoid blocking UI
  • Step 3: Buffer deletion is instant via vim.defer_fn(..., 0) to escape fast event context
  • Step 4: Navigation uses event-driven approach to prevent double visual jumps

Core Component Interactions

Diff Manager (diff/manager.lua) - Central Coordinator

  • Primary role: Provider-agnostic diff lifecycle management
  • Key methods: show_diff(), dismiss_diff(), navigation data collection
  • Events emitted: diff:created, diff:dismissed with navigation data
  • Performance note: Contains expensive git diff parsing - see optimization guide

Navigation Module (diff/navigation.lua) - Post-Dismissal Navigation

  • Primary role: Handle file navigation after diff dismissal
  • Events listened: diff:dismissed for Claude protocol
  • Critical issue: Double navigation visual jump (documented in optimization guide)
  • Fix location: Lines 62-72 need atomic cursor positioning

Claude Protocol Handler (protocol/claude.lua) - MCP Interface

  • Primary role: Translate MCP tool calls to core diff operations
  • Critical tools: showDiff, close_tab, dismissDiff
  • Performance note: Heavy use of vim.defer_fn(..., 0) for fast event context
  • Architecture violation: Should delegate ALL logic to core, no provider-specific handling

Event System (utils/events.lua) - Component Communication

  • Primary role: Pub/sub communication between components
  • emit() is synchronous (no vim.schedule overhead); use emit_async() only from libuv callbacks
  • Listener cleanup is MANDATORY — see rule below

MANDATORY: Event Listener Lifecycle Rule

Every events:on() call MUST have a corresponding cleanup path. Orphaned listeners accumulate across start/stop cycles and cause memory leaks + duplicate handler execution.

Required Pattern

-- Store the unsubscribe function returned by events:on()
self._event_unsubs = {}

function MyModule:_setup_events()
  -- Clear any orphaned listeners from prior cycles BEFORE registering
  self:_cleanup_events()

  self._event_unsubs[#self._event_unsubs + 1] = self._events:on("my:event", function(data)
    -- handler logic
  end)
end

function MyModule:_cleanup_events()
  for _, unsub in ipairs(self._event_unsubs) do
    unsub()
  end
  self._event_unsubs = {}
end

-- MUST call _cleanup_events() in stop()/destroy()/cleanup()
function MyModule:stop()
  self:_cleanup_events()
end

Forbidden Pattern

-- BAD: listener registered with no cleanup path — WILL leak
events:on("some:event", function(data) ... end)

Why this matters: Each DiffusionStop + DiffusionStart cycle re-runs setup. Without cleanup, listeners double on every cycle. After 5 restarts, every event fires 5 duplicate handlers.

Provider-Agnostic Design Patterns

Correct Pattern: Core Logic in Manager

-- In diff/manager.lua
function DiffManager:show_diff(data)
  -- Provider-independent logic here
  self._events:emit("diff:created", diff_entry)
end

-- In protocol/claude.lua  
protocol.on_tool_call("showDiff", function(data)
  -- Just delegate to core
  self._diff_manager:show_diff(data)
end)

Anti-Pattern: Provider Logic in Core

-- BAD: Don't do this in manager.lua
if data.protocol == "claude" then
  -- Claude-specific handling
elseif data.protocol == "opencode" then  
  -- OpenCode-specific handling
end

Performance-Critical Code Locations

Based on DIFFUSION_PERFORMANCE_OPTIMIZATION.md analysis:

High-Impact Performance Issues

  1. Double Navigation (diff/navigation.lua:62-72) - 5min fix, eliminates visual jump
  2. Git Diff Parsing (diff/manager.lua:675-790) - 15-25ms blocking operation
  3. Event Scheduling (utils/events.lua:84) - 100-250ms cumulative overhead
  4. Memory Leaks - Fixed: event listeners now use unsub pattern (see Mandatory Event Listener Lifecycle Rule above)

Optimization Priorities

  1. Immediate (1-2 days): Fix double navigation, cache git parsing, conditional event scheduling
  2. Medium-term (1 week): Async I/O, buffer pooling, performance monitoring
  3. Long-term (1 month): Complete provider abstraction, advanced caching, telemetry

WebSocket MCP Implementation

Server Architecture (server/)

  • Entry point: server/init.lua - Port discovery and lifecycle
  • WebSocket implementation: server/websocket_server.lua - RFC 6455 compliant
  • Authentication: server/handshake.lua - Token-based validation
  • Performance note: Already optimized with XOR implementations and string buffer pooling

Tool Registry (tools/)

  • Registry: tools/registry.lua - MCP tool registration and discovery
  • Execution: tools/executor.lua - Tool call routing and validation
  • Schema validation: tools/validator.lua - Parameter validation

Testing Structure

Test Organization

tests/
├── unit/           # Component unit tests
├── integration/    # Cross-component integration tests  
├── component/      # Individual component behavior tests
├── mocks/          # Test mocks and fixtures
└── fixtures/       # Test data and configurations

Running Tests

  • Full suite: lua tests/run_tests.lua
  • Specific tests: lua tests/unit/diff/manager_test.lua
  • Mock objects: Available in tests/mocks/ for isolated testing

Configuration Architecture

Layered Configuration (config.lua)

  • User config: Passed to setup()
  • Service-specific: Per-protocol configurations
  • Runtime config: Dynamic configuration updates
  • Validation: Schema validation with helpful error messages

Critical Configuration for Claude

require('diffusion').setup({
  services = {
    claude = {
      enabled = true,
      auto_reconnection = true,
      diff = {
        force_blocking = true,    -- Required for Claude
        context_lines = 3,        -- Performance optimization
        concurrent_limit = 3      -- Resource management
      }
    }
  },
  logging = {
    level = "DEBUG"             -- For performance analysis
  }
})

Debugging and Troubleshooting

Common Performance Issues

  1. Slow diff creation: Check git diff parsing in manager.lua lines 675-790
  2. Double navigation jumps: Issue in navigation.lua lines 62-72
  3. Memory growth: Event listener cleanup in events.lua
  4. Connection issues: WebSocket handshake and token validation

Log Analysis

  • Location: ~/.local/share/nvim/diffusion.log (user's config overrides default)
  • Performance data: Enable DEBUG level for timing information
  • Event tracing: Look for event emission/handling sequences
  • Connection flow: Track WebSocket handshake and MCP tool calls

Development Workflow

Making Changes

  1. Read performance guide first: Always check DIFFUSION_PERFORMANCE_OPTIMIZATION.md for relevant optimizations
  2. Core vs protocol logic: Keep business logic in diff/manager.lua, not protocol handlers
  3. Event-driven communication: Use events for component communication
  4. Test with real Claude: Use :CC <message> to test integration
  5. Monitor performance: Check DEBUG logs for timing regressions

Code Locations for Common Tasks

  • Add new tool: tools/registry.lua
  • Modify diff behavior: diff/manager.lua
  • Fix navigation issues: diff/navigation.lua
  • Protocol-specific logic: protocol/claude.lua (keep minimal)
  • Event handling: utils/events.lua
  • Performance optimization: Reference optimization guide sections

Security Considerations

Authentication and Access Control

  • Local-only binding: Server bound to 127.0.0.1 (server/init.lua)
  • Token-based auth: UUID4 tokens in lock files (utils/crypto.lua)
  • File validation: Path validation in tool handlers (utils/file.lua)
  • Process isolation: Different tokens per workspace

Critical Security Files

  • Token validation: server/handshake.lua:validate_auth()
  • Crypto functions: utils/crypto.lua
  • File safety: utils/file.lua:validate_file_path()

Performance Monitoring

Built-in Metrics

  • Connection stats: Each protocol handler tracks usage
  • Resource monitoring: Active diffs, connections, buffers
  • Event statistics: Event emission and handling counts
  • Timing data: Available in DEBUG logs

Performance Budgets (from optimization guide)

  • Diff creation: <10ms (currently 20-35ms)
  • Memory baseline: <10MB (currently 15-25MB)
  • Event processing: <100ms total (currently 100-250ms)
  • Navigation: Zero visual jumps (currently has double jump)

Future Claude Instances

When working on this codebase:

  1. Always reference DIFFUSION_PERFORMANCE_OPTIMIZATION.md for performance context
  2. Maintain provider-agnostic architecture - keep core logic in diff/manager.lua
  3. Use event system for communication - avoid direct component coupling
  4. Test with real Claude integration - use :CC commands for validation
  5. Monitor performance impact - enable DEBUG logging and watch for regressions
  6. Follow the 4-step diff flow - understand Claude's reactive nature
  7. Be mindful of fast event context - use vim.defer_fn(..., 0) when needed