Skip to content

feat(phase4): terminal formatter — ranked review path for wild diff#19

Open
avikalpg wants to merge 1 commit into
mainfrom
nia/v2-terminal-formatter
Open

feat(phase4): terminal formatter — ranked review path for wild diff#19
avikalpg wants to merge 1 commit into
mainfrom
nia/v2-terminal-formatter

Conversation

@avikalpg

@avikalpg avikalpg commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Phase 4 — Terminal Formatter

Status: 14 tests passing · Standalone — no dependency on Phases 1–3
Spec: docs/DiffGraph-CLI/design/TERMINAL-FORMATTER.md
Roadmap: docs/DiffGraph-CLI/design/V2-IMPLEMENTATION-ROADMAP.md Phase 4


What this does

Adds TerminalFormatter in diffgraph/formatters/terminal.py.

When wired as wild diff --format terminal, instead of opening diffgraph.html in a browser, the tool prints a priority-ranked review path to the terminal:

wild diff — 7 files changed  ·  3 symbols modified  ·  2 added  ·  1 deleted

▶ REVIEW FIRST
  auth/validator.py  validate_token [modified]
    ↳ imported by: api/routes.py, middleware/auth.py
    ↳ 29 lines changed

▶ REVIEW NEXT
  auth/validator.py  RateLimiter [added]  · 28 lines
  tests/test_validator.py  test_rate_limit_basic [added]  · 12 lines

────────────────────────────────────────────────────────────────────────
Analysis: structural · Python · tree-sitter · 840ms

Files changed

File Purpose
diffgraph/formatters/__init__.py New formatters package
diffgraph/formatters/terminal.py TerminalFormatter class (430 lines)
tests/test_terminal_formatter.py 14 unit tests, all pure functions

How the ranking works

Three-bucket ranking, fully evidence-based (no LLM required):

  1. REVIEW FIRST — symbols imported by other changed files. Cross-file changes carry ripple risk.
  2. REVIEW NEXT — isolated changes (not imported by anything else in the diff).
  3. CONTEXT — unchanged symbols in touched files (background, not urgently actionable).

Score formula: importers × 3 + lines_changed — importers weighted heavier than lines.


Acceptance criteria (all met)

  • diffgraph/formatters/terminal.py created with TerminalFormatter
  • _rank_symbols() is a pure function (14 tests pass; no stdout capture needed)
  • --compact omits CONTEXT section
  • --all disables 10-symbol truncation cap
  • NO_COLOR=1 and piped stdout suppress ANSI
  • File-level fallback when symbol extraction unavailable
  • Footer shows analysis tier, languages, duration, diff context
  • 14 unit tests pass; zero network/git calls in tests

B1 decision point

The formatter ships as wild diff --format terminal (opt-in). Whether this becomes the default output depends on B1:

  • B1 = yes → change cli.py default from html to terminal (1-line change)
  • B1 = no/browser → this PR ships as-is; browser remains default

This PR is mergeable regardless of B1. The B1 change is a trivial 1-line follow-up.


Phase context

This is Phase 4 of the V2 implementation roadmap. It is standalone — it does not depend on Phases 1, 2, or 3 merging first. The formatter is a pure consumer of any schema v2 dict and can be reviewed and merged independently.

The critical unblocked path:

Summary by CodeRabbit

  • New Features

    • Added a terminal review view for DiffGraph changes.
    • Highlights symbols in prioritized sections: Review First, Review Next, and Context.
    • Displays file and symbol counts, change details, importer hints, warnings, and analysis metadata.
    • Supports compact output, item limits, full-list output, and automatic color handling.
    • Provides clear fallback output when symbol details are unavailable.
  • Bug Fixes

    • Improved terminal readability for piped output and environments without color support.

TerminalFormatter renders a schema v2 DiffGraph dict as a priority-ranked
terminal output. This is the main user-facing change in DiffGraph v2:
'wild diff' stops opening a browser and instead prints a ranked review path
to the terminal.

Key features:
- Three-bucket ranking: REVIEW FIRST (imported by changed files), REVIEW NEXT
  (isolated changes), CONTEXT (unchanged symbols in touched files)
- Score = importer_count * 3 + lines_changed; evidence-based, LLM-free
- Symbol truncation cap (default 10) with --all override
- ANSI color with NO_COLOR and TTY auto-detection
- --compact flag omits CONTEXT section
- File-level fallback when symbol extraction unavailable
- Footer shows analysis tier, languages, duration, diff context
- 14 unit tests; all pass; pure functions, no git/network

Wires as 'wild diff --format terminal'. Default swap (browser → terminal)
pending B1 answer from Avikalp. Can ship as --format terminal opt-in today.

Spec: docs/DiffGraph-CLI/design/TERMINAL-FORMATTER.md
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a schema v2 terminal formatter that ranks symbols by review priority, renders colored or plain terminal output, supports compact and truncation options, and includes tests for ranking and rendering behavior.

Changes

Terminal formatter

Layer / File(s) Summary
Formatter package and options
diffgraph/formatters/__init__.py, diffgraph/formatters/terminal.py, tests/test_terminal_formatter.py
Creates the formatter package, exports TerminalFormatter, adds color controls and formatter configuration, and defines shared DiffGraph test fixtures.
Rank DiffGraph symbols
diffgraph/formatters/terminal.py, tests/test_terminal_formatter.py
Ranks symbols into review buckets using change kinds, importer relationships, and estimated changed lines, with tests for ordering and bucket allocation.
Render review paths
diffgraph/formatters/terminal.py, tests/test_terminal_formatter.py
Renders headers, warnings, sections, symbol details, fallbacks, metadata, truncation, compact mode, color handling, importer collapsing, and full-output mode, with corresponding tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DiffGraph
  participant TerminalFormatter
  participant TerminalOutput
  DiffGraph->>TerminalFormatter: provide schema v2 dictionary
  TerminalFormatter->>TerminalFormatter: rank symbols and build sections
  TerminalFormatter->>TerminalOutput: write formatted review path
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: a terminal formatter that renders a ranked review path for wild diff.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 nia/v2-terminal-formatter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
tests/test_terminal_formatter.py (2)

369-374: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer unpacking over list concatenation (Ruff RUF005).

🔧 Proposed fix
-    files = importer_files + [target_file]
+    files = [*importer_files, target_file]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_terminal_formatter.py` around lines 369 - 374, Update the files
construction near importer_files and target_file to use iterable unpacking
rather than list concatenation, preserving the existing order and resulting list
contents.

Source: Linters/SAST tools


71-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Implicit Optional on _make_diffgraph params (Ruff RUF013).

files, symbols, relationships, metadata, and diff_ref are typed as bare list/dict with None defaults, which PEP 484 prohibits implicitly.

🔧 Proposed fix
+from typing import Optional
+
 def _make_diffgraph(
-    files: list = None,
-    symbols: list = None,
-    relationships: list = None,
-    metadata: dict = None,
-    diff_ref: dict = None,
+    files: Optional[list] = None,
+    symbols: Optional[list] = None,
+    relationships: Optional[list] = None,
+    metadata: Optional[dict] = None,
+    diff_ref: Optional[dict] = None,
 ) -> dict:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_terminal_formatter.py` around lines 71 - 77, Update the
`_make_diffgraph` parameters `files`, `symbols`, `relationships`, `metadata`,
and `diff_ref` to explicitly use nullable `Optional` type annotations while
retaining their existing `None` defaults and return type.

Source: Linters/SAST tools

diffgraph/formatters/terminal.py (1)

307-337: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

section_style parameter is accepted but never used.

_write_section hardcodes header styling via if title == "REVIEW FIRST" / elif title == "CONTEXT" branches, ignoring the section_style argument passed by every caller (Line 155-158). This currently renders correctly only because the branches happen to match the caller-supplied styles — any future call with a different section_style would be silently ignored.

♻️ Use the passed style instead of re-deriving it from title
+_STYLE_FNS = {"bold_yellow": bold_yellow, "bold": bold, "dim": dim}
+
 def _write_section(
     self,
     title: str,
     items: list[_RankedSymbol],
     out,
     color: bool,
     section_style: str = "bold",
 ) -> None:
     ...
     prefix = "▶ " if not _is_dumb_terminal() else "> "
     header_text = f"{prefix}{title}"
-    if title == "REVIEW FIRST":
-        header = bold_yellow(header_text, color)
-    elif title == "CONTEXT":
-        header = dim(header_text, color)
-    else:
-        header = bold(header_text, color)
+    header = _STYLE_FNS.get(section_style, bold)(header_text, color)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@diffgraph/formatters/terminal.py` around lines 307 - 337, Update
_write_section to derive the header styling from its section_style parameter
instead of matching title values. Preserve the existing bold-yellow, dim, and
bold behavior by mapping the caller-provided styles accordingly, and ensure all
callers’ current rendering remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@diffgraph/formatters/terminal.py`:
- Around line 307-337: Update _write_section to derive the header styling from
its section_style parameter instead of matching title values. Preserve the
existing bold-yellow, dim, and bold behavior by mapping the caller-provided
styles accordingly, and ensure all callers’ current rendering remains unchanged.

In `@tests/test_terminal_formatter.py`:
- Around line 369-374: Update the files construction near importer_files and
target_file to use iterable unpacking rather than list concatenation, preserving
the existing order and resulting list contents.
- Around line 71-77: Update the `_make_diffgraph` parameters `files`, `symbols`,
`relationships`, `metadata`, and `diff_ref` to explicitly use nullable
`Optional` type annotations while retaining their existing `None` defaults and
return type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f9c5dc8d-8d25-4b84-ab00-8df54579687c

📥 Commits

Reviewing files that changed from the base of the PR and between 846cb9e and 12971bf.

📒 Files selected for processing (3)
  • diffgraph/formatters/__init__.py
  • diffgraph/formatters/terminal.py
  • tests/test_terminal_formatter.py

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