Add per-commit review mode#444
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an initial per-commit local review mode for commit ranges (tuicr --commits -r base..head), including per-commit comment scoping, per-commit export grouping, and updated CLI/UI/docs to support the workflow.
Changes:
- Introduces a new session diff source (
PerCommitRange) plusCommitReviewstorage to persist comments/files per commit while mirroring the active commit into the existing flat session fields. - Adds CLI flag parsing (
--commits) and commit-cycling behavior in the TUI, plus slug/manifest updates for persisted sessions. - Extends export/CLI output to include per-commit grouped markdown and comment JSON with commit metadata.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ui/status_bar.rs | Displays per-commit mode status (active commit index / total). |
| src/ui/help_popup.rs | Updates help text to reflect per-commit behavior for cycling commits. |
| src/slug.rs | Adds slug source + parsing/formatting for per-commit sessions (per-commit/<base>..<head>). |
| src/review_store.rs | Routes review add to the active commit for per-commit sessions + adds tests. |
| src/review_cli.rs | Emits per-commit comment listings with commit metadata when in per-commit sessions + adds tests. |
| src/persistence/manifest.rs | Computes comment/file counts correctly for per-commit sessions + labels per-commit diff source. |
| src/output/markdown.rs | Adds per-commit grouped markdown export with commit message context + adds tests. |
| src/model/review.rs | Introduces CommitReview and SessionDiffSource::PerCommitRange; updates session comment/review counters. |
| src/model/mod.rs | Re-exports CommitReview. |
| src/main.rs | Threads per_commit startup option into app initialization. |
| src/handler.rs | Ensures active per-commit state is synced before export/copy-and-quit; updates commit cycling behavior. |
| src/cli.rs | Adds --commits flag and conflict validation; adds CLI parsing tests. |
| src/app.rs | Implements per-commit mode core behavior (session creation/loading, commit switching, syncing, reload/export integration) + adds tests. |
| README.md | Documents --commits usage and per-commit export behavior. |
| docs/KEYBINDINGS.md | Documents commit cycling behavior updates for per-commit mode. |
| AGENTS.md | Documents how per-commit mode is stored/exported and how review CLI behaves with per-commit sessions. |
| let mut empty_lines = Vec::new(); | ||
| for (line, comments) in &mut review.line_comments { | ||
| if let Some(index) = comments.iter().position(|comment| comment.id == id) { | ||
| comments.remove(index); | ||
| if comments.is_empty() { | ||
| empty_lines.push(*line); | ||
| } | ||
| for line in empty_lines { | ||
| review.line_comments.remove(&line); | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
| for line in empty_lines { | ||
| review.line_comments.remove(&line); | ||
| } |
| let mut files: Vec<_> = commit_review.files.iter().collect(); | ||
| files.sort_by_key(|(path, _)| { | ||
| if path.as_path() == Path::new("Commit Message") { | ||
| String::new() | ||
| } else { | ||
| path.to_string_lossy().to_string() | ||
| } | ||
| }); | ||
| for (path, review) in files { | ||
| let path_display = if path.as_path() == Path::new("Commit Message") { | ||
| "commit message".to_string() | ||
| } else { | ||
| path.to_string_lossy().to_string() | ||
| }; |
| let mut files: Vec<_> = review.files.iter().collect(); | ||
| files.sort_by_key(|(path, _)| { | ||
| if path.as_path() == std::path::Path::new("Commit Message") { | ||
| String::new() | ||
| } else { | ||
| path.to_string_lossy().to_string() | ||
| } | ||
| }); | ||
|
|
||
| for (path, file_review) in files { | ||
| let path_str = if path.as_path() == std::path::Path::new("Commit Message") { | ||
| "commit message".to_string() | ||
| } else { | ||
| path.display().to_string() | ||
| }; |
| let mut msg_review = FileReview::new(PathBuf::from("Commit Message"), FileStatus::Added, 0); | ||
| msg_review.add_line_comment( | ||
| 1, | ||
| Comment::new( | ||
| "Commit message note".to_string(), | ||
| CommentType::Suggestion, | ||
| Some(LineSide::New), | ||
| ), | ||
| ); | ||
| second | ||
| .files | ||
| .insert(PathBuf::from("Commit Message"), msg_review); | ||
| session.per_commit_reviews.insert(second_id.clone(), second); |
| let mut msg_review = FileReview::new(PathBuf::from("Commit Message"), FileStatus::Added, 0); | ||
| msg_review.add_line_comment( | ||
| 1, | ||
| Comment::new( | ||
| "Subject should be imperative".to_string(), | ||
| CommentType::Suggestion, | ||
| Some(LineSide::New), | ||
| ), | ||
| ); | ||
| first | ||
| .files | ||
| .insert(PathBuf::from("Commit Message"), msg_review); | ||
| session.per_commit_reviews.insert(oldest.clone(), first); |
|
We already have a very similar workflow and I added #442 yesterday which is very similar to what you are doing, maybe you approach is better though. (Not that I have been using tuicr to do per commit review for a long time) |
|
Thanks @thiblahute and @ruifm - I prefer the approach in #442 that fixes the bugs with the existing per-commit reviews rather than building workflow into |
Adds an initial per-commit review workflow for local commit ranges. Reviewers can run
tuicr --commits -r main..HEAD, move through commits one at a time, leave comments on each commit’s diff or commit message, and export feedback grouped by commit for agent-friendly review output.Example export:
Advantages of per-commit mode:
apply without mixing feedback across unrelated changes.
active commit.