Skip to content

Commit 87c024a

Browse files
committed
Auto-sync comments/diffs
1 parent f7e369c commit 87c024a

File tree

12 files changed

+551
-65
lines changed

12 files changed

+551
-65
lines changed

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
- `GET /pulls/{number}/comments` is paginated; `get_review_comments()` must follow `Link` header `rel="next"` (and should request `per_page=100`) or comments silently truncate on larger PRs.
4242
- LEFT-side old-to-new mappings can legitimately resolve to `0` on full-file deletions; clamp mapped display lines into `[1, line_count]` before extmark placement to avoid out-of-range row errors.
4343

44+
## Learned while implementing autosync triggers
45+
46+
- Save-triggered PR sync should call CLI fetch with `--skip-comments` and preserve in-memory `review.comments`; this refreshes diff/highlights without comment API churn.
47+
- Auto-sync timers are owned by `init.lua`; `state.clear_review()` should call `neo_reviewer._stop_autosync()` when available to avoid leaking periodic timers across teardown paths and tests.
48+
- To keep the AI walkthrough panel stable during sync, plumb `keep_ai_ui` through `state.clear_review()` and re-render with `ai_ui.open()` after rebuilding review state.
49+
4450
## Project Overview
4551

4652
`neo-reviewer` is a Neovim plugin for reviewing GitHub PRs. Hybrid architecture: Rust CLI for GitHub API/diff parsing, Lua plugin for UI.

cli/src/commands/fetch.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::commands::diff::{ensure_git_commit_available, get_pr_review_files};
66
use crate::github::client::GitHubClient;
77
use crate::github::types::{FetchResponse, PrRef};
88

9-
pub async fn run(url: &str) -> Result<()> {
9+
pub async fn run(url: &str, skip_comments: bool) -> Result<()> {
1010
let client = GitHubClient::new()?;
1111
let pr_ref = GitHubClient::parse_pr_url(url)?;
1212

@@ -20,8 +20,11 @@ pub async fn run(url: &str) -> Result<()> {
2020
// Fetch change blocks from local git using the PR commit range.
2121
let files = get_pr_review_files(&pr.base_sha, &pr.head_sha, true)?;
2222

23-
// Fetch existing comments
24-
let comments = client.get_review_comments(&pr_ref).await?;
23+
let comments = if skip_comments {
24+
Vec::new()
25+
} else {
26+
client.get_review_comments(&pr_ref).await?
27+
};
2528

2629
let response = FetchResponse {
2730
pr,

cli/src/main.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ enum Commands {
4343
/// GitHub PR URL (e.g., https://github.com/owner/repo/pull/123)
4444
#[arg(short, long)]
4545
url: String,
46+
47+
/// Skip fetching review comments (diff/metadata only)
48+
#[arg(long)]
49+
skip_comments: bool,
4650
},
4751

4852
/// Add a review comment to a PR
@@ -167,8 +171,8 @@ async fn main() -> Result<()> {
167171
tracked_only,
168172
})?;
169173
}
170-
Commands::Fetch { url } => {
171-
commands::fetch::run(&url).await?;
174+
Commands::Fetch { url, skip_comments } => {
175+
commands::fetch::run(&url, skip_comments).await?;
172176
}
173177
Commands::Comment {
174178
url,

doc/neo_reviewer.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ Call setup() with your options: >lua
9898
"Cargo.lock",
9999
},
100100
},
101+
sync = {
102+
on_save = true, -- Auto-sync after saving reviewed files
103+
save_debounce_ms = 400, -- Debounce save-triggered sync
104+
periodic_enabled = true, -- Periodic background sync
105+
periodic_interval_ms = 120000, -- 2 minutes
106+
cooldown_ms = 1500, -- Min spacing between sync starts
107+
},
101108
ai = {
102109
enabled = false, -- Run AI analysis by default
103110
model = "gpt-5.3-codex", -- Model for Codex CLI (default)
@@ -137,6 +144,22 @@ review_diff ~
137144
- `noise_files`: Basenames to skip when `skip_noise_files` is enabled.
138145
Any file with a matching basename is excluded from local diff review.
139146

147+
*neo_reviewer-config-sync*
148+
sync ~
149+
Configure automatic review synchronization.
150+
- `on_save`: Trigger sync after saving a file in the active review
151+
(default: true). Applies to both PR and local diff reviews.
152+
- `save_debounce_ms`: Debounce window for save-triggered sync
153+
(default: 400).
154+
- `periodic_enabled`: Enable periodic background sync
155+
(default: true).
156+
- `periodic_interval_ms`: Periodic sync interval in milliseconds
157+
(default: 120000 / 2 minutes).
158+
- `cooldown_ms`: Minimum time between sync starts in milliseconds
159+
(default: 1500). This guards against stacked sync runs.
160+
Automatic save/periodic sync runs silently on success (no "Synced"
161+
notifications). Errors still notify.
162+
140163
*neo_reviewer-config-ai*
141164
ai ~
142165
Configure AI-powered review analysis. Requires an AI CLI (Codex CLI by
@@ -268,6 +291,10 @@ These user commands are created when you call `setup()`.
268291
same target/selector options used by |:ReviewDiff| and reapplies markers.
269292
Preserves cursor position, expanded change block state, and AI summary when
270293
possible.
294+
By default, sync also runs automatically on save for reviewed files and as
295+
a periodic background refresh every 2 minutes. Save-triggered PR sync skips
296+
comment refresh (diff/highlights only). Automatic sync keeps the AI
297+
walkthrough window open if it is already open. Configure via `sync.*`.
271298

272299
==============================================================================
273300
6. FUNCTIONS *neo_reviewer-functions*
@@ -411,6 +438,9 @@ require("neo_reviewer").sync() *neo_reviewer.s
411438
- During local diff review: re-runs local diff sourcing with the same
412439
selection options used by |neo_reviewer.review_diff()|.
413440
Preserves cursor position and expanded change block state when possible.
441+
Manual sync (`:ReviewSync`) always refreshes comments for PR reviews.
442+
Automatic save-triggered sync refreshes diff/highlights but skips comment
443+
refresh for PR reviews.
414444
See |:ReviewSync|.
415445

416446
require("neo_reviewer").toggle_ai_feedback() *neo_reviewer.toggle_ai_feedback()*

lua/neo_reviewer/cli.lua

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ local config = require("neo_reviewer.config")
3737
---@field merge_base? boolean Compare against merge-base(HEAD, target)
3838
---@field tracked_only? boolean Exclude untracked files
3939

40+
---@class NRFetchPROpts
41+
---@field skip_comments? boolean Skip fetching review comments
42+
4043
---@class NRCLIModule
4144
local M = {}
4245

@@ -169,10 +172,17 @@ end
169172

170173
---@param url string
171174
---@param callback fun(data: NRReviewData?, err: string?)
172-
function M.fetch_pr(url, callback)
175+
---@param opts? NRFetchPROpts
176+
function M.fetch_pr(url, callback, opts)
177+
opts = opts or {}
178+
local args = { "fetch", "--url", url }
179+
if opts.skip_comments then
180+
table.insert(args, "--skip-comments")
181+
end
182+
173183
Job:new({
174184
command = config.values.cli_path,
175-
args = { "fetch", "--url", url },
185+
args = args,
176186
on_exit = vim.schedule_wrap(function(j, code)
177187
if code == 0 then
178188
local output = table.concat(j:result(), "\n")

lua/neo_reviewer/config.lua

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
---@field skip_noise_files boolean Whether to skip common lock/noise files in local diff reviews
3737
---@field noise_files string[] Basenames to skip in local diff reviews when skip_noise_files is enabled
3838

39+
---@class NRSync
40+
---@field on_save boolean Whether to trigger sync after saving a reviewed file
41+
---@field save_debounce_ms integer Debounce window for save-triggered sync
42+
---@field periodic_enabled boolean Whether to run periodic background sync
43+
---@field periodic_interval_ms integer Periodic sync interval in milliseconds
44+
---@field cooldown_ms integer Minimum time between sync starts in milliseconds
45+
3946
---@class NRConfig
4047
---@field cli_path string Path to the neo-reviewer CLI binary
4148
---@field signs NRSigns
@@ -44,6 +51,7 @@
4451
---@field thread_window NRThreadWindow
4552
---@field input_window NRInputWindow
4653
---@field review_diff NRReviewDiff
54+
---@field sync NRSync
4755
---@field ai NRAI AI analysis configuration
4856

4957
---@class NRPartialSigns
@@ -77,6 +85,13 @@
7785
---@field skip_noise_files? boolean Whether to skip common lock/noise files in local diff reviews
7886
---@field noise_files? string[] Basenames to skip in local diff reviews when skip_noise_files is enabled
7987

88+
---@class NRPartialSync
89+
---@field on_save? boolean Whether to trigger sync after saving a reviewed file
90+
---@field save_debounce_ms? integer Debounce window for save-triggered sync
91+
---@field periodic_enabled? boolean Whether to run periodic background sync
92+
---@field periodic_interval_ms? integer Periodic sync interval in milliseconds
93+
---@field cooldown_ms? integer Minimum time between sync starts in milliseconds
94+
8095
---@class NRPartialAI
8196
---@field enabled? boolean Whether AI analysis is enabled by default
8297
---@field model? string Model for AI CLI
@@ -92,6 +107,7 @@
92107
---@field thread_window? NRPartialThreadWindow
93108
---@field input_window? NRPartialInputWindow
94109
---@field review_diff? NRPartialReviewDiff
110+
---@field sync? NRPartialSync
95111
---@field ai? NRPartialAI AI analysis configuration
96112

97113
---@class NRConfigModule
@@ -160,6 +176,13 @@ M.values = {
160176
".terraform.lock.hcl",
161177
},
162178
},
179+
sync = {
180+
on_save = true,
181+
save_debounce_ms = 400,
182+
periodic_enabled = true,
183+
periodic_interval_ms = 120000,
184+
cooldown_ms = 1500,
185+
},
163186
ai = {
164187
enabled = false,
165188
model = "gpt-5.3-codex",

0 commit comments

Comments
 (0)