pull/1635/head
zolinthecow 2 days ago
parent 50c50b94cc
commit 6f18060a51

1
.gitignore vendored

@ -15,3 +15,4 @@ lazy-lock.json
# Claude Code hooks
.claude/
.agent-work/

File diff suppressed because it is too large Load Diff

@ -44,7 +44,7 @@ end
-- Create a new worktree
function M.create_worktree(path, branch)
branch = branch or 'main'
branch = branch or M.default_branch()
-- Check if worktree already exists
local worktrees = M.list_worktrees()
@ -54,22 +54,44 @@ function M.create_worktree(path, branch)
end
end
-- Create worktree
local cmd = string.format('git worktree add "%s" "%s" 2>&1', path, branch)
-- Generate unique branch name for the worktree
local worktree_branch = 'agent-' .. utils.timestamp()
-- Create worktree with new branch based on specified branch
local cmd = string.format('git worktree add -b "%s" "%s" "%s" 2>&1', worktree_branch, path, branch)
local result, err = utils.exec(cmd)
if err then
return false, result
end
return true, { path = path, branch = branch }
-- For background agents, ensure no hooks by creating empty .claude directory
-- This prevents inline diffs from triggering
local claude_dir = path .. '/.claude'
if vim.fn.isdirectory(claude_dir) == 0 then
vim.fn.mkdir(claude_dir, 'p')
end
-- Create empty settings.json to disable hooks
local empty_settings = '{"hooks": {}}'
utils.write_file(claude_dir .. '/settings.json', empty_settings)
return true, { path = path, branch = worktree_branch, base_branch = branch }
end
-- Remove a worktree
function M.remove_worktree(path)
-- First try to remove as git worktree
local cmd = string.format('git worktree remove "%s" --force 2>&1', path)
local _, err = utils.exec(cmd)
return err == nil
local result, err = utils.exec(cmd)
-- If it's not a worktree or already removed, just delete the directory
if err or result:match('not a working tree') then
local rm_cmd = string.format('rm -rf "%s"', path)
utils.exec(rm_cmd)
end
return true
end
-- Add entry to .gitignore
@ -103,6 +125,32 @@ function M.current_branch()
return nil
end
-- Get default branch (usually main or master)
function M.default_branch()
-- Try to get the default branch from remote
local result = utils.exec('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null')
if result and result ~= '' then
local branch = result:match('refs/remotes/origin/(.+)')
if branch then
return branch:gsub('\n', '')
end
end
-- Fallback: check if main or master exists
local main_exists = utils.exec('git show-ref --verify --quiet refs/heads/main')
if main_exists and main_exists == '' then
return 'main'
end
local master_exists = utils.exec('git show-ref --verify --quiet refs/heads/master')
if master_exists and master_exists == '' then
return 'master'
end
-- Final fallback
return 'main'
end
-- Get git status
function M.status(path)
local cmd = 'git status --porcelain'

@ -20,16 +20,22 @@ end
-- Load registry from disk
function M.load()
vim.notify('registry.load() called', vim.log.levels.DEBUG)
local content = utils.read_file(M.registry_path)
if content then
vim.notify(string.format('registry.load: Read %d bytes from %s', #content, M.registry_path), vim.log.levels.DEBUG)
local ok, data = pcall(vim.json.decode, content)
if ok and type(data) == 'table' then
local agent_count = vim.tbl_count(data)
vim.notify(string.format('registry.load: Decoded %d agents from JSON', agent_count), vim.log.levels.DEBUG)
M.agents = data
M.validate_agents()
else
vim.notify('registry.load: Failed to decode JSON, clearing agents', vim.log.levels.WARN)
M.agents = {}
end
else
vim.notify('registry.load: No content read from file, clearing agents', vim.log.levels.WARN)
M.agents = {}
end
end
@ -47,14 +53,27 @@ function M.validate_agents()
local valid_agents = {}
local now = os.time()
for id, agent in pairs(M.agents) do
-- Check if agent directory still exists
if utils.file_exists(agent.work_dir .. '/mission.log') then
local mission_log_path = agent.work_dir .. '/mission.log'
local mission_exists = utils.file_exists(mission_log_path)
if mission_exists then
-- Check if tmux window still exists
local window_exists = M.check_window_exists(agent.window_id)
if window_exists then
agent.status = 'active'
-- Update progress from file for active agents
local progress_file = agent.work_dir .. '/progress.txt'
local progress_content = utils.read_file(progress_file)
if progress_content and progress_content ~= '' then
agent.progress = progress_content:gsub('\n$', '') -- Remove trailing newline
end
valid_agents[id] = agent
else
-- Window closed, mark as completed
@ -79,7 +98,7 @@ function M.check_window_exists(window_id)
end
-- Register a new agent
function M.register(task, work_dir, window_id, window_name)
function M.register(task, work_dir, window_id, window_name, fork_info)
local id = utils.timestamp() .. '-' .. math.random(1000, 9999)
local agent = {
id = id,
@ -90,6 +109,9 @@ function M.register(task, work_dir, window_id, window_name)
start_time = os.time(),
status = 'active',
project_root = utils.get_project_root(),
progress = 'Starting...', -- Add progress field
last_update = os.time(),
fork_info = fork_info, -- Store branch/stash info
}
M.agents[id] = agent
@ -105,12 +127,19 @@ end
-- Get all agents for current project
function M.get_project_agents()
-- Ensure registry is loaded
if not M.agents or vim.tbl_isempty(M.agents) then
M.load()
end
local project_root = utils.get_project_root()
local project_agents = {}
for id, agent in pairs(M.agents) do
if agent.project_root == project_root then
project_agents[id] = agent
-- Include the registry ID with the agent
agent._registry_id = id
table.insert(project_agents, agent)
end
end
@ -135,6 +164,16 @@ function M.update_status(id, status)
if status == 'completed' or status == 'failed' then
M.agents[id].end_time = os.time()
end
M.agents[id].last_update = os.time()
M.save()
end
end
-- Update agent progress
function M.update_progress(id, progress)
if M.agents[id] then
M.agents[id].progress = progress
M.agents[id].last_update = os.time()
M.save()
end
end
@ -187,12 +226,22 @@ function M.format_agent(agent)
age_str = string.format('%dd', math.floor(age / 86400))
end
local progress_str = ''
if agent.progress and agent.status == 'active' then
progress_str = string.format(' | %s', agent.progress)
end
-- Clean up task to single line
local task_line = agent.task:match('[^\n]*') or agent.task
local task_preview = task_line:sub(1, 50) .. (task_line:len() > 50 and '...' or '')
return string.format(
'[%s] %s (%s) - %s',
'[%s] %s (%s) - %s%s',
agent.status:upper(),
agent.task,
task_preview,
age_str,
agent.window_name or 'unknown'
agent.window_name or 'unknown',
progress_str
)
end

@ -0,0 +1,67 @@
-- Statusline components for nvim-claude
local M = {}
-- Get active agent count and summary
function M.get_agent_status()
local registry = require('nvim-claude.registry')
-- Validate agents to update their status
registry.validate_agents()
local agents = registry.get_project_agents()
local active_count = 0
local latest_progress = nil
local latest_task = nil
for _, agent in ipairs(agents) do
if agent.status == 'active' then
active_count = active_count + 1
-- Get the most recently updated active agent
if not latest_progress or (agent.last_update and agent.last_update > (latest_progress.last_update or 0)) then
latest_progress = agent.progress
latest_task = agent.task
end
end
end
if active_count == 0 then
return ''
elseif active_count == 1 and latest_progress then
-- Show single agent progress
local task_short = latest_task
if #latest_task > 20 then
task_short = latest_task:sub(1, 17) .. '...'
end
return string.format('🤖 %s: %s', task_short, latest_progress)
else
-- Show count of multiple agents
return string.format('🤖 %d agents', active_count)
end
end
-- Lualine component
function M.lualine_component()
return {
M.get_agent_status,
cond = function()
-- Only show if there are active agents
local status = M.get_agent_status()
return status ~= ''
end,
on_click = function()
-- Open agent list on click
vim.cmd('ClaudeAgents')
end,
}
end
-- Simple string function for custom statuslines
function M.statusline()
local status = M.get_agent_status()
if status ~= '' then
return ' ' .. status .. ' '
end
return ''
end
return M

@ -57,7 +57,7 @@ end
-- Check if file exists
function M.file_exists(path)
local stat = vim.loop.fs_stat(path)
return stat and stat.type == 'file'
return stat ~= nil
end
-- Generate timestamp string

@ -200,8 +200,8 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
#### 8.2 Quick Commands
- [x] `:ClaudeKill [agent]` - Terminate agent
- [x] `:ClaudeClean` - Clean up old agents
- [ ] `:ClaudeSwitch [agent]` - Switch to agent tmux
- [x] `:ClaudeAgents` - List all agents
- [x] `:ClaudeAgents` - Interactive agent manager (switch, diff, kill)
- [x] `:ClaudeDiffAgent` - Review agent changes with diffview
- [x] `:ClaudeResetBaseline` - Reset inline diff baseline
- [x] Test: Each command functions correctly
@ -316,4 +316,36 @@ Building a Claude Code integration for Neovim that works seamlessly with a tmux-
- 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
## Background Agent Features (v1.0 Complete)
### Key Improvements
1. **Hook Isolation**: Background agents always run without hooks (no inline diffs)
2. **ClaudeSwitch**: Switch to agent's worktree to chat/give follow-ups (hooks remain disabled)
3. **ClaudeDiffAgent**: Review agent changes using diffview.nvim
4. **Progress Tracking**: Agents can update progress.txt for real-time status updates
5. **Statusline Integration**: Shows active agent count and latest progress
6. **Enhanced Agent Creation UI**: Interactive popup for mission description with fork options
### Usage
- `ClaudeBg` - Opens interactive UI for creating agents with:
- Multi-line mission description editor
- Fork options: current branch, main, stash, or any branch
- Shows what the agent will be based on
- `ClaudeBg <task>` - Quick creation (backwards compatible)
- `ClaudeSwitch [agent]` - Switch to agent's worktree to chat (no inline diffs)
- `ClaudeDiffAgent [agent]` - Review agent changes with diffview
- `ClaudeAgents` - List all agents with progress
- Agents update progress: `echo 'status' > progress.txt`
### Agent Creation Options
- **Fork from current branch**: Default, uses your current branch state
- **Fork from default branch**: Start fresh from your default branch (auto-detects main/master)
- **Stash current changes**: Creates stash of current work, then applies to agent
- **Fork from other branch**: Choose any branch to base agent on
### Smart Branch Detection
The plugin automatically detects your repository's default branch (main, master, etc.) instead of assuming "main", making it compatible with older repositories that use "master".
### Design Philosophy
Background agents are kept simple - they're always background agents with hooks disabled. This avoids complexity around state transitions and keeps the workflow predictable. Use regular `:ClaudeChat` in your main workspace for inline diff functionality.

Loading…
Cancel
Save