Skill

render-diff

MANDATORY: Use BEFORE showing ANY diff to user - transforms git diff into 4-column table with box characters (╭╮╰╯│). Required for approval gates, code reviews, change summaries.

From cat
Install
1
Run in your terminal
$
npx claudepluginhub cowwoc/cat --plugin cat
Tool Access

This skill uses the workspace's default tool permissions.

Skill Content

Render Diff

Purpose

Transform raw git diff output into a 4-column table format optimized for approval gate reviews. Each hunk is rendered as a self-contained box with file header, making diffs easy to review.

Pre-computed Output (MANDATORY for Approval Gates)

Check context for "PRE-COMPUTED RENDER-DIFF OUTPUT".

If found:

  1. Output the rendered diff content directly - no preamble, no Bash commands
  2. The content is already formatted with 4-column tables and box characters
  3. Do NOT wrap in code blocks or show any tool invocations

If NOT found during an approval gate: FAIL immediately.

"${CLAUDE_PLUGIN_ROOT}/scripts/check-hooks-loaded.sh" "diff output" "render-diff"
if [[ $? -eq 0 ]]; then
  echo "ERROR: Pre-computed diff not found. Check:"
  echo "1. Handler is registered in skill_handlers/__init__.py"
  echo "2. Handler file exists in plugin/hooks/skill_handlers/"
  echo "3. Base branch is detectable from worktree/branch name"
fi

Output the error and STOP. Do NOT attempt manual Bash computation.

Why this matters (M238): Running git diff | render-diff.py via Bash shows the command execution to the user, breaking the clean output experience. Pre-computation hides the implementation details.

Usage

Pipe git diff output to the script:

git diff main..HEAD | "${CLAUDE_PLUGIN_ROOT}/scripts/render-diff.py"

Or provide a diff file:

"${CLAUDE_PLUGIN_ROOT}/scripts/render-diff.py" diff-output.txt

Configuration

The script reads terminalWidth from .claude/cat/cat-config.json:

{
  "terminalWidth": 50
}

Output Format

Hunk Box Structure

Each hunk is a self-contained box with file header repeated:

╭────────────────────────────────────────────────╮
│ FILE: src/main.js                              │
├────┬───┬────┬──────────────────────────────────┤
│ Old│   │ New│ ⌁ function init()                │
├────┼───┼────┼──────────────────────────────────┤
│   6│   │   6│   const app = express();         │
│   8│ - │    │   app.use([bodyParser].json());  │
│    │ + │   8│   app.use([express].json());     │
│   9│   │   9│   return app;                    │
╰────┴───┴────┴──────────────────────────────────╯

Column Definitions

ColumnWidthContent
Old4 charsLine number in original file (blank for additions)
Symbol3 chars- removed, + added, blank for context
New4 charsLine number in new file (blank for deletions)
ContentremainingThe actual line text

Features

Hunk Context: Function/class name from git shown in header row with :

│ Old│   │ New│ ⌁ function doSomething()         │

Word-Level Diff: Adjacent -/+ pairs highlight changed portions with []:

│   8│ - │    │   app.use([bodyParser].json());  │
│    │ + │   8│   app.use([express].json());     │

Whitespace Visibility: Tab↔space changes shown with markers:

│  15│ - │    │ →const indent = 1;               │
│    │ + │  15│ ····const indent = 1;            │
  • · (middle dot) for spaces
  • for tabs

Line Wrapping: Long lines wrap with :

│  46│ - │    │   logger.info(`Server running ↩  │
│    │   │    │ on port ${port}`);               │

Binary Files:

╭────────────────────────────────────────────────╮
│ FILE: logo.png (binary)                        │
├────────────────────────────────────────────────┤
│ Binary file changed                            │
╰────────────────────────────────────────────────╯

Renamed Files:

╭────────────────────────────────────────────────╮
│ FILE: old/path.js → new/path.js (renamed)      │
├────────────────────────────────────────────────┤
│ File renamed (no content changes)              │
╰────────────────────────────────────────────────╯

Legend

Appears once at end, showing only symbols used:

╭────────────────────────────────────────────────╮
│ Legend                                         │
├────────────────────────────────────────────────┤
│  -  del    +  add    []  changed    ·  space   │
╰────────────────────────────────────────────────╯

Example

Input:

git diff main..HEAD | render-diff.py

Output (multiple hunks in same file):

╭────────────────────────────────────────────────╮
│ FILE: src/api.js                               │
├────┬───┬────┬──────────────────────────────────┤
│ Old│   │ New│ ⌁ function init()                │
├────┼───┼────┼──────────────────────────────────┤
│   6│   │   6│   const app = express();         │
│   8│ - │    │   app.use([bodyParser].json());  │
│    │ + │   8│   app.use([express].json());     │
│   9│   │   9│   return app;                    │
╰────┴───┴────┴──────────────────────────────────╯

╭────────────────────────────────────────────────╮
│ FILE: src/api.js                               │
├────┬───┬────┬──────────────────────────────────┤
│ Old│   │ New│ ⌁ function start(port)           │
├────┼───┼────┼──────────────────────────────────┤
│  45│   │  45│   app.listen(port, () => {       │
│    │ + │  46│     logEnvironment();            │
│  46│   │  47│   });                            │
╰────┴───┴────┴──────────────────────────────────╯

╭────────────────────────────────────────────────╮
│ Legend                                         │
├────────────────────────────────────────────────┤
│  -  del    +  add    []  changed               │
╰────────────────────────────────────────────────╯

Integration with Approval Gates

Complete File Coverage (MANDATORY)

Before invoking render-diff, enumerate ALL changed files to ensure complete coverage:

# Step 1: List all changed files
git diff --name-only "${BASE_BRANCH}..HEAD"

# Step 2: Generate diff for ALL files (no path filtering)
git diff "${BASE_BRANCH}..HEAD" | \
  "${CLAUDE_PLUGIN_ROOT}/scripts/render-diff.py" > /tmp/review-diff.txt

# Step 3: Display for approval
cat /tmp/review-diff.txt

Anti-pattern: Manually specifying paths based on memory. This leads to incomplete diffs.

# ❌ WRONG - manual path specification misses files
git diff v2.0..HEAD -- plugin/scripts/ plugin/skills/

# ✅ CORRECT - diff entire branch, no path filtering
git diff v2.0..HEAD

Related Skills

  • cat:stakeholder-review - Uses render-diff for showing changes to reviewers
Similar Skills
cache-components

Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.

138.5k
Stats
Parent Repo Stars78
Parent Repo Forks1
Last CommitFeb 13, 2026