strings.Split produces an empty trailing element when the git diff
output ends with \n. The empty element was written into the last file's
buffer, and TrimSuffix only removed one \n — leaving the last Diff
field with an extra trailing newline that earlier files didn't have.
Switch to TrimRight so all Diff values are consistently clean.
Group functionally related files (e.g., impl + test, handler + model) into
a single review context using an LLM clustering step. This improves review
quality by giving the model cross-file awareness within related changes.
Key changes:
- Add file grouping agent with threshold-based decisions and recursive splitting
- Refactor dispatchSubtasks/executeSubtask to operate on DiffGroup instead of
individual diffs, supporting combined multi-file diffs with FILE separators
- Add path validation in code_comment tool for multi-file groups
- Add StripDiffHeaders to reduce token usage in prompts
- Add extractJSONArray fallback for filter response parsing robustness
- Persist file group metadata to JSONL and render groups in viewer UI
- Merge intermediate tool-only agent rounds in viewer for cleaner display
- Update prompt templates: remove {{current_file_path}}, use <pending_review_diff>
When a file was renamed on the target branch, ocr review emitted
'[ocr] WARNING: cannot read file <old path> at ref <to>: exit status 128'.
Two compounding bugs:
1. The parser required 'a/'/'b/' prefixes when matching '--- /dev/null' /
'+++ /dev/null', but git emits these lines without prefixes, so
IsNew/IsDeleted were never set and deleted files fell through to a
doomed 'git show ref:<old path>'.
2. 'rename from' / 'rename to' extended headers were never parsed, and
git diff/show call sites did not force rename detection, so renames
degraded to delete+add whenever the user had diff.renames=false.
Fixes:
- Parse 'rename from'/'rename to', 'new file mode', 'deleted file mode'
and unprefixed /dev/null markers in ParseDiffText.
- Pass --find-renames to all git diff/show invocations so rename
detection no longer depends on user config.
- Add IsRenamed to model.Diff (json: is_renamed) and prefer it in
diffStatus.
- Add parser unit tests and a range-mode rename regression test.
Fixes#99
Introduce gitcmd.Runner with channel-based semaphore to cap concurrent
git subprocesses (default 16, configurable via --max-git-procs). Route
all tool-layer and diff-layer git calls through the shared runner.
Also fix diff.Provider.runGit lacking context.Context — now the full
chain (GetDiff → MergeBase → ParseDiffText → finalizeDiff) propagates
the caller's context for proper cancellation and timeout support.
file_find and finalizeDiff always read from the working tree, even in
range/commit mode where the review targets a specific git ref. This
caused inconsistent file versions compared to file_read and code_search
which correctly used git show. Fix file_find to use git ls-tree and
finalizeDiff to use git show when a ref is specified.