Skip to content

Latest commit

 

History

History
1335 lines (1027 loc) · 39.4 KB

File metadata and controls

1335 lines (1027 loc) · 39.4 KB

OpenCode Integration Refactoring Roadmap

Document Version: 1.0 Created: 2025-10-22 Status: Active Refactoring Guide Last Commit: 811e953 (refactor(claude): partial migration to inline protocol implementation)


Executive Summary

Achievement: OpenCode Integration Complete ✅

The OpenCode provider has been successfully integrated into diffusion.nvim, providing:

  • HTTP client connectivity to OpenCode's API
  • Server-Sent Events (SSE) diff streaming
  • Real-time edit proposals and acceptance workflow
  • Automatic workspace detection and connection
  • Provider switching between Claude and OpenCode

Current State Assessment

Total Lines of Code: 4,533 lines (6 core files analyzed) Modified Files: 8 files in current working tree Architecture Health: 🟨 Yellow - Functional but needs refactoring Provider Agnosticism: 🔴 Red - Significant violations present

Key Metrics:

  • protocol/claude.lua: 1,147 lines (target: ~350 lines, -797 lines needed)
  • client/opencode_client.lua: 1,075 lines (target: ~600 lines, -475 lines needed)
  • protocol/opencode.lua: 476 lines (acceptable range)
  • plugin/functions.lua: 757 lines (needs abstraction extraction)
  • config.lua: 719 lines (needs business logic separation)

Critical Path Forward

This document provides a systematic, phased approach to refactoring the codebase to achieve:

  1. ✅ Full provider-agnostic architecture
  2. ✅ Standardized handler interfaces
  3. ✅ ~1,200 lines of code reduction
  4. ✅ Elimination of architectural violations
  5. ✅ Maintainable, testable, extensible design

1. Current HEAD Analysis

Modified Files (Unstaged Changes)

plugin/functions.lua | 212 changes (+142/-70)

Refactoring Headers Added (8 files):

  1. plugin/functions.lua - Commands layer with architectural violations
  2. lua/diffusion/protocol/init.lua - Protocol routing layer
  3. lua/diffusion/protocol/claude.lua - Claude MCP handler
  4. lua/diffusion/protocol/opencode.lua - OpenCode HTTP handler
  5. lua/diffusion/init.lua - Core initialization
  6. lua/diffusion/ui/init.lua - UI and selection layer
  7. lua/diffusion/config.lua - Configuration management
  8. lua/diffusion/client/opencode_client.lua - HTTP client transport

Architecture Violations by Severity

🔴 CRITICAL (Blocking Issues)

  1. Missing send_at_mention() Abstraction

    • Location: protocol/init.lua
    • Impact: DiffusionSend command bypasses protocol layer
    • Violation: Commands directly access handler internals
    • Files affected: plugin/functions.lua (lines 128-143)
    • Estimated fix time: 2 hours
  2. DiffusionSend Directly Accesses Claude Handler

    • Location: plugin/functions.lua:129
    • Code: claude_handler._server:broadcast_message()
    • Impact: Breaks provider agnosticism, OpenCode can't use command
    • Estimated fix time: 1 hour (depends on #1)
  3. Handler Interface Not Standardized

    • Missing methods: send_at_mention() in both handlers
    • No interface validation on initialization
    • Impact: Runtime errors, inconsistent behavior
    • Estimated fix time: 4 hours

🟠 HIGH Priority

  1. Visual Selection Extraction Duplicated

    • Location: plugin/functions.lua:113-126, ui/init.lua:41-222
    • Should be: utils/selection.lua:get_visual_marks()
    • Impact: Duplication, inconsistent behavior
    • Lines to save: ~80 lines
    • Estimated fix time: 3 hours
  2. Business Logic in Claude Handler

    • Location: protocol/claude.lua (1,147 lines, target: 350)
    • Tool implementations inline (should be in ToolService)
    • Diff management logic (should delegate to DiffManager)
    • Tmux operations (should be in TmuxService)
    • Impact: Massive file, hard to maintain, duplication risk
    • Lines to extract: ~797 lines
    • Estimated fix time: 2-3 days
  3. Business Logic in OpenCode Client

    • Location: client/opencode_client.lua (1,075 lines, target: 600)
    • Diff dismissal logic (lines 642-694)
    • Diff tracking in _pending_diffs
    • Navigation after edits
    • Impact: Transport layer doing business logic
    • Lines to extract: ~475 lines
    • Estimated fix time: 1-2 days

🟡 MEDIUM Priority

  1. Config Module Has Auto-Mode Logic

    • Location: config.lua:296-336
    • Code modifies handler enabled flags
    • Should be: Protocol layer interprets "auto" mode
    • Impact: Separation of concerns violation
    • Estimated fix time: 2 hours
  2. Selection Tracking Calls Handlers Directly

    • Location: ui/init.lua
    • Should emit events, handlers subscribe optionally
    • Impact: Tight coupling, not provider-agnostic
    • Estimated fix time: 2 hours

🔵 LOW Priority

  1. No Schema Validation in Config

    • Location: config.lua
    • Only basic nil checks
    • Should have: JSON-schema style validation
    • Estimated fix time: 4 hours
  2. Patch Parsing Duplicated

    • Location: client/opencode_client.lua
    • Should be: Shared diff utility
    • Impact: Minor duplication
    • Estimated fix time: 1 hour

2. Critical Architectural Issues (Detailed Analysis)

Issue #1: Missing send_at_mention() Abstraction [CRITICAL]

Problem Statement: The :DiffusionSend command (used for <leader>s keybinding) directly accesses Claude handler internals, completely bypassing the protocol abstraction layer. This violates the core architectural principle that commands should never know which provider is active.

Current Code Flow (BROKEN):

User presses <leader>s
  → DiffusionSend command
    → Directly accesses: diffusion._protocol._handlers.claude
      → Calls: claude_handler._server:broadcast_message()
        → Sends MCP "at_mentioned" message

Correct Code Flow (TARGET):

User presses <leader>s
  → DiffusionSend command
    → Calls: diffusion._protocol:send_at_mention(file, start, end)
      → Protocol gets active handler
        → Delegates to: handler:send_at_mention(file, start, end)
          → Claude: Sends MCP at_mentioned via WebSocket
          → OpenCode: Sends @file:line-line via HTTP

Files to Modify:

  1. lua/diffusion/protocol/init.lua - Add protocol method

    function Protocol:send_at_mention(file_path, start_line, end_line)
      local handler = self:_get_active_handler()
      if not handler then
        return false, "No active service"
      end
    
      if not handler.send_at_mention then
        return false, "Handler does not support at-mentions"
      end
    
      return handler:send_at_mention(file_path, start_line, end_line)
    end
  2. lua/diffusion/protocol/claude.lua - Implement handler method

    function ClaudeHandler:send_at_mention(file_path, start_line, end_line)
      if not self._server or not self._server:has_clients() then
        return false, "No clients connected"
      end
    
      local success = self._server:broadcast_message({
        jsonrpc = "2.0",
        method = "at_mentioned",
        params = {
          filePath = file_path,
          lineStart = start_line - 1,  -- 0-based for Claude
          lineEnd = end_line - 1,
        }
      })
    
      if success and self._config.focus_after_send and vim.env.TMUX then
        vim.fn.system("tmux select-pane -t +1 2>/dev/null")
      end
    
      return success, success and nil or "Broadcast failed"
    end
  3. lua/diffusion/protocol/opencode.lua - Implement handler method

    function OpenCodeHandler:send_at_mention(file_path, start_line, end_line)
      if not self._client or not self._client:is_connected() then
        return false, "Not connected to OpenCode"
      end
    
      local ref = self._client:format_file_reference(
        file_path, start_line, end_line
      )
    
      local success, err = self._client:send_prompt(ref, false)
      if success then
        self._stats.prompts_sent = self._stats.prompts_sent + 1
      end
    
      return success, err
    end
  4. plugin/functions.lua - Update command to use protocol

    -- BEFORE (lines 113-150):
    local mark_start = vim.fn.getpos("'<")[2]
    local mark_end = vim.fn.getpos("'>")[2]
    -- ... validation ...
    local claude_handler = diffusion._protocol._handlers.claude  -- ❌ WRONG
    local success = claude_handler._server:broadcast_message(...)  -- ❌ WRONG
    
    -- AFTER:
    local selection = require('diffusion.utils.selection').get_visual_marks()
    if not selection then
      vim.notify("No visual selection found", vim.log.levels.ERROR)
      return
    end
    
    local success, err = diffusion._protocol:send_at_mention(
      selection.file_path, selection.start_line, selection.end_line
    )
    
    if not success then
      vim.notify("Failed to send: " .. (err or "unknown error"), vim.log.levels.ERROR)
    end

Dependencies:

  • Requires Issue #4 (selection utility extraction) to be completed first
  • Blocks: All provider-agnostic command implementations

Estimated Effort: 2-3 hours Impact: HIGH - Enables provider-agnostic commands Risk: LOW - Straightforward delegation pattern


Issue #4: Visual Selection Utility Needs Extraction [HIGH]

Problem Statement: Visual selection extraction logic is duplicated between plugin/functions.lua and ui/init.lua, with slightly different implementations. This violates DRY and creates maintenance burden.

Current Duplication:

  • plugin/functions.lua:113-126 - Gets marks for DiffusionSend
  • ui/init.lua:41-222 - Complex selection extraction with mode handling
  • Different error handling, different data structures returned

Target Architecture: Create lua/diffusion/utils/selection.lua with a single, well-tested implementation:

-- lua/diffusion/utils/selection.lua
local M = {}

-- Get visual selection marks
-- Returns: {file_path, start_line, end_line, text, selection_type} or nil
function M.get_visual_marks()
  -- Get marks (persist after visual mode exits)
  local start_pos = vim.fn.getpos("'<")
  local end_pos = vim.fn.getpos("'>")

  -- Validate marks
  if start_pos[2] == 0 or end_pos[2] == 0 then
    return nil
  end

  -- Get file path
  local file_path = vim.api.nvim_buf_get_name(0)
  if not file_path or file_path == "" then
    return nil
  end

  -- Extract text using marks
  local start_line = start_pos[2]
  local end_line = end_pos[2]
  local start_col = math.max(0, start_pos[3] - 1)  -- Convert to 0-based
  local end_col = math.max(0, end_pos[3] - 1)

  local lines = vim.api.nvim_buf_get_text(
    0, start_line - 1, start_col, end_line - 1, end_col, {}
  )
  local text = table.concat(lines, '\n')

  if text == "" then
    return nil
  end

  return {
    file_path = file_path,
    start_line = start_line,
    end_line = end_line,
    start_col = start_pos[3],  -- Keep 1-based for consistency
    end_col = end_pos[3],
    text = text,
    selection_type = "visual_marks"
  }
end

-- Get current selection (visual mode or marks)
function M.get_current_selection()
  local mode = vim.fn.mode()

  -- If in visual mode, get live selection
  if mode == 'v' or mode == 'V' or mode == '\22' then
    -- Use marks even in visual mode (more reliable)
    return M.get_visual_marks()
  end

  -- Try marks first
  local selection = M.get_visual_marks()
  if selection then
    return selection
  end

  -- Fallback to word/line under cursor
  return M.get_fallback_selection()
end

-- Get fallback (word or line under cursor)
function M.get_fallback_selection()
  local cursor_pos = vim.api.nvim_win_get_cursor(0)
  local line_num = cursor_pos[1]

  -- Try word under cursor
  local word = vim.fn.expand('<cword>')
  if word and word ~= "" then
    return {
      file_path = vim.api.nvim_buf_get_name(0),
      start_line = line_num,
      end_line = line_num,
      text = word,
      selection_type = "word"
    }
  end

  -- Fallback to current line
  local line = vim.api.nvim_get_current_line()
  return {
    file_path = vim.api.nvim_buf_get_name(0),
    start_line = line_num,
    end_line = line_num,
    text = line,
    selection_type = "line"
  }
end

return M

Files to Modify:

  1. Create: lua/diffusion/utils/selection.lua (new file)
  2. Update: plugin/functions.lua:113-126 → use utility
  3. Update: ui/init.lua:41-222 → use utility
  4. Add: tests/unit/utils/selection_test.lua (unit tests)

Code Reduction: ~80 lines Estimated Effort: 3 hours Impact: HIGH - Enables Issue #1, improves consistency Risk: LOW - Pure refactoring, behavior unchanged


Issue #5: Business Logic in Claude Handler [HIGH]

Problem Statement: The Claude handler (protocol/claude.lua) is 1,147 lines long when it should be a thin translation layer of ~350 lines. It contains massive amounts of business logic that should be in core services.

Current File Breakdown:

Lines 1-140:    Module setup, imports, initialization (OK)
Lines 141-180:  is_available() logic (OK, provider-specific)
Lines 181-300:  start/stop/connection lifecycle (OK)
Lines 301-400:  send_message, send_selection (OK, will add send_at_mention)

Lines 401-700:  ❌ TOOL IMPLEMENTATIONS (should be ToolService)
  - openFile tool
  - showDiff/openDiff tools
  - dismissDiff tool
  - close_tab tool
  - getCurrentSelection tool
  - saveDocument tool
  - getDiagnostics tool

Lines 701-900:  ❌ DIFF MANAGEMENT (should delegate to DiffManager)
  - Diff creation logic
  - Diff dismissal logic
  - Navigation after diff

Lines 901-1000: ❌ TMUX OPERATIONS (should be TmuxService)
  - Pane discovery
  - /ide command sending
  - Pane focusing

Lines 1001-1100: ❌ SELECTION TRACKING (should be UIService)
  - Real-time selection monitoring
  - selection_changed MCP messages

Lines 1101-1147: WebSocket message handling (OK, protocol-specific)

Refactoring Strategy:

Phase 1: Extract Tool Service (Lines 401-700)

  • Create: lua/diffusion/services/tool_service.lua
  • Move all tool implementations to service
  • Handler becomes: Receive MCP call → Validate → Delegate to ToolService → Return result
  • Estimated reduction: 300 lines

Phase 2: Delegate to DiffManager (Lines 701-900)

  • Already exists: lua/diffusion/diff/manager.lua
  • Remove diff logic from handler
  • Change to: Handler translates MCP params → Calls DiffManager methods → Translates response
  • Estimated reduction: 200 lines

Phase 3: Extract Tmux Operations (Lines 901-1000)

  • Already exists: lua/diffusion/services/tmux.lua
  • Move pane discovery to TmuxService
  • Handler just calls service methods
  • Estimated reduction: 100 lines

Phase 4: Move Selection Tracking (Lines 1001-1100)

  • Already exists: lua/diffusion/ui/init.lua has tracking
  • Remove handler-specific tracking
  • Change to: UI emits events → Handler subscribes → Sends MCP notifications
  • Estimated reduction: 100 lines

Target Handler Size:

  • Before: 1,147 lines
  • After: ~350 lines
  • Reduction: 797 lines (69% reduction)

Estimated Effort: 2-3 days Impact: VERY HIGH - Enables provider agnosticism, reduces duplication Risk: MEDIUM - Requires careful refactoring, extensive testing


Issue #6: Business Logic in OpenCode Client [HIGH]

Problem Statement: The OpenCode HTTP client (client/opencode_client.lua) is 1,075 lines when it should be a focused transport layer of ~600 lines. It contains business logic for diff management and navigation.

Current File Breakdown:

Lines 1-150:    Module setup, connection discovery (OK)
Lines 151-300:  HTTP communication, SSE setup (OK)
Lines 301-450:  SSE event parsing (OK)

Lines 451-600:  ❌ EDIT DETAILS PARSING (should be utils or DiffManager)
  - Diff structure parsing
  - Content extraction
  - Should be shared utility (Claude may need it too)

Lines 601-700:  ❌ DIFF DISMISSAL LOGIC (should be DiffManager)
  - _handle_file_edited() manually dismisses diffs
  - _find_diff_for_file() duplicates DiffManager
  - Should just emit event, let Navigation module handle

Lines 701-850:  ❌ DIFF TRACKING (_pending_diffs)
  - Tracks diff_id by callID
  - Should be in DiffManager
  - Single source of truth violation

Lines 851-1075: Process discovery, tmux checks (OK, but could simplify)

Refactoring Strategy:

Phase 1: Simplify Event Handling (Lines 601-700)

  • Remove direct diff dismissal
  • Change to: Just emit "sse:file_edited" event with file_path
  • Let OpenCodeHandler or Navigation module listen and handle dismissal
  • Estimated reduction: 100 lines

Phase 2: Remove Diff Tracking (Lines 701-850)

  • Delete _pending_diffs table
  • DiffManager should track call_id → diff_id relationship
  • Client doesn't need to know about diffs
  • Estimated reduction: 150 lines

Phase 3: Extract Edit Details Parsing (Lines 451-600)

  • Create: lua/diffusion/utils/diff_parser.lua
  • Shared utility for parsing diff structures
  • Both Claude and OpenCode can use
  • Estimated reduction: 150 lines

Phase 4: Simplify Format Functions (Various)

  • Move format_file_reference() to handler or utils
  • Keep only transport-specific code in client
  • Estimated reduction: 75 lines

Target Client Size:

  • Before: 1,075 lines
  • After: ~600 lines
  • Reduction: 475 lines (44% reduction)

Estimated Effort: 1-2 days Impact: HIGH - Cleaner separation, easier to maintain Risk: LOW - Well-isolated changes


3. File-by-File Refactoring Tasks

File 1: plugin/functions.lua (757 lines)

Current State: Commands layer with direct handler access

Violations:

  • ☐ DiffusionSend (lines 95-154): Directly accesses claude_handler._server
  • ☐ DiffusionSend (lines 135-143): Provider-specific MCP message
  • ☐ DiffusionSend (lines 113-126): Selection extraction should be utility
  • ☐ Missing: Protocol abstraction calls

Refactoring Tasks:

  1. Extract Selection Utility [HIGH]

    • Lines: 113-126
    • Target: lua/diffusion/utils/selection.lua:get_visual_marks()
    • Complexity: LOW
    • Time: 1 hour
  2. Add Protocol Delegation [CRITICAL]

    • Lines: 95-154
    • Change to: diffusion._protocol:send_at_mention()
    • Dependency: Requires protocol method first
    • Complexity: LOW
    • Time: 30 minutes
  3. Remove Direct Handler Access [CRITICAL]

    • Lines: 129-133
    • Delete: claude_handler._server:broadcast_message()
    • Complexity: LOW
    • Time: 15 minutes

Expected Result:

  • ✅ All commands delegate to protocol layer
  • ✅ No direct handler access
  • ✅ Selection extraction reusable

Code Reduction: ~30 lines (reuse utility)


File 2: lua/diffusion/protocol/init.lua (Current: Unknown, Acceptable)

Current State: Protocol routing layer, missing send_at_mention

Violations:

  • ☐ Missing: send_at_mention() method
  • ☐ send_selection() exists but no distinction from at-mention
  • ☐ No handler interface validation

Refactoring Tasks:

  1. Add send_at_mention() Method [CRITICAL]

    • Add new method to Protocol class
    • Delegate to active handler
    • Handle errors gracefully
    • Complexity: LOW
    • Time: 1 hour
  2. Document Selection vs At-Mention [MEDIUM]

    • Add comments explaining distinction
    • selection: Continuous tracking, real-time
    • at_mention: User action, explicit reference
    • Complexity: TRIVIAL
    • Time: 15 minutes
  3. Add Handler Validation [MEDIUM]

    • Validate handlers implement required interface on init
    • Required methods: start, stop, is_available, is_connected
    • Required methods: send_message, send_selection, send_at_mention
    • Complexity: MEDIUM
    • Time: 2 hours

Expected Result:

  • ✅ Complete protocol abstraction for all operations
  • ✅ Clear interface requirements documented
  • ✅ Runtime validation prevents incomplete handlers

Code Addition: ~60 lines


File 3: lua/diffusion/protocol/claude.lua (1,147 lines → Target: 350)

Current State: Massive handler with embedded business logic

Violations:

  • ☐ Missing: send_at_mention() method
  • ☐ File size: 1,147 lines (should be ~350)
  • ☐ Tool implementations inline (lines 401-700)
  • ☐ Diff management logic (lines 701-900)
  • ☐ Tmux operations (lines 901-1000)
  • ☐ Selection tracking (lines 1001-1100)

Refactoring Tasks:

  1. Add send_at_mention() Method [CRITICAL]

    • Insert after send_selection() method
    • ~30 lines implementation
    • Complexity: LOW
    • Time: 30 minutes
  2. Extract Tool Implementations [HIGH] ⏰ 2 days

    • Create: lua/diffusion/services/tool_service.lua
    • Move: Lines 401-700 → ToolService
    • Handler becomes thin wrapper
    • Complexity: HIGH
    • Time: 2 days
  3. Delegate Diff Operations [HIGH] ⏰ 1 day

    • Remove: Lines 701-900
    • Use: Existing DiffManager methods
    • Handler translates MCP ↔ DiffManager
    • Complexity: MEDIUM
    • Time: 1 day
  4. Extract Tmux Service Calls [MEDIUM] ⏰ 4 hours

    • Remove: Lines 901-1000
    • Use: Existing TmuxService
    • Complexity: LOW
    • Time: 4 hours
  5. Move Selection Tracking to UI [MEDIUM] ⏰ 3 hours

    • Remove: Lines 1001-1100
    • Change: UI emits events, handler subscribes
    • Complexity: MEDIUM
    • Time: 3 hours

Expected Result:

  • ✅ Handler is ~350 lines (thin translation layer)
  • ✅ All business logic in core services
  • ✅ Easier to maintain and test
  • ✅ No duplication with OpenCode handler

Code Reduction: 797 lines (69%)


File 4: lua/diffusion/protocol/opencode.lua (476 lines - Acceptable)

Current State: Reasonably sized, but missing send_at_mention

Violations:

  • ☐ Missing: send_at_mention() method
  • ☐ Some diff dismissal logic (should delegate)

Refactoring Tasks:

  1. Add send_at_mention() Method [CRITICAL]

    • Insert after send_selection() or send_message()
    • ~25 lines implementation
    • Complexity: LOW
    • Time: 30 minutes
  2. Simplify Diff Handling [MEDIUM]

    • Review _handle_file_edited()
    • Emit events instead of direct dismissal
    • Complexity: LOW
    • Time: 1 hour

Expected Result:

  • ✅ Complete handler interface implemented
  • ✅ Cleaner event-driven architecture

Code Changes: +25 lines, ~15 lines simplified


File 5: lua/diffusion/init.lua (359 lines - Acceptable)

Current State: Core initialization, mostly clean

Violations:

  • ☐ send_selection() may be redundant with protocol
  • ☐ Direct protocol internals access in send_message()
  • ☐ No protocol interface validation

Refactoring Tasks:

  1. Add Protocol Validation [MEDIUM]

    • After protocol setup in _init_components()
    • Validate each handler has required methods
    • Warn if incomplete
    • Complexity: LOW
    • Time: 1 hour
  2. Review send_selection() API [LOW]

    • Determine if needed at top level
    • May just delegate to protocol
    • Complexity: TRIVIAL
    • Time: 30 minutes

Expected Result:

  • ✅ Early validation catches incomplete handlers
  • ✅ Clear API delegation

Code Addition: ~30 lines (validation)


File 6: lua/diffusion/ui/init.lua (Current: Unknown)

Current State: UI layer with selection extraction

Violations:

  • ☐ get_current_selection() should use extracted utility
  • ☐ Selection tracking calls handlers directly
  • ☐ Could be provider-coupled

Refactoring Tasks:

  1. Use Selection Utility [HIGH]

    • Replace internal selection logic
    • Use: require('diffusion.utils.selection').get_current_selection()
    • Complexity: LOW
    • Time: 1 hour
  2. Emit Events for Selection Changes [MEDIUM]

    • Change: Don't call protocol directly
    • Emit: "ui:selection_changed" event
    • Handlers subscribe if they want tracking
    • Complexity: MEDIUM
    • Time: 2 hours

Expected Result:

  • ✅ Reuses common selection utility
  • ✅ Decoupled from protocols

Code Reduction: ~80 lines (reuse utility)


File 7: lua/diffusion/config.lua (719 lines)

Current State: Configuration with embedded logic

Violations:

  • ☐ Auto-mode logic in config (lines 296-336)
  • ☐ Modifies handler enabled flags
  • ☐ Should be pure data

Refactoring Tasks:

  1. Move Auto-Mode Logic [MEDIUM]

    • Remove: Lines 296-336 from config
    • Move to: Protocol layer's detect_service()
    • Config just stores "auto" value
    • Complexity: MEDIUM
    • Time: 2 hours
  2. Add Schema Validation [LOW]

    • Type checking, enum validation
    • Range validation
    • Complexity: MEDIUM
    • Time: 4 hours

Expected Result:

  • ✅ Config is pure data
  • ✅ Better error messages
  • ✅ Separation of concerns

Code Reduction: ~40 lines (remove logic)


File 8: lua/diffusion/client/opencode_client.lua (1,075 lines → Target: 600)

Current State: HTTP client with business logic

Violations:

  • ☐ _handle_file_edited() has dismissal logic
  • ☐ _find_diff_for_file() duplicates DiffManager
  • ☐ _pending_diffs tracking (should be DiffManager)
  • ☐ format_file_reference() is presentation logic

Refactoring Tasks:

  1. Simplify Event Handling [MEDIUM]

    • _handle_file_edited(): Just emit event
    • Remove: Direct dismiss_diff() calls
    • Remove: _find_diff_for_file() method
    • Complexity: LOW
    • Time: 2 hours
  2. Remove Diff Tracking [HIGH]

    • Delete: _pending_diffs table
    • DiffManager should track relationships
    • Complexity: MEDIUM
    • Time: 3 hours
  3. Extract Edit Parser [MEDIUM]

    • Create: utils/diff_parser.lua
    • Move: Edit details parsing logic
    • Complexity: MEDIUM
    • Time: 3 hours
  4. Move Format Function [LOW]

    • Move format_file_reference to handler
    • Or: utils/opencode_format.lua
    • Complexity: TRIVIAL
    • Time: 30 minutes

Expected Result:

  • ✅ Client is focused on transport
  • ✅ Business logic in appropriate layers
  • ✅ Easier to test and maintain

Code Reduction: 475 lines (44%)


4. Code Reduction Opportunities

Summary of Reductions

File Current Target Reduction Method
protocol/claude.lua 1,147 350 -797 Extract to services
client/opencode_client.lua 1,075 600 -475 Remove business logic
plugin/functions.lua 757 ~727 -30 Use utilities
ui/init.lua ~800 ~720 -80 Use utilities
config.lua 719 ~679 -40 Remove logic
TOTAL 4,498 3,076 -1,422 31% reduction

Duplicate Code Identification

Between Claude and OpenCode Handlers:

  • Lock file reading logic (Claude-specific, but pattern useful)
  • Status reporting structure (should be standardized)
  • Connection lifecycle (start/stop patterns similar)

Between Commands:

  • Multiple commands do initialization checks (DRY opportunity)
  • Error notification patterns repeated
  • Could extract: ensure_initialized() helper

Between Client and Handler:

  • Error handling patterns
  • Event emission patterns
  • Logging patterns (could standardize)

5. Provider-Agnostic Checklist

Operations Requiring Protocol Abstraction

  • send_message(text) - ✅ Already abstracted
  • send_selection(selection_data) - ✅ Already abstracted
  • send_at_mention(file, start, end) - ❌ MISSING (Issue #1)
  • start() - ✅ Already abstracted
  • stop() - ✅ Already abstracted
  • is_available() - ✅ Already abstracted
  • is_connected() - ✅ Already abstracted
  • get_status() - ✅ Already abstracted

Common Code to Extract

Priority 1 - Create New:

  • lua/diffusion/utils/selection.lua - Visual selection extraction
  • lua/diffusion/utils/diff_parser.lua - Diff structure parsing
  • lua/diffusion/services/tool_service.lua - Tool implementations

Priority 2 - Enhance Existing:

  • lua/diffusion/diff/manager.lua - Add call_id tracking
  • lua/diffusion/services/tmux.lua - Add pane discovery methods
  • lua/diffusion/ui/init.lua - Event-based selection tracking

Handler Interface Requirements

Standard Interface (all handlers must implement):

-- Lifecycle
function Handler:start() -> success, error
function Handler:stop() -> success, error

-- Availability
function Handler:is_available() -> boolean
function Handler:is_connected() -> boolean

-- Communication
function Handler:send_message(text) -> success, error
function Handler:send_selection(selection_data) -> success, error
function Handler:send_at_mention(file, start, end) -> success, error

-- Status
function Handler:get_status() -> table

Optional Methods (handler-specific):

  • get_tmux_pane() - If tmux integration available
  • is_same_tmux_window() - If workspace checking available
  • Custom configuration handling

6. Implementation Phases

Phase 1: Critical Fixes (Week 1) ⏰ Estimated: 8-12 hours

Goal: Establish provider-agnostic foundation

Tasks:

  1. Create visual selection utility (utils/selection.lua)
  2. Add Protocol:send_at_mention() method
  3. Implement ClaudeHandler:send_at_mention()
  4. Implement OpenCodeHandler:send_at_mention()
  5. Update DiffusionSend command to use protocol
  6. Add handler interface validation in init

Deliverables:

  • ✅ All commands use protocol abstraction
  • ✅ No direct handler access from commands
  • ✅ Basic interface validation working

Success Criteria:

  • DiffusionSend works with both Claude and OpenCode
  • Tests pass
  • No architectural violations in command layer

Phase 2: Protocol Interface Standardization (Week 2) ⏰ Estimated: 12-16 hours

Goal: Ensure consistent handler interfaces

Tasks:

  1. Document standard handler interface
  2. Add interface compliance tests
  3. Standardize error return formats
  4. Standardize status report structure
  5. Add protocol-level error handling
  6. Create handler development guide

Deliverables:

  • ✅ Documented handler interface contract
  • ✅ Validation tests for all handlers
  • ✅ Consistent error handling across handlers

Success Criteria:

  • All handlers pass interface compliance tests
  • Error messages are consistent and helpful
  • New handler implementation is straightforward

Phase 3: Business Logic Extraction (Weeks 3-4) ⏰ Estimated: 3-5 days

Goal: Extract business logic from handlers to core services

Major Refactorings:

  1. Extract Claude tool implementations to ToolService
  2. Remove diff logic from Claude handler (use DiffManager)
  3. Extract tmux operations to TmuxService
  4. Move selection tracking to UI layer
  5. Simplify OpenCode client event handling
  6. Remove diff tracking from OpenCode client
  7. Extract diff parsing to utils

Deliverables:

  • services/tool_service.lua created
  • ✅ Claude handler reduced to ~350 lines
  • ✅ OpenCode client reduced to ~600 lines
  • utils/diff_parser.lua created

Success Criteria:

  • Handler files meet target sizes
  • All business logic in appropriate layers
  • Tests pass
  • No duplication between handlers

Phase 4: Polish and Optimization (Week 5) ⏰ Estimated: 1-2 days

Goal: Final cleanup, optimization, documentation

Tasks:

  1. Remove auto-mode logic from config
  2. Add schema validation to config
  3. Standardize logging across handlers
  4. Extract duplicate code patterns
  5. Add integration tests
  6. Update all documentation
  7. Performance regression tests
  8. Update CLAUDE.md with new architecture

Deliverables:

  • ✅ Config is pure data
  • ✅ Comprehensive test coverage
  • ✅ Updated documentation
  • ✅ Performance benchmarks maintained

Success Criteria:

  • All checkboxes in file headers completed
  • Zero architectural violations
  • Code reduction target met (1,400+ lines removed)
  • Performance maintained or improved

7. Testing Strategy

Unit Tests Required

New Utilities:

  • tests/unit/utils/selection_test.lua

    • Visual marks extraction
    • Mode detection
    • Fallback behavior
    • Edge cases (empty files, unnamed buffers)
  • tests/unit/utils/diff_parser_test.lua

    • OpenCode edit details parsing
    • Claude diff format (if applicable)
    • Malformed input handling

Modified Components:

  • tests/unit/protocol/init_test.lua

    • send_at_mention() routing
    • Handler interface validation
    • Error handling
  • tests/unit/protocol/claude_test.lua

    • send_at_mention() implementation
    • MCP message format
    • Line number conversion (0-based)
  • tests/unit/protocol/opencode_test.lua

    • send_at_mention() implementation
    • @file:line-line format
    • HTTP request handling

Integration Tests Required

Provider Switching:

  • Test switching between Claude and OpenCode
  • Test send_at_mention() with each provider
  • Test auto-mode detection

End-to-End:

  • Visual selection → DiffusionSend → Claude receives at_mentioned
  • Visual selection → DiffusionSend → OpenCode receives @file:line
  • Error handling when no provider available

Regression Tests

Critical Paths:

  • Diff creation and display (existing flow must still work)
  • Navigation after diff dismissal
  • Selection tracking (continuous monitoring)
  • Connection establishment and discovery

8. Risk Assessment

High Risk Areas

1. Claude Handler Refactoring (Phase 3)

  • Risk: Breaking existing diff workflow
  • Mitigation: Incremental changes, extensive testing after each step
  • Rollback: Keep git commits small, test after each extraction

2. Selection Utility Extraction (Phase 1)

  • Risk: Subtle behavior differences between implementations
  • Mitigation: Comprehensive unit tests, test with real workflows
  • Rollback: Easy to revert, single file change

3. Event System Changes (Phase 3)

  • Risk: Event listeners not properly subscribed/unsubscribed
  • Mitigation: Add debug logging, test cleanup on stop()
  • Rollback: Keep event emission patterns simple

Medium Risk Areas

4. Config Auto-Mode Logic Move (Phase 4)

  • Risk: Service detection behavior changes
  • Mitigation: Test auto-mode extensively before/after
  • Rollback: Logic is isolated, easy to revert

5. OpenCode Client Simplification (Phase 3)

  • Risk: Diff dismissal timing changes
  • Mitigation: Test edit workflow end-to-end
  • Rollback: Isolated to OpenCode, doesn't affect Claude

Low Risk Areas

6. Protocol Method Addition (Phase 1)

  • Risk: Minimal, new method doesn't break existing
  • Mitigation: Standard delegation pattern
  • Rollback: N/A (new feature)

7. Documentation Updates (Phase 4)

  • Risk: None
  • Mitigation: N/A
  • Rollback: N/A

9. Success Criteria

Code Metrics

  • Claude handler: ≤ 400 lines (currently 1,147)
  • OpenCode client: ≤ 650 lines (currently 1,075)
  • Total reduction: ≥ 1,200 lines
  • Zero direct handler access from commands
  • Zero architectural violations (per file headers)

Functionality

  • DiffusionSend works with Claude
  • DiffusionSend works with OpenCode
  • Provider switching maintains state correctly
  • Auto-mode detects available services
  • All existing features still work

Quality

  • All unit tests pass
  • All integration tests pass
  • No performance regression
  • Code coverage ≥ current level
  • Documentation complete and accurate

Architecture

  • All checkboxes in file headers marked complete
  • Handler interface standardized and validated
  • Business logic in core services only
  • Protocols are thin translation layers
  • Commands delegate to protocol layer only

10. Maintenance and Next Steps

After This Refactoring

Immediate Priorities:

  1. Add third provider (Gemini or Codex) to validate architecture
  2. Implement performance optimizations from DIFFUSION_PERFORMANCE_OPTIMIZATION.md
  3. Add telemetry and monitoring
  4. Improve error messages and debugging

Long-term Improvements:

  1. Plugin manager integration testing
  2. Multi-workspace support
  3. Offline mode / fallback behavior
  4. Configuration UI/TUI
  5. Advanced diff features (3-way merge, conflict resolution)

Contributing Guidelines

When adding new features after this refactoring:

DO:

  • Add new operations to protocol layer first
  • Implement in all handlers consistently
  • Write tests before implementation
  • Update interface validation
  • Follow established patterns

DON'T:

  • Access handlers directly from commands
  • Put business logic in handlers
  • Skip interface validation
  • Duplicate code between handlers
  • Break provider agnosticism

Appendix A: Quick Reference

File Header Checklist Status

Track completion of refactoring tasks from file headers:

plugin/functions.lua:

  • Extract get_visual_marks() to utils/selection.lua
  • Add Protocol:send_at_mention() to protocol/init.lua
  • Implement ClaudeHandler:send_at_mention()
  • Implement OpenCodeHandler:send_at_mention()
  • Update DiffusionSend to use protocol abstraction
  • Remove all direct handler access from this file
  • Add unit tests for selection utility

lua/diffusion/protocol/init.lua:

  • Add Protocol:send_at_mention() method
  • Document selection vs at-mention distinction
  • Verify all handlers implement standard interface
  • Add handler interface validation on initialization
  • Update error messages to be provider-agnostic
  • Extract handler availability checks to helper method
  • Add integration tests for handler switching

lua/diffusion/protocol/claude.lua:

  • Implement ClaudeHandler:send_at_mention()
  • Extract tool implementations to ToolService
  • Move diff logic to DiffManager core
  • Move tmux logic to TmuxService
  • Move selection tracking to UI layer
  • Reduce handler to pure translation (~350 lines)
  • Add handler interface compliance tests

lua/diffusion/protocol/opencode.lua:

  • Implement OpenCodeHandler:send_at_mention()
  • Extract diff dismissal to Navigation module
  • Move patch parsing to core diff utilities
  • Verify handler implements full protocol interface
  • Add error handling for HTTP failures
  • Add reconnection logic for SSE stream drops
  • Test with multiple OpenCode workspaces

lua/diffusion/init.lua:

  • Add protocol interface validation
  • Clarify send_selection() vs protocol:send_selection()
  • Improve error categorization and messages
  • Document component initialization dependencies
  • Add integration tests for initialization sequence
  • Verify cleanup runs on all exit paths

lua/diffusion/ui/init.lua:

  • Extract selection logic to utils/selection.lua
  • Create get_visual_marks() utility function
  • Update UI layer to use new utility
  • Update DiffusionSend to use new utility
  • Change selection tracking to emit events
  • Add unit tests for selection utility
  • Document mode detection behavior

lua/diffusion/config.lua:

  • Move auto-mode logic from config to protocol layer
  • Document all provider-specific config options
  • Add schema validation with helpful error messages
  • Extract config merging logic to separate function
  • Add config validation tests
  • Document configuration layer priority

lua/diffusion/client/opencode_client.lua:

  • Simplify _handle_file_edited() to just emit event
  • Remove _pending_diffs tracking
  • Remove _find_diff_for_file() method
  • Move format_file_reference() to handler or utils
  • Add reconnection logic for dropped connections
  • Add rate limiting for HTTP requests
  • Improve error messages with context

Appendix B: Command Reference

Useful Commands During Refactoring

Check line counts:

wc -l lua/diffusion/protocol/claude.lua

Find TODO comments:

grep -r "TODO\|FIXME\|XXX" lua/

Run specific test:

lua tests/unit/utils/selection_test.lua

Check for direct handler access:

grep -r "_handlers\\.claude\|_handlers\\.opencode" lua/ plugin/

Validate no provider checks in core:

grep -r 'if.*"claude"\|if.*"opencode"' lua/diffusion/diff/ lua/diffusion/ui/

Document Changelog

v1.0 - 2025-10-22

  • Initial document creation
  • Complete analysis of current HEAD
  • Detailed refactoring roadmap
  • File-by-file task breakdown
  • Implementation phases defined

END OF DOCUMENT

This roadmap will be updated as refactoring progresses. Mark checkboxes with ☑ when completed.