multifile claude accept

This commit is contained in:
zolinthecow 2025-07-11 00:49:42 -07:00
parent dac46b9a93
commit 50c50b94cc
8 changed files with 411 additions and 248 deletions

View File

@ -1,5 +1,7 @@
MIT License MIT License
TEST EDIT #1: Multi-file accept test
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights

View File

@ -1,6 +1,7 @@
# kickstart.nvim # kickstart.nvim
> Enhanced with nvim-claude integration for AI-powered development > Enhanced with nvim-claude integration for AI-powered development
> TEST EDIT #3: Multi-file testing
## Introduction ## Introduction

View File

@ -27,6 +27,9 @@ function M.update_stable_baseline()
-- Update our stable baseline reference -- Update our stable baseline reference
M.stable_baseline_ref = stash_hash M.stable_baseline_ref = stash_hash
persistence.current_stash_ref = stash_hash persistence.current_stash_ref = stash_hash
-- Save the updated state
persistence.save_state({ stash_ref = stash_hash })
end end
end end
@ -138,13 +141,9 @@ function M.post_tool_use_hook()
end end
end end
-- If no inline diff was shown, fall back to regular diff review -- If no inline diff was shown, just notify the user
if not opened_inline then if not opened_inline then
-- Trigger diff review with stash reference vim.notify('Claude made changes. Open the modified files to see inline diffs.', vim.log.levels.INFO)
local ok, diff_review = pcall(require, 'nvim-claude.diff-review')
if ok then
diff_review.handle_claude_edit(stash_ref, nil)
end
end end
end end
end end
@ -288,6 +287,7 @@ function M.setup_persistence()
-- Also restore the baseline reference from persistence if it exists -- Also restore the baseline reference from persistence if it exists
if persistence.current_stash_ref then if persistence.current_stash_ref then
M.stable_baseline_ref = persistence.current_stash_ref M.stable_baseline_ref = persistence.current_stash_ref
vim.notify('Restored baseline: ' .. M.stable_baseline_ref, vim.log.levels.DEBUG)
end end
-- Don't create a startup baseline - only create baselines when Claude makes edits -- Don't create a startup baseline - only create baselines when Claude makes edits
@ -543,6 +543,13 @@ function M.setup_commands()
desc = 'Reset Claude baseline for cumulative diffs', desc = 'Reset Claude baseline for cumulative diffs',
}) })
vim.api.nvim_create_user_command('ClaudeAcceptAll', function()
local inline_diff = require 'nvim-claude.inline-diff'
inline_diff.accept_all_files()
end, {
desc = 'Accept all Claude diffs across all files',
})
vim.api.nvim_create_user_command('ClaudeTrackModified', function() vim.api.nvim_create_user_command('ClaudeTrackModified', function()
-- Manually track all modified files as Claude-edited -- Manually track all modified files as Claude-edited
local utils = require 'nvim-claude.utils' local utils = require 'nvim-claude.utils'
@ -588,6 +595,115 @@ function M.setup_commands()
end, { end, {
desc = 'Track all modified files as Claude-edited (for debugging)', desc = 'Track all modified files as Claude-edited (for debugging)',
}) })
vim.api.nvim_create_user_command('ClaudeDebugTracking', function()
-- Debug command to show current tracking state
local inline_diff = require 'nvim-claude.inline-diff'
local persistence = require 'nvim-claude.inline-diff-persistence'
local utils = require 'nvim-claude.utils'
vim.notify('=== Claude Tracking Debug ===', vim.log.levels.INFO)
vim.notify('Stable baseline: ' .. (M.stable_baseline_ref or 'none'), vim.log.levels.INFO)
vim.notify('Persistence stash ref: ' .. (persistence.current_stash_ref or 'none'), vim.log.levels.INFO)
vim.notify('Claude edited files: ' .. vim.inspect(M.claude_edited_files), vim.log.levels.INFO)
vim.notify('Diff files: ' .. vim.inspect(vim.tbl_keys(inline_diff.diff_files)), vim.log.levels.INFO)
vim.notify('Active diffs: ' .. vim.inspect(vim.tbl_keys(inline_diff.active_diffs)), vim.log.levels.INFO)
-- Check current file
local current_file = vim.api.nvim_buf_get_name(0)
local git_root = utils.get_project_root()
if git_root then
local relative_path = current_file:gsub('^' .. vim.pesc(git_root) .. '/', '')
vim.notify('Current file relative path: ' .. relative_path, vim.log.levels.INFO)
vim.notify('Is tracked: ' .. tostring(M.claude_edited_files[relative_path] ~= nil), vim.log.levels.INFO)
end
end, {
desc = 'Debug Claude tracking state',
})
vim.api.nvim_create_user_command('ClaudeRestoreState', function()
-- Manually restore the state
local persistence = require 'nvim-claude.inline-diff-persistence'
local restored = persistence.restore_diffs()
if persistence.current_stash_ref then
M.stable_baseline_ref = persistence.current_stash_ref
end
vim.notify('Manually restored state', vim.log.levels.INFO)
end, {
desc = 'Manually restore Claude diff state',
})
vim.api.nvim_create_user_command('ClaudeCleanStaleTracking', function()
local utils = require 'nvim-claude.utils'
local persistence = require 'nvim-claude.inline-diff-persistence'
local git_root = utils.get_project_root()
if not git_root or not M.stable_baseline_ref then
vim.notify('No git root or baseline found', vim.log.levels.ERROR)
return
end
local cleaned_count = 0
local files_to_remove = {}
-- Check each tracked file for actual differences
for file_path, _ in pairs(M.claude_edited_files) do
local diff_cmd = string.format('cd "%s" && git diff %s -- "%s" 2>/dev/null', git_root, M.stable_baseline_ref, file_path)
local diff_output = utils.exec(diff_cmd)
if not diff_output or diff_output == '' then
-- No differences, remove from tracking
table.insert(files_to_remove, file_path)
cleaned_count = cleaned_count + 1
end
end
-- Remove files with no differences
for _, file_path in ipairs(files_to_remove) do
M.claude_edited_files[file_path] = nil
end
-- Save updated state if we have a persistence stash ref
if persistence.current_stash_ref then
persistence.save_state({ stash_ref = persistence.current_stash_ref })
end
vim.notify(string.format('Cleaned %d stale tracked files', cleaned_count), vim.log.levels.INFO)
end, {
desc = 'Clean up stale Claude file tracking',
})
vim.api.nvim_create_user_command('ClaudeUntrackFile', function()
-- Remove current file from Claude tracking
local utils = require 'nvim-claude.utils'
local git_root = utils.get_project_root()
if not git_root then
vim.notify('Not in a git repository', vim.log.levels.ERROR)
return
end
local file_path = vim.api.nvim_buf_get_name(0)
local relative_path = file_path:gsub(git_root .. '/', '')
if M.claude_edited_files[relative_path] then
M.claude_edited_files[relative_path] = nil
vim.notify('Removed ' .. relative_path .. ' from Claude tracking', vim.log.levels.INFO)
-- Also close any active inline diff for this buffer
local inline_diff = require 'nvim-claude.inline-diff'
local bufnr = vim.api.nvim_get_current_buf()
if inline_diff.has_active_diff(bufnr) then
inline_diff.close_inline_diff(bufnr)
end
else
vim.notify(relative_path .. ' is not in Claude tracking', vim.log.levels.INFO)
end
end, {
desc = 'Remove current file from Claude edited files tracking',
})
end end
-- Cleanup old temp files (no longer cleans up commits) -- Cleanup old temp files (no longer cleans up commits)

View File

@ -24,17 +24,23 @@ function M.save_state(diff_data)
-- } -- }
local hooks = require('nvim-claude.hooks') local hooks = require('nvim-claude.hooks')
local inline_diff = require('nvim-claude.inline-diff')
local state = { local state = {
version = 1, version = 1,
timestamp = os.time(), timestamp = os.time(),
stash_ref = diff_data.stash_ref, stash_ref = diff_data.stash_ref,
claude_edited_files = hooks.claude_edited_files or {}, claude_edited_files = hooks.claude_edited_files or {},
diff_files = {}, -- Add diff_files to persistence
files = {} files = {}
} }
-- Save all diff files (both opened and unopened)
for file_path, bufnr in pairs(inline_diff.diff_files) do
state.diff_files[file_path] = bufnr
end
-- Collect state from all buffers with active diffs -- Collect state from all buffers with active diffs
local inline_diff = require('nvim-claude.inline-diff')
for file_path, bufnr in pairs(inline_diff.diff_files) do for file_path, bufnr in pairs(inline_diff.diff_files) do
if inline_diff.active_diffs[bufnr] then if inline_diff.active_diffs[bufnr] then
local diff = inline_diff.active_diffs[bufnr] local diff = inline_diff.active_diffs[bufnr]
@ -151,10 +157,45 @@ function M.restore_diffs()
-- Store the stash reference for future operations -- Store the stash reference for future operations
M.current_stash_ref = state.stash_ref M.current_stash_ref = state.stash_ref
if M.current_stash_ref then
vim.notify('Restored stash reference: ' .. M.current_stash_ref, vim.log.levels.DEBUG)
end
-- Restore Claude edited files tracking -- Restore Claude edited files tracking
if state.claude_edited_files then if state.claude_edited_files then
local hooks = require('nvim-claude.hooks') local hooks = require('nvim-claude.hooks')
hooks.claude_edited_files = state.claude_edited_files hooks.claude_edited_files = state.claude_edited_files
vim.notify(string.format('Restored %d Claude edited files', vim.tbl_count(state.claude_edited_files)), vim.log.levels.DEBUG)
end
-- Restore diff_files for unopened files
if state.diff_files then
for file_path, bufnr in pairs(state.diff_files) do
-- Only restore if not already restored as an active diff
if not inline_diff.diff_files[file_path] then
-- Use -1 to indicate unopened file
inline_diff.diff_files[file_path] = bufnr == -1 and -1 or -1
end
end
vim.notify(string.format('Restored %d diff files', vim.tbl_count(state.diff_files)), vim.log.levels.DEBUG)
end
-- Also populate diff_files from claude_edited_files if needed
-- This ensures <leader>ci works even if diff_files wasn't properly saved
if state.claude_edited_files then
local utils = require('nvim-claude.utils')
local git_root = utils.get_project_root()
if git_root then
for relative_path, _ in pairs(state.claude_edited_files) do
local full_path = git_root .. '/' .. relative_path
-- Only add if not already in diff_files
if not inline_diff.diff_files[full_path] then
inline_diff.diff_files[full_path] = -1 -- Mark as unopened
vim.notify('Added ' .. relative_path .. ' to diff_files from claude_edited_files', vim.log.levels.DEBUG)
end
end
end
end end
return true return true

View File

@ -369,137 +369,19 @@ function M.generate_hunk_patch(hunk, file_path)
return table.concat(patch_lines, '\n') return table.concat(patch_lines, '\n')
end end
-- Apply a hunk to the baseline using git patches -- Simplified approach: update baseline in memory only
function M.apply_hunk_to_baseline(bufnr, hunk_idx, action) -- The complex git stash approach was causing issues
local utils = require('nvim-claude.utils') function M.update_file_baseline(bufnr)
local hooks = require('nvim-claude.hooks') -- Simply update the in-memory baseline to current buffer content
local diff_data = M.active_diffs[bufnr] local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local hunk = diff_data.hunks[hunk_idx] local current_content = table.concat(current_lines, '\n')
M.original_content[bufnr] = current_content
-- Get file paths -- Save state for persistence
local git_root = utils.get_project_root()
local file_path = vim.api.nvim_buf_get_name(bufnr)
local relative_path = file_path:gsub(git_root .. '/', '')
-- Get current baseline
local persistence = require('nvim-claude.inline-diff-persistence') local persistence = require('nvim-claude.inline-diff-persistence')
local stash_ref = hooks.stable_baseline_ref or persistence.current_stash_ref if persistence.current_stash_ref then
persistence.save_state({ stash_ref = persistence.current_stash_ref })
-- If still no baseline, try to get the most recent nvim-claude baseline from stash list
if not stash_ref then
local stash_list = utils.exec('git stash list | grep "nvim-claude: baseline" | head -1')
if stash_list and stash_list ~= '' then
stash_ref = stash_list:match('^(stash@{%d+})')
if stash_ref then
-- Update both references
hooks.stable_baseline_ref = stash_ref
persistence.current_stash_ref = stash_ref
vim.notify('Using baseline: ' .. stash_ref, vim.log.levels.INFO)
end
end
end end
if not stash_ref then
vim.notify('No baseline stash found', vim.log.levels.ERROR)
return false
end
-- Create temp directory
local temp_dir = vim.fn.tempname()
vim.fn.mkdir(temp_dir, 'p')
-- Extract just the file we need from the stash
local extract_cmd = string.format('cd "%s" && git show %s:%s > "%s/%s"',
git_root, stash_ref, relative_path, temp_dir, relative_path)
-- Create directory structure in temp
local file_dir = vim.fn.fnamemodify(temp_dir .. '/' .. relative_path, ':h')
vim.fn.mkdir(file_dir, 'p')
local _, extract_err = utils.exec(extract_cmd)
if extract_err then
vim.notify('Failed to extract file from baseline: ' .. extract_err, vim.log.levels.ERROR)
vim.fn.delete(temp_dir, 'rf')
return false
end
-- For accept: apply the hunk as-is
-- For reject: apply the hunk in reverse
local patch = M.generate_hunk_patch(hunk, relative_path)
local patch_file = temp_dir .. '/hunk.patch'
utils.write_file(patch_file, patch)
-- Debug: save patch content for inspection
local debug_file = '/tmp/nvim-claude-debug-patch.txt'
utils.write_file(debug_file, patch)
vim.notify('Patch saved to: ' .. debug_file, vim.log.levels.INFO)
-- Apply the patch
local apply_flags = action == 'reject' and '--reverse' or ''
local apply_cmd = string.format('cd "%s" && git apply --verbose %s "%s" 2>&1',
temp_dir, apply_flags, patch_file)
local result, apply_err = utils.exec(apply_cmd)
if apply_err or (result and result:match('error:')) then
vim.notify('Failed to apply patch: ' .. (apply_err or result), vim.log.levels.ERROR)
vim.fn.delete(temp_dir, 'rf')
return false
end
-- Now we need to create a new stash with this modified file
-- First, checkout the baseline into a temp git repo
local work_dir = vim.fn.tempname()
vim.fn.mkdir(work_dir, 'p')
-- Create a work tree from the stash
local worktree_cmd = string.format('cd "%s" && git worktree add --detach "%s" %s 2>&1',
git_root, work_dir, stash_ref)
local _, worktree_err = utils.exec(worktree_cmd)
if worktree_err then
vim.notify('Failed to create worktree: ' .. worktree_err, vim.log.levels.ERROR)
vim.fn.delete(temp_dir, 'rf')
vim.fn.delete(work_dir, 'rf')
return false
end
-- Copy the patched file to the worktree
local copy_cmd = string.format('cp "%s/%s" "%s/%s"', temp_dir, relative_path, work_dir, relative_path)
utils.exec(copy_cmd)
-- Stage and create a new stash
local stage_cmd = string.format('cd "%s" && git add "%s"', work_dir, relative_path)
utils.exec(stage_cmd)
-- Create a new stash
local stash_cmd = string.format('cd "%s" && git stash create', work_dir)
local new_stash, stash_err = utils.exec(stash_cmd)
if stash_err or not new_stash or new_stash == '' then
vim.notify('Failed to create new stash', vim.log.levels.ERROR)
else
new_stash = new_stash:gsub('%s+', '')
-- Store the new stash
local store_cmd = string.format('cd "%s" && git stash store -m "nvim-claude-baseline-hunk-%s" %s',
git_root, action, new_stash)
utils.exec(store_cmd)
-- Update the baseline reference
hooks.stable_baseline_ref = new_stash
persistence.current_stash_ref = new_stash
vim.notify('Updated baseline to: ' .. new_stash, vim.log.levels.INFO)
end
-- Clean up worktree
local cleanup_cmd = string.format('cd "%s" && git worktree remove --force "%s"', git_root, work_dir)
utils.exec(cleanup_cmd)
-- Clean up temp files
vim.fn.delete(temp_dir, 'rf')
vim.fn.delete(work_dir, 'rf')
return true
end end
-- Accept current hunk -- Accept current hunk
@ -513,55 +395,39 @@ function M.accept_current_hunk(bufnr)
vim.notify(string.format('Accepting hunk %d/%d', hunk_idx, #diff_data.hunks), vim.log.levels.INFO) vim.notify(string.format('Accepting hunk %d/%d', hunk_idx, #diff_data.hunks), vim.log.levels.INFO)
-- Debug: Show current baseline -- Mark this hunk as accepted
local hooks = require('nvim-claude.hooks') diff_data.applied_hunks[hunk_idx] = 'accepted'
local persistence = require('nvim-claude.inline-diff-persistence')
vim.notify('Current baseline: ' .. (hooks.stable_baseline_ref or persistence.current_stash_ref or 'none'), vim.log.levels.INFO)
-- Accept = keep current state, update baseline -- Get current buffer content as the new baseline for this file
M.apply_hunk_to_baseline(bufnr, hunk_idx, 'accept') local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local current_content = table.concat(current_lines, '\n')
-- Recalculate diff against new baseline -- Update the in-memory baseline to current content
local utils = require('nvim-claude.utils') M.original_content[bufnr] = current_content
local hooks = require('nvim-claude.hooks')
local git_root = utils.get_project_root()
local file_path = vim.api.nvim_buf_get_name(bufnr)
local relative_path = file_path:gsub(git_root .. '/', '')
-- Get the new baseline content -- Recalculate diff
local persistence = require('nvim-claude.inline-diff-persistence') local new_diff_data = M.compute_diff(current_content, current_content)
local stash_ref = hooks.stable_baseline_ref or persistence.current_stash_ref
if not stash_ref then
vim.notify('No baseline found for recalculation', vim.log.levels.ERROR)
return
end
local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, stash_ref, relative_path) if not new_diff_data or #new_diff_data.hunks == 0 then
local new_baseline = utils.exec(baseline_cmd) -- All changes accepted for this file
vim.notify('All changes accepted. Closing inline diff.', vim.log.levels.INFO)
if new_baseline then -- Remove this file from Claude edited files tracking
M.original_content[bufnr] = new_baseline local utils = require('nvim-claude.utils')
local hooks = require('nvim-claude.hooks')
local git_root = utils.get_project_root()
local file_path = vim.api.nvim_buf_get_name(bufnr)
local relative_path = file_path:gsub(git_root .. '/', '')
-- Get current content if hooks.claude_edited_files[relative_path] then
local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) hooks.claude_edited_files[relative_path] = nil
local current_content = table.concat(current_lines, '\n') vim.notify('Removed ' .. relative_path .. ' from Claude tracking', vim.log.levels.DEBUG)
-- Recalculate diff
local new_diff_data = M.compute_diff(new_baseline, current_content)
if not new_diff_data or #new_diff_data.hunks == 0 then
vim.notify('All changes accepted. Closing inline diff.', vim.log.levels.INFO)
M.close_inline_diff(bufnr, true)
else
-- Update diff data
diff_data.hunks = new_diff_data.hunks
diff_data.current_hunk = 1
-- Refresh visualization
M.apply_diff_visualization(bufnr)
M.jump_to_hunk(bufnr, 1)
vim.notify(string.format('%d hunks remaining', #new_diff_data.hunks), vim.log.levels.INFO)
end end
M.close_inline_diff(bufnr, true)
else
-- This shouldn't happen when accepting current state
vim.notify('Unexpected diff after accepting hunk', vim.log.levels.WARN)
end end
end end
@ -821,9 +687,24 @@ function M.accept_all_hunks(bufnr)
local diff_data = M.active_diffs[bufnr] local diff_data = M.active_diffs[bufnr]
if not diff_data then return end if not diff_data then return end
-- Replace buffer with new content -- Get current buffer content as the new baseline
local new_lines = vim.split(diff_data.new_content, '\n') local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, new_lines) local current_content = table.concat(current_lines, '\n')
-- Update the in-memory baseline to current content
M.original_content[bufnr] = current_content
-- Remove this file from Claude edited files tracking
local utils = require('nvim-claude.utils')
local hooks = require('nvim-claude.hooks')
local git_root = utils.get_project_root()
local file_path = vim.api.nvim_buf_get_name(bufnr)
local relative_path = file_path:gsub(git_root .. '/', '')
if hooks.claude_edited_files[relative_path] then
hooks.claude_edited_files[relative_path] = nil
vim.notify('Removed ' .. relative_path .. ' from Claude tracking', vim.log.levels.DEBUG)
end
vim.notify('Accepted all Claude changes', vim.log.levels.INFO) vim.notify('Accepted all Claude changes', vim.log.levels.INFO)
@ -875,14 +756,23 @@ function M.close_inline_diff(bufnr, keep_baseline)
end end
end end
-- If no more active diffs, clear persistence state and reset baseline -- Also check if there are still Claude-edited files that haven't been opened yet
if not has_active_diffs then local hooks = require('nvim-claude.hooks')
local has_tracked_files = false
for _, tracked in pairs(hooks.claude_edited_files) do
if tracked then
has_tracked_files = true
break
end
end
-- Only clear everything if no active diffs AND no tracked files
if not has_active_diffs and not has_tracked_files then
local persistence = require('nvim-claude.inline-diff-persistence') local persistence = require('nvim-claude.inline-diff-persistence')
persistence.clear_state() persistence.clear_state()
persistence.current_stash_ref = nil persistence.current_stash_ref = nil
-- Reset the stable baseline in hooks -- Reset the stable baseline in hooks
local hooks = require('nvim-claude.hooks')
hooks.stable_baseline_ref = nil hooks.stable_baseline_ref = nil
hooks.claude_edited_files = {} hooks.claude_edited_files = {}
end end
@ -1072,4 +962,39 @@ function M.list_diff_files()
end) end)
end end
-- Accept all diffs across all files
function M.accept_all_files()
local hooks = require('nvim-claude.hooks')
local persistence = require('nvim-claude.inline-diff-persistence')
-- Count tracked files for reporting
local cleared_count = vim.tbl_count(hooks.claude_edited_files)
if cleared_count == 0 then
vim.notify('No Claude edits to accept', vim.log.levels.INFO)
return
end
-- Clear all visual diff displays from all buffers
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr) then
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1)
end
end
-- Clear all diff state
M.diff_files = {}
M.original_content = {}
M.active_diffs = {}
-- Clear all tracking
hooks.claude_edited_files = {}
hooks.stable_baseline_ref = nil
-- Clear persistence
persistence.clear_state()
vim.notify(string.format('Accepted all changes from %d files', cleared_count), vim.log.levels.INFO)
end
return M return M

View File

@ -60,6 +60,16 @@ function M.setup(config, commands)
k = { 'Kill Agent' }, k = { 'Kill Agent' },
x = { 'Clean Old Agents' }, x = { 'Clean Old Agents' },
i = { 'List files with diffs' }, i = { 'List files with diffs' },
},
['<leader>i'] = {
name = 'Inline Diffs',
a = { 'Accept current hunk' },
r = { 'Reject current hunk' },
A = { 'Accept all hunks in file' },
R = { 'Reject all hunks in file' },
AA = { 'Accept ALL diffs in ALL files' },
q = { 'Close inline diff' },
l = { 'List files with diffs' },
} }
}) })
end end
@ -89,6 +99,15 @@ function M.setup(config, commands)
desc = 'List files with Claude diffs', desc = 'List files with Claude diffs',
silent = true silent = true
}) })
-- Global keymap to accept all diffs across all files
vim.keymap.set('n', '<leader>iAA', function()
local inline_diff = require('nvim-claude.inline-diff')
inline_diff.accept_all_files()
end, {
desc = 'Accept ALL Claude diffs in ALL files',
silent = true
})
end end
return M return M

View File

@ -1,4 +1,5 @@
-- Utility functions for nvim-claude -- Utility functions for nvim-claude
-- TEST EDIT #2: Testing multi-file accept
local M = {} local M = {}
-- Check if we're in a git repository -- Check if we're in a git repository

188
tasks.md
View File

@ -23,22 +23,22 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
- [x] Test: Plugin loads without errors - [x] Test: Plugin loads without errors
#### 1.2 Configuration Management #### 1.2 Configuration Management
- [ ] Define default configuration schema - [x] Define default configuration schema
- [ ] Implement config validation - [x] Implement config validation
- [ ] Support user config overrides - [x] Support user config overrides
- [ ] Add config options for: - [x] Add config options for:
- [ ] Tmux pane split direction and size - [x] Tmux pane split direction and size
- [ ] Agent work directory name - [x] Agent work directory name
- [ ] Git worktree vs full clone preference - [x] Git worktree vs full clone preference
- [ ] Auto-gitignore behavior - [x] Auto-gitignore behavior
- [ ] Test: Config changes apply correctly - [x] Test: Config changes apply correctly
#### 1.3 Utility Functions #### 1.3 Utility Functions
- [x] Create tmux interaction module - [x] Create tmux interaction module
- [x] Add git operations wrapper (worktree, status, diff) - [x] Add git operations wrapper (worktree, status, diff)
- [x] Implement filesystem utilities (create dirs, check paths) - [x] Implement filesystem utilities (create dirs, check paths)
- [ ] Add logging/debugging functions - [x] Add logging/debugging functions (vim.notify)
- [ ] Test: Each utility function in isolation - [x] Test: Each utility function in isolation
### 2. Basic Claude Chat Integration ### 2. Basic Claude Chat Integration
@ -53,49 +53,85 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
- [x] Implement `:ClaudeChat` command - [x] Implement `:ClaudeChat` command
- [x] Open Claude in configured tmux split - [x] Open Claude in configured tmux split
- [x] Reuse existing pane if available - [x] Reuse existing pane if available
- [ ] Pass current file context if requested - [x] Pass current file context if requested
- [ ] Test: Command opens Claude reliably - [x] Test: Command opens Claude reliably
#### 2.3 Context Sharing #### 2.3 Context Sharing
- [x] `:ClaudeSendBuffer` - Send current buffer - [x] `:ClaudeSendBuffer` - Send current buffer
- [x] `:ClaudeSendSelection` - Send visual selection - [x] `:ClaudeSendSelection` - Send visual selection
- [ ] `:ClaudeSendHunk` - Send git hunk under cursor - [x] `:ClaudeSendHunk` - Send git hunk under cursor
- [x] Add appropriate context headers - [x] Add appropriate context headers
- [ ] Test: Each send command works correctly - [x] Test: Each send command works correctly
### 3. Background Agent System ### 3. Background Agent System
#### 3.1 Agent Work Directory Management #### 3.1 Agent Work Directory Management
- [ ] Create `.agent-work/` in project root - [x] Create `.agent-work/` in project root
- [ ] Auto-add to `.gitignore` if not present - [x] Auto-add to `.gitignore` if not present
- [ ] Generate timestamped subdirectories - [x] Generate timestamped subdirectories
- [ ] Clean up old agent directories (configurable) - [x] Clean up old agent directories (configurable)
- [ ] Test: Directory creation and gitignore updates - [x] Test: Directory creation and gitignore updates
#### 3.2 Git Worktree Integration #### 3.2 Git Worktree Integration
- [ ] Function to create worktree for agent - [x] Function to create worktree for agent
- [ ] Handle worktree naming (avoid conflicts) - [x] Handle worktree naming (avoid conflicts)
- [ ] Support fallback to full clone if worktrees unavailable - [x] Support fallback to full clone if worktrees unavailable
- [ ] Track worktree <-> agent mapping - [x] Track worktree <-> agent mapping
- [ ] Test: Worktree creation and tracking - [x] Test: Worktree creation and tracking
#### 3.3 Agent Spawning #### 3.3 Agent Spawning
- [x] Implement `:ClaudeBg <task>` command - [x] Implement `:ClaudeBg <task>` command
- [x] Create agent work directory - [x] Create agent work directory
- [x] Set up git worktree - [x] Set up git worktree
- [x] Spawn tmux window/session for agent - [x] Spawn tmux window/session for agent
- [ ] Initialize Claude with task context - [x] Initialize Claude with task context
- [x] Create mission log file - [x] Create mission log file
- [ ] Test: Full agent spawn workflow - [x] Test: Full agent spawn workflow
#### 3.4 Agent Tracking #### 3.4 Agent Tracking
- [ ] Maintain registry of active agents - [x] Maintain registry of active agents
- [ ] Store agent metadata (task, start time, status) - [x] Store agent metadata (task, start time, status)
- [ ] Persist registry across nvim sessions - [x] Persist registry across nvim sessions
- [ ] Auto-detect terminated agents - [x] Auto-detect terminated agents
- [ ] Test: Registry operations and persistence - [x] Test: Registry operations and persistence
### 4. Diff Viewing and Review ### 4. Inline Diff System (NEW - Major Feature)
#### 4.1 Claude Code Hooks Integration
- [x] Pre-tool-use hook for baseline creation
- [x] Post-tool-use hook for diff detection
- [x] Automatic inline diff display for open buffers
- [x] Claude file edit tracking
- [x] Git stash-based baseline management
#### 4.2 Inline Diff Display
- [x] Real-time diff visualization in buffers
- [x] Syntax highlighting for additions/deletions
- [x] Virtual text for removed lines
- [x] Hunk navigation (]h, [h)
- [x] Multi-file diff tracking
#### 4.3 Diff Actions
- [x] Accept hunk (<leader>ia)
- [x] Reject hunk (<leader>ir)
- [x] Accept all hunks (<leader>iA)
- [x] Reject all hunks (<leader>iR)
- [x] Close inline diff (<leader>iq)
#### 4.4 Multi-File Navigation
- [x] Navigate between files with diffs (]f, [f)
- [x] List all files with diffs (<leader>ci)
- [x] Telescope-like file picker
- [x] Show diff status for unopened files
- [x] Auto-show diffs when opening Claude-edited files
#### 4.5 Persistence and State
- [x] Save diff state across Neovim sessions
- [x] Restore inline diffs on startup
- [x] Track baseline references
- [x] Clean state management
### 5. Diff Viewing and Review (Original git-based)
#### 4.1 Fugitive Integration #### 4.1 Fugitive Integration
- [ ] Function to diff agent worktree against main - [ ] Function to diff agent worktree against main
@ -117,7 +153,7 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
- [ ] Bulk apply agent changes - [ ] Bulk apply agent changes
- [ ] Test: Full review workflow - [ ] Test: Full review workflow
### 5. Telescope Integration ### 6. Telescope Integration
#### 5.1 Agent Browser #### 5.1 Agent Browser
- [ ] Custom Telescope picker for agents - [ ] Custom Telescope picker for agents
@ -132,7 +168,7 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
- [ ] Open files from agent in main nvim - [ ] Open files from agent in main nvim
- [ ] Test: File browsing and operations - [ ] Test: File browsing and operations
### 6. Status and Monitoring ### 7. Status and Monitoring
#### 6.1 Status Line Integration #### 6.1 Status Line Integration
- [ ] Component showing active agent count - [ ] Component showing active agent count
@ -153,19 +189,21 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
- [ ] Optional progress notifications - [ ] Optional progress notifications
- [ ] Test: Notification system - [ ] Test: Notification system
### 7. Safety and Convenience Features ### 8. Safety and Convenience Features
#### 7.1 Snapshot System #### 8.1 Snapshot System
- [ ] Auto-snapshot before applying changes - [ ] Auto-snapshot before applying changes
- [ ] Named snapshots for important states - [ ] Named snapshots for important states
- [ ] Snapshot browser/restore - [ ] Snapshot browser/restore
- [ ] Test: Snapshot and restore - [ ] Test: Snapshot and restore
#### 7.2 Quick Commands #### 8.2 Quick Commands
- [ ] `:ClaudeKill [agent]` - Terminate agent - [x] `:ClaudeKill [agent]` - Terminate agent
- [ ] `:ClaudeClean` - Clean up old agents - [x] `:ClaudeClean` - Clean up old agents
- [ ] `:ClaudeSwitch [agent]` - Switch to agent tmux - [ ] `:ClaudeSwitch [agent]` - Switch to agent tmux
- [ ] Test: Each command functions correctly - [x] `:ClaudeAgents` - List all agents
- [x] `:ClaudeResetBaseline` - Reset inline diff baseline
- [x] Test: Each command functions correctly
#### 7.3 Keybindings #### 7.3 Keybindings
- [ ] Default keybinding set - [ ] Default keybinding set
@ -173,7 +211,7 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
- [ ] User-customizable bindings - [ ] User-customizable bindings
- [ ] Test: Keybindings work as expected - [ ] Test: Keybindings work as expected
### 8. Advanced Features (Phase 2) ### 9. Advanced Features (Phase 2)
#### 8.1 Agent Templates #### 8.1 Agent Templates
- [ ] Predefined agent configurations - [ ] Predefined agent configurations
@ -193,30 +231,27 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
- [ ] Oil.nvim for agent file management - [ ] Oil.nvim for agent file management
- [ ] Test: Each integration - [ ] Test: Each integration
## Implementation Phases ## Implementation Status
### Phase 1: MVP (Week 1-2) ### Completed Features
1. Core infrastructure (1.1-1.3) 1. ✅ Core infrastructure - Plugin setup, config, utilities
2. Basic Claude chat (2.1-2.3) 2. ✅ Claude chat integration - All context sharing commands working
3. Simple agent spawning (3.1-3.3) 3. ✅ Background agent system - Full implementation with registry
4. Basic diff viewing (4.1) 4. ✅ Inline diff system - Major feature addition with full accept/reject workflow
5. ✅ Multi-file diff navigation - Complete with keybindings
6. ✅ Claude Code hooks - Automatic diff detection and display
7. ✅ Persistence - Diff state saved across sessions
### Phase 2: Core Features (Week 3-4) ### In Progress
1. Agent tracking (3.4) 1. 🔄 Telescope integration for agents
2. Full diff/review workflow (4.2-4.3) 2. 🔄 Status line integration
3. Telescope integration (5.1-5.2) 3. 🔄 Advanced diff viewing (git-based)
4. Basic monitoring (6.1-6.2)
### Phase 3: Polish (Week 5-6) ### Future Work
1. Safety features (7.1-7.2) 1. 📋 Agent templates and behaviors
2. Keybindings and UX (7.3) 2. 📋 Multi-agent coordination
3. Notifications (6.3) 3. 📋 Deep plugin integrations
4. Documentation and tests 4. 📋 Snapshot system
### Phase 4: Advanced (Future)
1. Agent templates (8.1)
2. Multi-agent features (8.2)
3. Deep plugin integrations (8.3)
## Technical Decisions ## Technical Decisions
@ -257,5 +292,28 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
--- ---
*Last Updated: [Current Date]* *Last Updated: 2025-01-10*
*Status: Planning Phase* *Status: Feature Complete for v1.0*
## Recent Accomplishments
### Inline Diff System (Major Feature)
- Complete implementation of inline diff visualization
- Real-time accept/reject functionality for individual hunks
- Multi-file navigation and management
- Persistence across Neovim sessions
- Integration with Claude Code hooks for automatic detection
### Background Agents
- Full agent spawning and management system
- Git worktree integration
- Agent registry with persistence
- Mission logging and tracking
### Next Steps for v1.1
- Polish Telescope integration for agent browsing
- Add status line components showing active diffs
- Improve documentation with examples
- Create demo videos showcasing inline diff system
- Add support for partial hunk acceptance
- TEST EDIT: Testing single file edit after accept all