claude-baseline-1752125979

This commit is contained in:
zolinthecow 2025-07-09 22:39:39 -07:00
parent 3310fe0901
commit 2c74a4bba7
2 changed files with 320 additions and 152 deletions

View File

@ -18,10 +18,10 @@ function M.pre_tool_use_hook()
vim.notify('Pre-hook called', vim.log.levels.INFO) vim.notify('Pre-hook called', vim.log.levels.INFO)
-- Debug log -- Debug log
local debug_msg = string.format('Pre-hook executing at %s', os.date('%Y-%m-%d %H:%M:%S')) local debug_msg = string.format('Pre-hook executing at %s', os.date '%Y-%m-%d %H:%M:%S')
vim.fn.writefile({ debug_msg }, '/tmp/claude-pre-hook-debug.log', 'a') vim.fn.writefile({ debug_msg }, '/tmp/claude-pre-hook-debug.log', 'a')
local utils = require('nvim-claude.utils') local utils = require 'nvim-claude.utils'
-- Check if we're in a git repository -- Check if we're in a git repository
local git_root = utils.get_project_root() local git_root = utils.get_project_root()
@ -63,7 +63,7 @@ function M.pre_tool_use_hook()
local commit_cmd = string.format('cd "%s" && git commit -m "%s" --allow-empty', git_root, commit_msg) local commit_cmd = string.format('cd "%s" && git commit -m "%s" --allow-empty', git_root, commit_msg)
local commit_result, commit_err = utils.exec(commit_cmd) local commit_result, commit_err = utils.exec(commit_cmd)
if commit_err and not commit_err:match('nothing to commit') then if commit_err and not commit_err:match 'nothing to commit' then
vim.notify('Failed to create baseline commit: ' .. commit_err, vim.log.levels.ERROR) vim.notify('Failed to create baseline commit: ' .. commit_err, vim.log.levels.ERROR)
return return
end end
@ -89,17 +89,17 @@ function M.post_tool_use_hook()
vim.notify('Post-hook called', vim.log.levels.INFO) vim.notify('Post-hook called', vim.log.levels.INFO)
-- Debug log -- Debug log
local debug_msg = string.format('Post-hook executing at %s', os.date('%Y-%m-%d %H:%M:%S')) local debug_msg = string.format('Post-hook executing at %s', os.date '%Y-%m-%d %H:%M:%S')
vim.fn.writefile({ debug_msg }, '/tmp/claude-post-hook-debug.log', 'a') vim.fn.writefile({ debug_msg }, '/tmp/claude-post-hook-debug.log', 'a')
vim.notify('Post-tool-use hook triggered', vim.log.levels.INFO) vim.notify('Post-tool-use hook triggered', vim.log.levels.INFO)
-- Use vim.schedule to ensure we're in the main thread -- Use vim.schedule to ensure we're in the main thread
vim.schedule(function() vim.schedule(function()
local utils = require('nvim-claude.utils') local utils = require 'nvim-claude.utils'
-- Refresh all buffers to show Claude's changes -- Refresh all buffers to show Claude's changes
vim.cmd('checktime') vim.cmd 'checktime'
-- Check if Claude made any changes -- Check if Claude made any changes
local git_root = utils.get_project_root() local git_root = utils.get_project_root()
@ -118,15 +118,15 @@ function M.post_tool_use_hook()
-- Get list of modified files -- Get list of modified files
local modified_files = {} local modified_files = {}
for line in status_result:gmatch('[^\n]+') do for line in status_result:gmatch '[^\n]+' do
local file = line:match('^.M (.+)$') or line:match('^M. (.+)$') or line:match('^.. (.+)$') local file = line:match '^.M (.+)$' or line:match '^M. (.+)$' or line:match '^.. (.+)$'
if file then if file then
table.insert(modified_files, file) table.insert(modified_files, file)
end end
end end
-- Create a stash of Claude's changes (but keep them in working directory) -- Create a stash of Claude's changes (but keep them in working directory)
local timestamp = os.date('%Y-%m-%d %H:%M:%S') local timestamp = os.date '%Y-%m-%d %H:%M:%S'
local stash_msg = string.format('[claude-edit] %s', timestamp) local stash_msg = string.format('[claude-edit] %s', timestamp)
-- Use git stash create to create stash without removing changes -- Use git stash create to create stash without removing changes
@ -140,13 +140,13 @@ function M.post_tool_use_hook()
utils.exec(store_cmd) utils.exec(store_cmd)
-- Get the baseline commit reference -- Get the baseline commit reference
local baseline_ref = utils.read_file('/tmp/claude-baseline-commit') local baseline_ref = utils.read_file '/tmp/claude-baseline-commit'
if baseline_ref then if baseline_ref then
baseline_ref = baseline_ref:gsub('%s+', '') -- trim whitespace baseline_ref = baseline_ref:gsub('%s+', '') -- trim whitespace
end end
-- Check if any modified files are currently open in buffers -- Check if any modified files are currently open in buffers
local inline_diff = require('nvim-claude.inline-diff') local inline_diff = require 'nvim-claude.inline-diff'
local opened_inline = false local opened_inline = false
for _, file in ipairs(modified_files) do for _, file in ipairs(modified_files) do
@ -181,7 +181,9 @@ function M.post_tool_use_hook()
end end
end end
if opened_inline then break end if opened_inline then
break
end
end end
-- If no inline diff was shown, fall back to regular diff review -- If no inline diff was shown, fall back to regular diff review
@ -200,6 +202,52 @@ function M.post_tool_use_hook()
end) end)
end end
-- Test inline diff manually
function M.test_inline_diff()
vim.notify('Testing inline diff manually...', vim.log.levels.INFO)
local utils = require 'nvim-claude.utils'
local git_root = utils.get_project_root()
if not git_root then
vim.notify('Not in git repository', vim.log.levels.ERROR)
return
end
-- Get current buffer
local bufnr = vim.api.nvim_get_current_buf()
local buf_name = vim.api.nvim_buf_get_name(bufnr)
if buf_name == '' then
vim.notify('Current buffer has no file', vim.log.levels.ERROR)
return
end
-- Get relative path
local relative_path = buf_name:gsub(git_root .. '/', '')
vim.notify('Testing inline diff for: ' .. relative_path, vim.log.levels.INFO)
-- Get baseline content
local baseline_ref = utils.read_file '/tmp/claude-baseline-commit' or 'HEAD'
baseline_ref = baseline_ref:gsub('%s+', '')
local baseline_cmd = string.format('cd "%s" && git show %s:%s 2>/dev/null', git_root, baseline_ref, relative_path)
local original_content, orig_err = utils.exec(baseline_cmd)
if orig_err then
vim.notify('Failed to get baseline content: ' .. orig_err, vim.log.levels.ERROR)
return
end
-- Get current content
local current_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local current_content = table.concat(current_lines, '\n')
-- Show inline diff
local inline_diff = require 'nvim-claude.inline-diff'
inline_diff.show_inline_diff(bufnr, original_content, current_content)
end
-- Manual hook testing -- Manual hook testing
function M.test_hooks() function M.test_hooks()
vim.notify('=== Testing nvim-claude hooks ===', vim.log.levels.INFO) vim.notify('=== Testing nvim-claude hooks ===', vim.log.levels.INFO)
@ -223,7 +271,7 @@ end
-- Install Claude Code hooks -- Install Claude Code hooks
function M.install_hooks() function M.install_hooks()
local utils = require('nvim-claude.utils') local utils = require 'nvim-claude.utils'
-- Get project root -- Get project root
local project_root = utils.get_project_root() local project_root = utils.get_project_root()
@ -240,34 +288,40 @@ function M.install_hooks()
-- Create hooks configuration -- Create hooks configuration
local server_name = vim.v.servername or 'NVIM' local server_name = vim.v.servername or 'NVIM'
local pre_command = string.format('nvim --headless --server %s --remote-send "<C-\\><C-N>:lua require(\'nvim-claude.hooks\').pre_tool_use_hook()<CR>" 2>/dev/null || true', server_name) local pre_command = string.format(
local post_command = string.format('nvim --headless --server %s --remote-send "<C-\\><C-N>:lua require(\'nvim-claude.hooks\').post_tool_use_hook()<CR>" 2>/dev/null || true', server_name) 'nvim --headless --server %s --remote-send "<C-\\><C-N>:lua require(\'nvim-claude.hooks\').pre_tool_use_hook()<CR>" 2>/dev/null || true',
server_name
)
local post_command = string.format(
'nvim --headless --server %s --remote-send "<C-\\><C-N>:lua require(\'nvim-claude.hooks\').post_tool_use_hook()<CR>" 2>/dev/null || true',
server_name
)
local hooks_config = { local hooks_config = {
hooks = { hooks = {
PreToolUse = { PreToolUse = {
{ {
matcher = "Edit|Write|MultiEdit", -- Only match file editing tools matcher = 'Edit|Write|MultiEdit', -- Only match file editing tools
hooks = { hooks = {
{ {
type = "command", type = 'command',
command = pre_command command = pre_command,
} },
} },
} },
}, },
PostToolUse = { PostToolUse = {
{ {
matcher = "Edit|Write|MultiEdit", -- Only match file editing tools matcher = 'Edit|Write|MultiEdit', -- Only match file editing tools
hooks = { hooks = {
{ {
type = "command", type = 'command',
command = post_command command = post_command,
} },
} },
} },
} },
} },
} }
-- Write hooks configuration -- Write hooks configuration
@ -279,7 +333,7 @@ function M.install_hooks()
local gitignore_path = project_root .. '/.gitignore' local gitignore_path = project_root .. '/.gitignore'
local gitignore_content = utils.read_file(gitignore_path) or '' local gitignore_content = utils.read_file(gitignore_path) or ''
if not gitignore_content:match('%.claude/') then if not gitignore_content:match '%.claude/' then
local new_content = gitignore_content .. '\n# Claude Code hooks\n.claude/\n' local new_content = gitignore_content .. '\n# Claude Code hooks\n.claude/\n'
utils.write_file(gitignore_path, new_content) utils.write_file(gitignore_path, new_content)
vim.notify('Added .claude/ to .gitignore', vim.log.levels.INFO) vim.notify('Added .claude/ to .gitignore', vim.log.levels.INFO)
@ -294,7 +348,7 @@ end
-- Uninstall Claude Code hooks -- Uninstall Claude Code hooks
function M.uninstall_hooks() function M.uninstall_hooks()
local utils = require('nvim-claude.utils') local utils = require 'nvim-claude.utils'
-- Get project root -- Get project root
local project_root = utils.get_project_root() local project_root = utils.get_project_root()
@ -318,11 +372,23 @@ function M.setup_commands()
vim.api.nvim_create_user_command('ClaudeTestHooks', function() vim.api.nvim_create_user_command('ClaudeTestHooks', function()
M.test_hooks() M.test_hooks()
end, { end, {
desc = 'Test Claude Code hooks' desc = 'Test Claude Code hooks',
})
vim.api.nvim_create_user_command('ClaudeTestInlineDiff', function()
M.test_inline_diff()
end, {
desc = 'Test Claude inline diff manually',
})
vim.api.nvim_create_user_command('ClaudeTestKeymap', function()
require('nvim-claude.inline-diff').test_keymap()
end, {
desc = 'Test Claude keymap functionality',
}) })
vim.api.nvim_create_user_command('ClaudeTestDiff', function() vim.api.nvim_create_user_command('ClaudeTestDiff', function()
local utils = require('nvim-claude.utils') local utils = require 'nvim-claude.utils'
-- Check if we're in a git repository -- Check if we're in a git repository
local git_root = utils.get_project_root() local git_root = utils.get_project_root()
@ -341,7 +407,7 @@ function M.setup_commands()
end end
-- Create test stash without restoring (to avoid conflicts) -- Create test stash without restoring (to avoid conflicts)
local timestamp = os.date('%Y-%m-%d %H:%M:%S') local timestamp = os.date '%Y-%m-%d %H:%M:%S'
local stash_msg = string.format('[claude-test] %s', timestamp) local stash_msg = string.format('[claude-test] %s', timestamp)
local stash_cmd = string.format('cd "%s" && git stash push -u -m "%s"', git_root, stash_msg) local stash_cmd = string.format('cd "%s" && git stash push -u -m "%s"', git_root, stash_msg)
@ -353,35 +419,35 @@ function M.setup_commands()
end end
-- Trigger diff review with the stash (no pre-edit ref for manual test) -- Trigger diff review with the stash (no pre-edit ref for manual test)
local diff_review = require('nvim-claude.diff-review') local diff_review = require 'nvim-claude.diff-review'
diff_review.handle_claude_edit('stash@{0}', nil) diff_review.handle_claude_edit('stash@{0}', nil)
-- Pop the stash to restore changes -- Pop the stash to restore changes
vim.defer_fn(function() vim.defer_fn(function()
local pop_cmd = string.format('cd "%s" && git stash pop --quiet', git_root) local pop_cmd = string.format('cd "%s" && git stash pop --quiet', git_root)
utils.exec(pop_cmd) utils.exec(pop_cmd)
vim.cmd('checktime') -- Refresh buffers vim.cmd 'checktime' -- Refresh buffers
end, 100) end, 100)
end, { end, {
desc = 'Test Claude diff review with current changes' desc = 'Test Claude diff review with current changes',
}) })
vim.api.nvim_create_user_command('ClaudeInstallHooks', function() vim.api.nvim_create_user_command('ClaudeInstallHooks', function()
M.install_hooks() M.install_hooks()
end, { end, {
desc = 'Install Claude Code hooks for this project' desc = 'Install Claude Code hooks for this project',
}) })
vim.api.nvim_create_user_command('ClaudeUninstallHooks', function() vim.api.nvim_create_user_command('ClaudeUninstallHooks', function()
M.uninstall_hooks() M.uninstall_hooks()
end, { end, {
desc = 'Uninstall Claude Code hooks for this project' desc = 'Uninstall Claude Code hooks for this project',
}) })
end end
-- Cleanup old Claude commits and temp files -- Cleanup old Claude commits and temp files
function M.cleanup_old_commits() function M.cleanup_old_commits()
local utils = require('nvim-claude.utils') local utils = require 'nvim-claude.utils'
local git_root = utils.get_project_root() local git_root = utils.get_project_root()
if not git_root then if not git_root then
@ -393,7 +459,7 @@ function M.cleanup_old_commits()
'/tmp/claude-pre-edit-commit', '/tmp/claude-pre-edit-commit',
'/tmp/claude-baseline-commit', '/tmp/claude-baseline-commit',
'/tmp/claude-last-snapshot', '/tmp/claude-last-snapshot',
'/tmp/claude-hook-test.log' '/tmp/claude-hook-test.log',
} }
for _, file in ipairs(temp_files) do for _, file in ipairs(temp_files) do
@ -403,13 +469,14 @@ function M.cleanup_old_commits()
end end
-- Clean up old Claude commits (keep only the last 5) -- Clean up old Claude commits (keep only the last 5)
local log_cmd = string.format('cd "%s" && git log --oneline --grep="claude-" --grep="claude-baseline" --grep="claude-pre-edit" --all --max-count=10', git_root) local log_cmd =
string.format('cd "%s" && git log --oneline --grep="claude-" --grep="claude-baseline" --grep="claude-pre-edit" --all --max-count=10', git_root)
local log_result = utils.exec(log_cmd) local log_result = utils.exec(log_cmd)
if log_result and log_result ~= '' then if log_result and log_result ~= '' then
local commits = {} local commits = {}
for line in log_result:gmatch('[^\n]+') do for line in log_result:gmatch '[^\n]+' do
local hash = line:match('^(%w+)') local hash = line:match '^(%w+)'
if hash then if hash then
table.insert(commits, hash) table.insert(commits, hash)
end end
@ -427,3 +494,4 @@ function M.cleanup_old_commits()
end end
return M return M

View File

@ -105,7 +105,7 @@ function M.parse_diff(diff_text)
return hunks return hunks
end end
-- Apply visual indicators for diff -- Apply visual indicators for diff with line highlights
function M.apply_diff_visualization(bufnr) function M.apply_diff_visualization(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
@ -113,58 +113,91 @@ function M.apply_diff_visualization(bufnr)
-- Clear existing highlights -- Clear existing highlights
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1)
-- Get current buffer lines for reference
local buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
-- Apply highlights for each hunk -- Apply highlights for each hunk
for i, hunk in ipairs(diff_data.hunks) do for i, hunk in ipairs(diff_data.hunks) do
local line_num = hunk.old_start - 1 -- 0-indexed -- Track additions and deletions separately with proper line mapping
local current_new_line = hunk.new_start - 1 -- 0-indexed, tracks position in current buffer
-- Track lines to highlight local current_old_line = hunk.old_start - 1 -- 0-indexed, tracks position in old content
local del_lines = {} local deletions = {}
local add_lines = {}
local current_line = line_num
for _, diff_line in ipairs(hunk.lines) do for _, diff_line in ipairs(hunk.lines) do
if diff_line:match('^%-') then if diff_line:match('^%+') then
-- Deletion -- This is an added line - highlight it in the current buffer
table.insert(del_lines, current_line) if current_new_line >= 0 and current_new_line < #buf_lines then
current_line = current_line + 1 vim.api.nvim_buf_set_extmark(bufnr, ns_id, current_new_line, 0, {
elseif diff_line:match('^%+') then line_hl_group = 'DiffAdd',
-- Addition (shown as virtual text) id = 4000 + i * 1000 + current_new_line
table.insert(add_lines, {
line = current_line - 1,
text = diff_line:sub(2)
}) })
end
current_new_line = current_new_line + 1
-- Don't advance old_line for additions
elseif diff_line:match('^%-') then
-- This is a deleted line - show as virtual text above current position
table.insert(deletions, {
line = current_new_line, -- Show deletion above current position
text = diff_line:sub(2),
})
current_old_line = current_old_line + 1
-- Don't advance new_line for deletions
else else
-- Context line -- Context line - advance both
current_line = current_line + 1 current_new_line = current_new_line + 1
current_old_line = current_old_line + 1
end end
end end
-- Apply deletion highlights -- Show deletions as virtual text above their position with full-width background
for _, line in ipairs(del_lines) do for j, del in ipairs(deletions) do
vim.api.nvim_buf_add_highlight(bufnr, ns_id, 'DiffDelete', line, 0, -1) if del.line >= 0 and del.line <= #buf_lines then
end -- Calculate full width for the deletion line
local text = '- ' .. del.text
local win_width = vim.api.nvim_win_get_width(0)
local padding = string.rep(' ', math.max(0, win_width - vim.fn.strdisplaywidth(text)))
-- Add virtual text for additions vim.api.nvim_buf_set_extmark(bufnr, ns_id, del.line, 0, {
for _, add in ipairs(add_lines) do
vim.api.nvim_buf_set_extmark(bufnr, ns_id, add.line, 0, {
virt_lines = {{ virt_lines = {{
{' + ' .. add.text, 'DiffAdd'} {'- ' .. del.text .. padding, 'DiffDelete'}
}},
virt_lines_above = false
})
end
-- Add hunk header as virtual text
vim.api.nvim_buf_set_extmark(bufnr, ns_id, line_num, 0, {
virt_lines = {{
{hunk.header .. ' [Hunk ' .. i .. '/' .. #diff_data.hunks .. ']', 'Comment'}
}}, }},
virt_lines_above = true, virt_lines_above = true,
id = 1000 + i -- Unique ID for hunk headers id = 3000 + i * 100 + j
}) })
end end
end end
-- Add sign in gutter for hunk
local sign_line = hunk.new_start - 1
local sign_text = '>'
local sign_hl = 'DiffAdd'
-- If hunk has deletions, use different sign
if #deletions > 0 then
sign_text = '~'
sign_hl = 'DiffChange'
end
if sign_line >= 0 and sign_line < #buf_lines then
vim.api.nvim_buf_set_extmark(bufnr, ns_id, sign_line, 0, {
sign_text = sign_text,
sign_hl_group = sign_hl,
id = 2000 + i
})
end
-- Add subtle hunk info at end of first line
local info_line = hunk.new_start - 1
if info_line >= 0 and info_line < #buf_lines then
vim.api.nvim_buf_set_extmark(bufnr, ns_id, info_line, 0, {
virt_text = {{' [Hunk ' .. i .. '/' .. #diff_data.hunks .. ']', 'Comment'}},
virt_text_pos = 'eol',
id = 1000 + i
})
end
end
end
-- Set up buffer-local keymaps for inline diff -- Set up buffer-local keymaps for inline diff
function M.setup_inline_keymaps(bufnr) function M.setup_inline_keymaps(bufnr)
local opts = { buffer = bufnr, silent = true } local opts = { buffer = bufnr, silent = true }
@ -261,12 +294,65 @@ function M.reject_current_hunk(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
local hunk = diff_data.hunks[diff_data.current_hunk]
if not hunk then return end
-- Revert the hunk by applying original content
M.revert_hunk_changes(bufnr, hunk)
vim.notify(string.format('Rejected hunk %d/%d', diff_data.current_hunk, #diff_data.hunks), vim.log.levels.INFO) vim.notify(string.format('Rejected hunk %d/%d', diff_data.current_hunk, #diff_data.hunks), vim.log.levels.INFO)
-- Move to next hunk -- Move to next hunk
M.next_hunk(bufnr) M.next_hunk(bufnr)
end end
-- Revert hunk changes (restore original content)
function M.revert_hunk_changes(bufnr, hunk)
-- Get current buffer lines
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local original_content = M.original_content[bufnr]
if not original_content then
vim.notify('No original content available for rejection', vim.log.levels.ERROR)
return
end
-- Split original content into lines
local original_lines = vim.split(original_content, '\n')
-- Build new lines by reverting this hunk
local new_lines = {}
local buffer_line = 1
local applied = false
while buffer_line <= #lines do
if buffer_line >= hunk.new_start and buffer_line < hunk.new_start + hunk.new_count and not applied then
-- Revert this section by using original lines
local orig_start = hunk.old_start
local orig_end = hunk.old_start + hunk.old_count - 1
for orig_line = orig_start, orig_end do
if orig_line <= #original_lines then
table.insert(new_lines, original_lines[orig_line])
end
end
-- Skip the modified lines in current buffer
buffer_line = hunk.new_start + hunk.new_count
applied = true
else
-- Copy unchanged line
if buffer_line <= #lines then
table.insert(new_lines, lines[buffer_line])
end
buffer_line = buffer_line + 1
end
end
-- Update buffer
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, new_lines)
end
-- Apply hunk changes to buffer -- Apply hunk changes to buffer
function M.apply_hunk_changes(bufnr, hunk) function M.apply_hunk_changes(bufnr, hunk)
-- Get current buffer lines -- Get current buffer lines
@ -357,4 +443,18 @@ function M.has_active_diff(bufnr)
return M.active_diffs[bufnr] ~= nil return M.active_diffs[bufnr] ~= nil
end end
-- Test keymap functionality
function M.test_keymap()
local bufnr = vim.api.nvim_get_current_buf()
vim.notify('Testing keymap for buffer: ' .. bufnr, vim.log.levels.INFO)
vim.notify('Available diff data: ' .. vim.inspect(vim.tbl_keys(M.active_diffs)), vim.log.levels.INFO)
if M.active_diffs[bufnr] then
vim.notify('Diff data found! Calling reject function...', vim.log.levels.INFO)
M.reject_current_hunk(bufnr)
else
vim.notify('No diff data for this buffer', vim.log.levels.ERROR)
end
end
return M return M