Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .claude/scripts/process-review-file.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

# Process a single review file
# Usage: ./process-review-file.sh <filename>

if [ $# -ne 1 ]; then
echo "Usage: $0 <filename>"
exit 1
fi

FILE="$1"

if [ ! -f "$FILE" ]; then
echo "Error: File not found: $FILE"
exit 1
fi

# Extract the ID from filename
BASENAME=$(basename "$FILE")
# Remove prefix and suffix using parameter expansion
ID="${BASENAME#action-required_}"
ID="${ID%.md}"

echo "Processing file: $FILE"
echo "ID: $ID"

# The actual processing will be done by Claude Code
# This script just handles the file renaming after processing

# Check if we should rename (this will be called after Claude adds the reply)
if [ -f "$FILE" ]; then
# Replace prefix using parameter expansion
NEW_FILE="${FILE/action-required_/waiting-review_}"
echo "Renaming to: $NEW_FILE"
mv "$FILE" "$NEW_FILE"
echo "File renamed successfully"
else
echo "File no longer exists (may have been already processed)"
fi
41 changes: 41 additions & 0 deletions .claude/scripts/review-loop-monitor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Review Loop Monitor Script
# This script continuously monitors for action-required files and reports their status

REVIEW_DIR=".code-review"
CHECK_INTERVAL=5
STATUS_INTERVAL=30
last_status_time=$(date +%s)

echo "Starting review loop monitor..."
echo "Checking for action-required files every ${CHECK_INTERVAL} seconds"
echo "Status updates every ${STATUS_INTERVAL} seconds"
echo "---"

while true; do
current_time=$(date +%s)

# Find action-required files
files=$(find "$REVIEW_DIR" -name "action-required_*.md" -type f 2>/dev/null)
# Use grep -c for counting non-empty lines
file_count=$(echo "$files" | grep -c -v '^$' || echo "0")

if [ -n "$files" ] && [ "$files" != "" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Found $file_count action-required file(s):"
echo "$files" | while read -r file; do
[ -n "$file" ] && echo " - $file"
done
echo "ACTION_REQUIRED_FOUND"
exit 0
fi

# Periodic status update
time_diff=$((current_time - last_status_time))
if [ $time_diff -ge $STATUS_INTERVAL ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] No action-required files found. Continuing to monitor..."
last_status_time=$current_time
fi

sleep $CHECK_INTERVAL
done
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ require('code-review').setup({
},
},
auto_copy_on_add = false, -- Automatically copy each new comment to clipboard when added
-- Author name used by Claude Code (for automatic status management)
-- Comments from this author trigger "waiting-review" status
-- Comments from other authors trigger "action-required" status
claude_code_author = 'Claude Code',
},
-- Keymaps (set to false to disable all keymaps)
keymaps = {
Expand Down Expand Up @@ -312,10 +316,23 @@ The preview buffer is fully editable. You can:

Comments automatically create discussion threads. You can:

- **Reply to comments**: Add comments on the same line to create a thread
- **Resolve threads**: Use `:CodeReviewResolveThread` when discussion is complete
- **Reopen threads**: Use `:CodeReviewReopenThread` if more discussion is needed
- Thread status is displayed in the preview (`[open]` or `[resolved]`)
- **Reply to comments**: Use `<leader>rr` to reply to existing comments
- **Create new threads**: Use `<leader>rc` to start a new thread on the same line
- **Resolve threads**: Use `<leader>ro` to mark a thread as resolved
- Thread status is displayed in the comment list:
- `[!]` Action Required - awaiting response from code author (Claude Code)
- `[⏳]` Waiting Review - awaiting reviewer response
- `[✓]` Resolved - discussion complete

#### Automatic Status Management

When using file storage backend, thread status is automatically managed based on the latest comment author:

- Comments from `claude_code_author` (configurable) → `waiting-review`
- Comments from other authors → `action-required`
- Manual resolution → `resolved`

This enables efficient workflow between AI assistants and human reviewers.

### Comment List Picker

Expand Down Expand Up @@ -387,7 +404,6 @@ This function needs error handling for nil input.
Consider using table.filter for better readability.
````


## 💾 Storage Backends

code-review.nvim supports two storage backends:
Expand Down Expand Up @@ -439,7 +455,6 @@ require('code-review').setup({
3. Comments persist across Neovim sessions
4. External tools can monitor the directory for new files


## 🔨 Development

### Setup
Expand Down
135 changes: 111 additions & 24 deletions lua/code-review/comment.lua
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,77 @@ end
add_signs = function(bufnr, comments)
local config = require("code-review.config").get("ui.signs")

-- Define sign if not already defined
-- Remove existing signs first
vim.fn.sign_unplace("CodeReviewSigns", { buffer = bufnr })

-- Define signs for each status (using same text but different colors)
vim.fn.sign_define("CodeReviewWaitingReview", {
text = config.text,
texthl = "CodeReviewWaitingReview",
linehl = config.linehl,
numhl = config.numhl,
})

vim.fn.sign_define("CodeReviewActionRequired", {
text = config.text,
texthl = "CodeReviewActionRequired",
linehl = config.linehl,
numhl = config.numhl,
})

vim.fn.sign_define("CodeReviewResolved", {
text = config.text,
texthl = "CodeReviewResolved",
linehl = config.linehl,
numhl = config.numhl,
})

-- Default sign for unknown status
vim.fn.sign_define("CodeReviewComment", {
text = config.text,
texthl = config.texthl,
linehl = config.linehl,
numhl = config.numhl,
})

-- Place signs
-- Group comments by line to determine thread status
local status_by_line = {}
for _, comment in ipairs(comments) do
for line = comment.line_start, comment.line_end do
vim.fn.sign_place(0, "CodeReviewSigns", "CodeReviewComment", bufnr, { lnum = line, priority = 100 })
-- Determine status based on thread_status or thread info
local status = comment.thread_status or "open"

-- If resolved thread, mark as resolved
if comment.thread_id then
local sign_state = require("code-review.state")
local thread_data = sign_state.get_all_threads()[comment.thread_id]
if thread_data and thread_data.status == "resolved" then
status = "resolved"
end
end
-- Priority: resolved < action-required < waiting-review
if not status_by_line[line] then
status_by_line[line] = status
elseif status == "waiting-review" then
status_by_line[line] = status
elseif status == "action-required" and status_by_line[line] ~= "waiting-review" then
status_by_line[line] = status
end
end
end

-- Place signs based on status
for line, status in pairs(status_by_line) do
local sign_name = "CodeReviewComment"
if status == "waiting-review" then
sign_name = "CodeReviewWaitingReview"
elseif status == "action-required" then
sign_name = "CodeReviewActionRequired"
elseif status == "resolved" then
sign_name = "CodeReviewResolved"
end

vim.fn.sign_place(0, "CodeReviewSigns", sign_name, bufnr, { lnum = line, priority = 100 })
end
end

Expand Down Expand Up @@ -168,42 +226,71 @@ add_virtual_text = function(bufnr, comments)

-- Add virtual text
for line, line_threads in pairs(threads_by_line) do
local text = config.prefix
local thread_count = vim.tbl_count(line_threads)
local text = ""
local highlight = config.hl
local show_virt_text = true

if thread_count > 1 then
-- Multiple threads on same line
text = text .. string.format("(%d threads)", thread_count)
text = config.prefix .. string.format("(%d threads)", thread_count)
else
-- Single thread - find the latest comment
local _, thread_comments = next(line_threads)

-- Find the latest comment (last in thread)
local latest_comment = thread_comments[#thread_comments]

-- If no timestamp, assume comments are in chronological order
if thread_comments[1].timestamp then
-- Sort by timestamp to find the latest
table.sort(thread_comments, function(a, b)
return (a.timestamp or 0) < (b.timestamp or 0)
end)
latest_comment = thread_comments[#thread_comments]
local thread_id, thread_comments = next(line_threads)

-- Determine thread status
local status = "open"
if thread_comments[1].thread_status then
status = thread_comments[1].thread_status
end

-- Check if thread is resolved
local comment_state = require("code-review.state")
local thread_data = comment_state.get_all_threads()[thread_id]
if thread_data and thread_data.status == "resolved" then
status = "resolved"
show_virt_text = false -- Don't show virtual text for resolved
end
local first_line = latest_comment.comment:match("^[^\n]*") or latest_comment.comment

-- Truncate if too long
if #first_line > 40 then
first_line = first_line:sub(1, 37) .. "..."
if show_virt_text then
-- Find the latest comment (last in thread)
local latest_comment = thread_comments[#thread_comments]

-- If no timestamp, assume comments are in chronological order
if thread_comments[1].timestamp then
-- Sort by timestamp to find the latest
table.sort(thread_comments, function(a, b)
return (a.timestamp or 0) < (b.timestamp or 0)
end)
latest_comment = thread_comments[#thread_comments]
end

-- Set prefix based on status
local prefix = config.prefix
if status == "waiting-review" then
prefix = "󰇮 " -- Mail icon for waiting review (Nerd Font)
highlight = "CodeReviewWaitingReview"
elseif status == "action-required" then
prefix = "○ "
highlight = "CodeReviewActionRequired"
end

local first_line = latest_comment.comment:match("^[^\n]*") or latest_comment.comment

-- Truncate if too long
if #first_line > 40 then
first_line = first_line:sub(1, 37) .. "..."
end
text = prefix .. first_line
end
text = text .. first_line
end

-- Ensure buffer is loaded and line is valid
if vim.api.nvim_buf_is_loaded(bufnr) then
if show_virt_text and text ~= "" and vim.api.nvim_buf_is_loaded(bufnr) then
local line_count = vim.api.nvim_buf_line_count(bufnr)
if line <= line_count then
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns_virtual_text, line - 1, 0, {
virt_text = { { text, config.hl } },
virt_text = { { text, highlight } },
virt_text_pos = "eol",
})
end
Expand Down
10 changes: 7 additions & 3 deletions lua/code-review/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ local defaults = {
ui = {
-- Floating window settings for comment input
input_window = {
width = 60,
width = 80,
height = 1,
max_height = 20, -- Maximum height when content requires scrolling
border = "rounded",
Expand All @@ -16,10 +16,10 @@ local defaults = {
-- Preview window settings
preview = {
split = "vertical", -- 'vertical' or 'horizontal' or 'float'
vertical_width = 80,
vertical_width = 100,
horizontal_height = 20,
float = {
width = 0.8,
width = 0.85,
height = 0.8,
border = "rounded",
title = " Review Preview ",
Expand Down Expand Up @@ -67,6 +67,10 @@ local defaults = {
},
-- Automatically copy each new comment to clipboard when added
auto_copy_on_add = false,
-- Author name used by Claude Code (for automatic status management)
-- Comments from this author trigger "waiting-review" status
-- Comments from other authors trigger "action-required" status
claude_code_author = "Claude Code",
},
-- Keymaps (set to false to disable all keymaps)
keymaps = {
Expand Down
Loading