Skip to content

Code Review Tools usage experience improvements#14

Merged
peteretelej merged 11 commits intomainfrom
review-usefulness
Apr 9, 2026
Merged

Code Review Tools usage experience improvements#14
peteretelej merged 11 commits intomainfrom
review-usefulness

Conversation

@peteretelej
Copy link
Copy Markdown
Owner

@peteretelej peteretelej commented Apr 9, 2026

AI code review tools that consume diffchunk need to fit diffs into limited context windows. These changes give them three levers: choose a compact format that drops removed-code hunks, reduce context lines at load time, and see token estimates before deciding which chunks to fetch.

  • Add output format modes (raw, annotated, compact) to get_chunk so review agents can request token-efficient or structured representations of diffs
  • Add context_lines parameter to load_diff for stripping excess context at load time, and surface files_excluded count so agents know what was filtered
  • Add per-chunk and total token count estimates to list_chunks so agents can budget context windows before fetching chunk content

Summary by CodeRabbit

  • New Features

    • Control context lines shown around changes.
    • New chunk output modes: raw, annotated, compact.
    • Per-chunk token estimates and aggregate total token count.
    • Report count of files excluded by filters.
  • Documentation

    • Updated examples and docs for format options, context reduction, and token-count fields; removed coverage badges.
  • Tests

    • Expanded tests for formatting modes, token estimation, context reduction, and integration scenarios.
  • Chores

    • Pre-push hook now lints/formats the whole repository.

Plumbs a format string parameter through the MCP and tools layers with
raw as the default. Adds FormatMode enum, a formatter module with
placeholder branches for annotated/compact, and validation that rejects
unknown values.
Implements the annotated output mode that parses raw diff content
into structured sections with file headers, line-numbered new hunks,
and old hunks for removed code. Includes function context from @@ headers.
Compact mode shows only new hunks (context + added lines with new-file
line numbers), omitting removed lines and old hunk sections entirely.
Reuses the annotated format's hunk parsing infrastructure.
The load_diff response now includes a files_excluded count
showing how many files were removed by exclude_patterns.
Strips excess context lines from diff hunks at load time, reducing
token usage. Applied per-file before chunking via reduce_context()
in the parser.
Integration and edge case tests covering format mode composition,
context_lines reduction, and exclude_patterns interaction. Updated
design.md and README with format options documentation.
list_chunks now returns per-chunk token_count and total_token_count,
enabling consuming skills to budget context windows. Uses a chars/4
heuristic with no new dependencies.
Update all test files to handle the new list_chunks dict response shape,
add unit and integration tests for token estimation, and document the
token_count field in README and design docs.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Adds per-chunk token estimation and a total token count, a context-line reduction option for diffs, and chunk formatting modes (raw/annotated/compact). Threads context_lines through load/chunk tooling, tracks files_excluded in stats, adds parser/formatter utilities, and updates tests/docs to the new shapes and behaviors.

Changes

Cohort / File(s) Summary
Docs
README.md, docs/design.md
Documented context_lines, new format modes, token_count fields, files_excluded, updated examples and usage notes.
Models & Stats
src/models.py
Added estimate_tokens() and FormatMode enum; added files_excluded to DiffStats and token_count to ChunkInfo.
Diff Parsing
src/parser.py
Added DiffParser.reduce_context(content, context_lines) to trim context around changes, split/recompute sub-hunks, and return reduced diffs.
Chunking
src/chunker.py
DiffChunker.chunk_diff(..., context_lines: Optional[int]) applies context reduction per-file and tracks files_excluded_count, persisted to session stats.
Formatting
src/formatter.py
New module format_chunk(content, mode, chunk_files) and data classes (FileSection, Hunk, HunkLine); supports ANNOTATED and COMPACT renderings while leaving RAW unchanged.
Tools & Server APIs
src/tools.py, src/server.py
load_diff(..., context_lines) added; list_chunks() now returns { "chunks": [...], "total_token_count": int } with per-chunk token_count; get_chunk(..., format) added and applies validated formatting.
Tests
tests/...
test_integration.py, test_mcp_components.py, test_get_file_diff.py, test_case_insensitive.py, test_filesystem_edge_cases.py
Updated tests for new list_chunks shape; added tests for estimate_tokens() and extensive formatter unit/integration tests and edge cases; adjusted many tests to assert token_count and total_token_count.
Hook
scripts/pre-push
Expanded Ruff checks from src/ to repo root (ruff check ., ruff format --check .).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Tools as DiffChunkTools
    participant Chunker as DiffChunker
    participant Parser as DiffParser
    participant Formatter
    participant Session

    Client->>Tools: load_diff(path, exclude_patterns, context_lines)
    Tools->>Chunker: chunk_diff(content, context_lines=N)
    Chunker->>Parser: reduce_context(file_diff, context_lines=N)
    Parser-->>Chunker: reduced_diff
    Chunker->>Session: create chunks from reduced_diff
    Chunker->>Session: update stats (files_excluded)
    Session-->>Tools: chunks metadata
    Tools->>Session: estimate_tokens per chunk
    Tools-->>Client: {chunks, total_token_count, files_excluded}

    Client->>Tools: get_chunk(file, num, format="annotated")
    Tools->>Session: fetch raw chunk
    Session-->>Tools: raw chunk
    rect rgba(200,150,100,0.5)
    Tools->>Formatter: format_chunk(content, ANNOTATED, files)
    Formatter->>Parser: parse file sections and hunks
    Parser-->>Formatter: structured sections
    Formatter-->>Tools: annotated output
    end
    Tools-->>Client: formatted chunk content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Modernize MCP Server #11: Modifies DiffChunker.chunk_diff() signature—overlaps with this PR's addition of context_lines to the chunking API.

Poem

🐇 I nibble lines and count the hops,

Tokens tallied in tidy little crops.
Raw, annotated, compact on cue,
I trim the fluff and show what's new.
A rabbit cheers this tidy diff stew!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main objective of the changeset: improving the usage experience of code review tools by adding formatting options, token estimation, and context reduction features.
Docstring Coverage ✅ Passed Docstring coverage is 87.74% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch review-usefulness

Comment @coderabbitai help to get the list of available commands and usage tips.

@peteretelej peteretelej changed the title Code Review Tool use improvements Code Review Tools usage experience improvements Apr 9, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/chunker.py (1)

152-153: Consider setting files_excluded within update_stats() for better encapsulation.

Currently files_excluded is set after update_stats() completes, which works but creates a subtle dependency on the call order. This is a minor cohesion concern - passing the count to update_stats() would be cleaner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/chunker.py` around lines 152 - 153, The code sets
session.stats.files_excluded after calling session.update_stats(), creating a
call-order dependency; change update_stats to accept a files_excluded parameter
(e.g., update_stats(self, files_excluded=0) or similar) and assign
session.stats.files_excluded inside that method, then update the call site here
(replace session.update_stats() with session.update_stats(files_excluded_count))
and any other callers to pass the count so the stats update is encapsulated
within update_stats().
src/parser.py (1)

340-344: Redundant if/else branch can be simplified.

Both branches append the same text value, making the conditional unnecessary.

♻️ Suggested simplification
                 for lt, text, _, _ in sub:
-                    if lt != "meta":
-                        hunk_lines.append(text)
-                    else:
-                        hunk_lines.append(text)
+                    hunk_lines.append(text)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/parser.py` around lines 340 - 344, The loop over "sub" contains a
redundant conditional: both branches append the same "text" to "hunk_lines";
remove the if/else and always append "text" (i.e., replace the conditional block
with a single hunk_lines.append(text)) to simplify the code in the loop that
iterates "for lt, text, _, _ in sub".
tests/test_integration.py (1)

308-311: Accessing internal members for testing is acceptable here but worth noting.

The test accesses tools._get_file_key() and tools.sessions[] which are implementation details. This is fine for integration testing, but be aware these tests will break if the internal structure changes. Consider adding a comment noting this is an intentional white-box test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_integration.py` around lines 308 - 311, This test intentionally
reaches into internals by calling tools._get_file_key(react_diff_file) and
indexing tools.sessions[file_key] to call session.get_chunk_info(1); add a short
inline comment above these lines indicating this is a deliberate
white-box/integration check that depends on internal structure (so future
maintainers understand it may break if internals change) and keep the existing
calls as-is; reference the symbols _get_file_key, sessions, get_chunk_info and
react_diff_file in the comment to make intent explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/formatter.py`:
- Around line 79-83: The flush logic in _flush_file currently only appends
FileSection when current_hunks is non-empty, dropping rename-only/binary/no-hunk
file sections; update _flush_file to always append a FileSection (or a distinct
RawFileSection/passthrough marker) when current_path is set even if
current_hunks is empty so those files are carried through to output (preserve
current_path, current_hunks, in_hunk semantics), and make the same change to the
analogous flush/append branch later in the file that currently skips empty-hunk
files so list_chunks() and get_chunk(..., format="annotated"|"compact") remain
consistent.

---

Nitpick comments:
In `@src/chunker.py`:
- Around line 152-153: The code sets session.stats.files_excluded after calling
session.update_stats(), creating a call-order dependency; change update_stats to
accept a files_excluded parameter (e.g., update_stats(self, files_excluded=0) or
similar) and assign session.stats.files_excluded inside that method, then update
the call site here (replace session.update_stats() with
session.update_stats(files_excluded_count)) and any other callers to pass the
count so the stats update is encapsulated within update_stats().

In `@src/parser.py`:
- Around line 340-344: The loop over "sub" contains a redundant conditional:
both branches append the same "text" to "hunk_lines"; remove the if/else and
always append "text" (i.e., replace the conditional block with a single
hunk_lines.append(text)) to simplify the code in the loop that iterates "for lt,
text, _, _ in sub".

In `@tests/test_integration.py`:
- Around line 308-311: This test intentionally reaches into internals by calling
tools._get_file_key(react_diff_file) and indexing tools.sessions[file_key] to
call session.get_chunk_info(1); add a short inline comment above these lines
indicating this is a deliberate white-box/integration check that depends on
internal structure (so future maintainers understand it may break if internals
change) and keep the existing calls as-is; reference the symbols _get_file_key,
sessions, get_chunk_info and react_diff_file in the comment to make intent
explicit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 091f245a-a6b8-459f-9eda-f80437658076

📥 Commits

Reviewing files that changed from the base of the PR and between 8c6c4fb and 54d2c0b.

📒 Files selected for processing (13)
  • README.md
  • docs/design.md
  • src/chunker.py
  • src/formatter.py
  • src/models.py
  • src/parser.py
  • src/server.py
  • src/tools.py
  • tests/test_case_insensitive.py
  • tests/test_filesystem_edge_cases.py
  • tests/test_get_file_diff.py
  • tests/test_integration.py
  • tests/test_mcp_components.py

Rename ambiguous variable 'l' to 'ln' in test_mcp_components.py (E741).
Align pre-push hook with CI by linting the full repo, not just src/.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 96.52778% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.07%. Comparing base (b75d40c) to head (cc26a6f).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #14      +/-   ##
==========================================
+ Coverage   91.54%   94.07%   +2.53%     
==========================================
  Files           7        8       +1     
  Lines         461      743     +282     
==========================================
+ Hits          422      699     +277     
- Misses         39       44       +5     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/test_mcp_components.py (2)

840-859: Consider deduplicating repeated fixtures across test classes.

test_data_dir, react_diff_file, and go_diff_file are repeated in multiple classes. A shared module-level fixture set would reduce duplication and keep skip behavior consistent.

Also applies to: 1149-1165

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mcp_components.py` around lines 840 - 859, Move the duplicated
pytest fixtures into a shared module-level fixture set and update tests to use
them: extract the functions test_data_dir, go_diff_file, and react_diff_file
into a top-level (module or conftest) fixture module so they are defined once
(retain the same names and behavior, including the pytest.skip logic) and remove
the repeated definitions from individual test classes; update imports/usage so
tests reference the single fixtures (test_data_dir, go_diff_file,
react_diff_file) to ensure consistent skip behavior across tests.

315-320: Avoid strict wall-clock thresholds in this unit test.

list_time < 2.0 is CI-environment sensitive and can produce intermittent failures unrelated to correctness. Consider moving hard timing checks to perf/benchmark tests and keep this test focused on functional assertions.

Proposed adjustment
-        assert list_time < 2.0, f"List chunks took too long: {list_time}s"
-        assert len(list_result["chunks"]) == result["chunks"]
+        # Keep unit test deterministic: verify behavior, not machine-dependent timing.
+        assert len(list_result["chunks"]) == result["chunks"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mcp_components.py` around lines 315 - 320, The test currently
enforces a strict wall-clock assertion on list_chunks by computing list_time and
asserting list_time < 2.0; remove or disable this timing assertion and keep the
functional checks (i.e., retain the call to tools.list_chunks(go_diff_file) and
the assert on len(list_result["chunks"]) == result["chunks"]). If you still need
performance coverage, move the timing check into a separate perf/benchmark test
(or change the assertion to a non-failing warning/log) so the unit test remains
deterministic and CI-stable; target the symbols list_chunks, list_result,
list_time and the timing assert for the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_mcp_components.py`:
- Around line 1083-1087: The test test_context_lines_negative_raises_valueerror
uses a non-existent path which can cause file-existence validation to trigger
before the context_lines validation; update the test to pass a valid diff file
to DiffChunkTools.load_diff (e.g., create a minimal valid diff via a fixture or
tmp_path and pass that path) so the call to DiffChunkTools.load_diff(...)
exercises the context_lines validation path with context_lines=-1; reference
DiffChunkTools and its load_diff method when making this change.

---

Nitpick comments:
In `@tests/test_mcp_components.py`:
- Around line 840-859: Move the duplicated pytest fixtures into a shared
module-level fixture set and update tests to use them: extract the functions
test_data_dir, go_diff_file, and react_diff_file into a top-level (module or
conftest) fixture module so they are defined once (retain the same names and
behavior, including the pytest.skip logic) and remove the repeated definitions
from individual test classes; update imports/usage so tests reference the single
fixtures (test_data_dir, go_diff_file, react_diff_file) to ensure consistent
skip behavior across tests.
- Around line 315-320: The test currently enforces a strict wall-clock assertion
on list_chunks by computing list_time and asserting list_time < 2.0; remove or
disable this timing assertion and keep the functional checks (i.e., retain the
call to tools.list_chunks(go_diff_file) and the assert on
len(list_result["chunks"]) == result["chunks"]). If you still need performance
coverage, move the timing check into a separate perf/benchmark test (or change
the assertion to a non-failing warning/log) so the unit test remains
deterministic and CI-stable; target the symbols list_chunks, list_result,
list_time and the timing assert for the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 46873719-9020-47da-938c-24c8e308a7d7

📥 Commits

Reviewing files that changed from the base of the PR and between e2d6174 and 1c2570b.

📒 Files selected for processing (1)
  • tests/test_mcp_components.py

Preserve no-hunk file sections (binary, rename) in formatted output,
remove dead code in parser, and harden test assertions.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_mcp_components.py (1)

47-57: ⚠️ Potential issue | 🟡 Minor

Add per-chunk token_count assertions to cover the full list_chunks contract.

The test validates total_token_count but not each chunk’s token_count, which is part of this PR’s budgeting feature surface.

Suggested test hardening
         assert "chunks" in result
         assert "total_token_count" in result
         assert isinstance(result["total_token_count"], int)
         chunks = result["chunks"]
         assert len(chunks) == total_chunks
         assert all("chunk" in chunk for chunk in chunks)
+        assert all("token_count" in chunk for chunk in chunks)
+        assert all(
+            isinstance(chunk["token_count"], int) and chunk["token_count"] >= 0
+            for chunk in chunks
+        )
         assert all("files" in chunk for chunk in chunks)
         assert all("lines" in chunk for chunk in chunks)
         assert all("summary" in chunk for chunk in chunks)
+        assert sum(chunk["token_count"] for chunk in chunks) == result["total_token_count"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mcp_components.py` around lines 47 - 57, The test for
tools.list_chunks is missing per-chunk token_count checks; update the test in
tests/test_mcp_components.py after calling tools.list_chunks and obtaining
chunks to assert each chunk contains a "token_count" key, that
chunk["token_count"] is an int (or non-negative int), and that the sum of all
chunk["token_count"] values equals result["total_token_count"]; use the existing
variables result, chunks and total_chunks and keep the other chunk assertions
intact.
🧹 Nitpick comments (2)
tests/test_mcp_components.py (2)

980-988: test_reduce_context_none_keeps_all is mislabeled for what it actually verifies.

Line 987 calls DiffParser.reduce_context(diff, 100), so this test checks “large context value keeps all lines,” not the context_lines=None path in DiffChunkTools.load_diff(...).

Minimal clarity fix
-    def test_reduce_context_none_keeps_all(self):
-        """context_lines=None (not calling reduce_context) keeps all context."""
+    def test_reduce_context_large_value_keeps_all(self):
+        """Large context_lines value preserves all available context lines."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mcp_components.py` around lines 980 - 988, The test name/test
intent mismatch: test_reduce_context_none_keeps_all actually calls
DiffParser.reduce_context(diff, 100) which verifies that a large numeric context
keeps lines, not the context_lines=None branch in DiffChunkTools.load_diff;
either rename the test to reflect "large context keeps all" (e.g.,
test_reduce_context_large_keeps_all) or change the test to exercise the None
path by invoking DiffChunkTools.load_diff(..., context_lines=None) (or calling
the method that dispatches to that behavior) and asserting that all context
lines are kept; update references to DiffParser.reduce_context and
DiffChunkTools.load_diff accordingly so the test name and implementation match.

533-539: Line-number assertions are too permissive ("6" in ln / "22" in ln).

These can pass on incorrect values like 16 or 122. Prefer exact leading-number checks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mcp_components.py` around lines 533 - 539, The assertions that
check line numbers are too permissive because they use substring checks ("6" in
ln / "22" in ln); update the checks around add1_lines and add2_lines to assert
the line number exactly at the start of the line (e.g., use ln.startswith("6")
or a regex like re.match(r'^\s*6\b', ln) for add1_lines and similarly for 22 for
add2_lines) so that "16" or "122" won't match; locate and modify the assertions
referencing add1_lines, add2_lines and lines in the test to perform exact
leading-number matching.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@tests/test_mcp_components.py`:
- Around line 47-57: The test for tools.list_chunks is missing per-chunk
token_count checks; update the test in tests/test_mcp_components.py after
calling tools.list_chunks and obtaining chunks to assert each chunk contains a
"token_count" key, that chunk["token_count"] is an int (or non-negative int),
and that the sum of all chunk["token_count"] values equals
result["total_token_count"]; use the existing variables result, chunks and
total_chunks and keep the other chunk assertions intact.

---

Nitpick comments:
In `@tests/test_mcp_components.py`:
- Around line 980-988: The test name/test intent mismatch:
test_reduce_context_none_keeps_all actually calls
DiffParser.reduce_context(diff, 100) which verifies that a large numeric context
keeps lines, not the context_lines=None branch in DiffChunkTools.load_diff;
either rename the test to reflect "large context keeps all" (e.g.,
test_reduce_context_large_keeps_all) or change the test to exercise the None
path by invoking DiffChunkTools.load_diff(..., context_lines=None) (or calling
the method that dispatches to that behavior) and asserting that all context
lines are kept; update references to DiffParser.reduce_context and
DiffChunkTools.load_diff accordingly so the test name and implementation match.
- Around line 533-539: The assertions that check line numbers are too permissive
because they use substring checks ("6" in ln / "22" in ln); update the checks
around add1_lines and add2_lines to assert the line number exactly at the start
of the line (e.g., use ln.startswith("6") or a regex like re.match(r'^\s*6\b',
ln) for add1_lines and similarly for 22 for add2_lines) so that "16" or "122"
won't match; locate and modify the assertions referencing add1_lines, add2_lines
and lines in the test to perform exact leading-number matching.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 817e3d7e-9607-437d-9ebb-faa65ef3e7ae

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2570b and cc26a6f.

📒 Files selected for processing (3)
  • src/formatter.py
  • src/parser.py
  • tests/test_mcp_components.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/formatter.py
  • src/parser.py

@peteretelej peteretelej merged commit 6fd6541 into main Apr 9, 2026
8 checks passed
@peteretelej peteretelej deleted the review-usefulness branch April 9, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant