# @heyhuynhgiabuu/pi-diff

> Shiki-powered terminal diff renderer for pi — syntax-highlighted, word-level diffs in split and unified views.

Latest version **0.9.1** (published 2026-09-10) · MIT license · 492 weekly downloads

## Install

```sh
npm install @heyhuynhgiabuu/pi-diff
pnpm add @heyhuynhgiabuu/pi-diff
yarn add @heyhuynhgiabuu/pi-diff
bun add @heyhuynhgiabuu/pi-diff
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.9.1 |
| Published | 2026-09-10 |
| First published | 2026-03-25 |
| Weekly downloads | 492 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 6 |
| Unpacked size | 1004.3 KB |
| Known vulnerabilities | 0 (+1 in 1 direct dependencies) |
| Install scripts | no |
| GitHub stars | 44 |
| Author | huynhgiabuu |
| Maintainers | killerkidbo |
| Keywords | diff, pi, pi-extension, pi-package, shiki, syntax-highlighting, terminal |

## Links

- npm: https://www.npmjs.com/package/@heyhuynhgiabuu/pi-diff
- Repository: https://github.com/buddingnewinsights/pi-diff
- Homepage: https://github.com/buddingnewinsights/pi-diff#readme
- Issues: https://github.com/buddingnewinsights/pi-diff/issues
- npm.io page: https://npm.io/package/@heyhuynhgiabuu/pi-diff

## Dependencies (6)

- [diff](https://npm.io/package/diff.md) ^7.0.0
- [xxhash-wasm](https://npm.io/package/xxhash-wasm.md) ^1.1.0
- [@shikijs/cli](https://npm.io/package/@shikijs/cli.md) ^4.0.2
- [@earendil-works/pi-tui](https://npm.io/package/@earendil-works/pi-tui.md) ^0.85.0
- [@earendil-works/pi-server](https://npm.io/package/@earendil-works/pi-server.md) ^0.85.0
- [@earendil-works/pi-coding-agent](https://npm.io/package/@earendil-works/pi-coding-agent.md) ^0.85.0

## Alternatives

- [@oh-my-pi/pi-natives](https://npm.io/package/@oh-my-pi/pi-natives.md) — 51.8K weekly downloads
- [@capgo/capacitor-light-sensor](https://npm.io/package/@capgo/capacitor-light-sensor.md) — 3.0K weekly downloads
- [@lotsa/verdant-lang-asm](https://npm.io/package/@lotsa/verdant-lang-asm.md) — 38 weekly downloads
- [new-era-syntax](https://npm.io/package/new-era-syntax.md) — 20 weekly downloads
- [angular-ui-block](https://npm.io/package/angular-ui-block.md) — 8 weekly downloads

## Recent versions

- 0.9.1 (latest) — 2026-09-10
- 0.9.0 — 2026-09-09
- 0.8.2 — 2026-09-08
- 0.8.1 — 2026-08-23
- 0.8.0 — 2026-08-12
- 0.7.6 — 2026-07-21
- 0.7.5 — 2026-07-11
- 0.7.4 — 2026-07-08
- 0.7.3 — 2026-07-08
- 0.7.2 — 2026-07-05
- 0.7.1 — 2026-07-03
- 0.7.0 — 2026-07-03
- 0.6.10 — 2026-07-01
- 0.6.9 — 2026-07-01
- 0.6.8 — 2026-07-01
- … 27 more at https://npm.io/package/@heyhuynhgiabuu/pi-diff/versions

## README

# pi-diff

[![npm version](https://img.shields.io/npm/v/@heyhuynhgiabuu/pi-diff)](https://www.npmjs.com/package/@heyhuynhgiabuu/pi-diff)
[![GitHub release](https://img.shields.io/github/v/release/buddingnewinsights/pi-diff)](https://github.com/buddingnewinsights/pi-diff/releases/latest)

A [pi](https://pi.dev) extension that replaces the default `write` and `edit` tool output with **Shiki-powered, syntax-highlighted diffs** — side-by-side split view, unified stacked view, and word-level change emphasis, all rendered directly in your terminal.

> **Status:** Early release.

### Unified view — stacked single-column diff

<img width="700" alt="pi-diff unified view" src="https://github.com/buddingnewinsights/pi-diff/raw/main/media/unified.png" />

### Split view — side-by-side comparison

<img width="700" alt="pi-diff split view" src="https://github.com/buddingnewinsights/pi-diff/raw/main/media/split.png" />

## Features

- **Syntax-highlighted diffs** — full Shiki grammar highlighting (190+ languages) composited with diff background colors
- **Split view** — side-by-side comparison for `edit` tool, auto-falls back to unified on narrow terminals
- **Unified view** — stacked single-column layout for `write` tool overwrites
- **Word-level emphasis** — changed characters get brighter backgrounds so you see exactly what changed
- **New file preview** — syntax-highlighted preview when creating files
- **Adaptive layout** — auto-detects terminal width; wraps intelligently on wide terminals, truncates on narrow ones
- **LRU cache** — singleton Shiki highlighter with 192-entry cache for fast re-renders
- **Large diff fallback** — gracefully degrades (skips highlighting, still shows diff structure) for files > 80k chars
- **Fully customizable** — every color and threshold is overridable via environment variables
- **Strict edit safety** — delegates matching, uniqueness, overlap checks, mutation queues, aborts, BOM, and EOL preservation to Pi's SDK edit tool

## Install

```bash
pi install npm:@heyhuynhgiabuu/pi-diff
```

Latest release: https://github.com/buddingnewinsights/pi-diff/releases/latest

Or load directly for development:

```bash
pi -e ./src/index.ts
```

## How It Works

pi-diff wraps the built-in `write` and `edit` tools from the pi SDK, including single-edit and multi-edit `edit` calls. When the agent writes or edits a file:

1. **Before the write** — reads the existing file content
2. **Delegates** to the original SDK tool (file is actually written)
3. **After the write** — computes a structured diff between old and new content
4. **Renders** the diff with syntax highlighting and word-level emphasis

For `edit` calls, Pi's SDK performs matching, uniqueness and overlap validation, mutation queueing, and the file write. pi-diff adapts the SDK's returned unified patch into its syntax-highlighted renderer, so the preview reflects the actual matched source rather than the requested text.

### `apply_patch` safety contract

`apply_patch` remains a separate structured JSON tool for multi-file add, update, delete, and move operations. Paths are resolved relative to Pi's current workspace and must remain inside it; ancestor symlinks are rejected. Updates may use one `oldText`/`newText` replacement or an `edits` array of disjoint replacements matched against the original file. Matching is exact first, then tolerates escaped sequences and Unicode/trailing-whitespace drift; a match is accepted only when it is unique, and ambiguous or missing `oldText` fails with a specific error.

All changes are prepared before the first commit. A commit failure triggers reverse-order rollback, but filesystem rollback is necessarily best effort; callers should treat a failed result as requiring verification. Existing files are never intentionally clobbered by `add` or `move`, and invalid UTF-8 files are rejected rather than rewritten as text.

The rendering pipeline:

```
Old content ──┐
              ├── diff (structuredPatch) ── parse ── highlight (Shiki → ANSI)
New content ──┘                                          │
                                                         ├── inject diff bg
                                                         ├── inject word-level bg
                                                         └── wrap/fit to terminal
```

### Views

| View        | Used by      | Description                                                                                                                                                       |
| ----------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Split**   | `edit` tool  | Side-by-side with old on left, new on right. Diagonal stripes fill empty slots. Auto-falls back to unified when terminal < 150 cols or > 20% of lines would wrap. |
| **Unified** | `write` tool | Single column with `+`/`-` gutter. Compact, works at any terminal width.                                                                                          |

Both views show:

- Colored border bars (`▌`) for changed lines
- Line numbers in the gutter
- Hunk separators (`··· N unmodified lines ···`)
- Word-level emphasis on paired add/del lines

## Configuration

### Disable pi-diff tools

Add `disabledTools` to `pi-diff.json` to omit pi-diff tools. Disabling `write` or `edit` leaves Pi's built-in tool available; disabling `apply_patch` removes that custom tool.

```json
{
  "disabledTools": ["apply_patch"]
}
```

See [CONFIG.md](CONFIG.md) for the full `pi-diff.json` reference.

### Diff Theme Presets

pi-diff ships with built-in theme presets optimized for different terminal backgrounds. Add to your `.pi/settings.json`:

```json
{
  "theme": "dark",
  "diffTheme": "midnight"
}
```

| Preset     | Best for                         | Description                                               |
| ---------- | -------------------------------- | --------------------------------------------------------- |
| `default`  | Dark theme bases (~`#1e1e2e`)    | Original pi-diff colors — balanced contrast               |
| `midnight` | Pure black (`#000000`) terminals | Subtle tints that don't overwhelm on black                |
| `subtle`   | Any dark theme                   | Minimal backgrounds — barely-there tints for a clean look |
| `neon`     | Low-contrast displays            | Higher contrast backgrounds for better visibility         |

### Per-Color Overrides

Override individual diff colors in `.pi/settings.json` using hex `#RRGGBB` values:

```json
{
  "theme": "dark",
  "diffTheme": "midnight",
  "diffColors": {
    "bgAdd": "#0d1a12",
    "bgDel": "#1a0d0d",
    "bgAddHighlight": "#1a3825",
    "bgDelHighlight": "#381a1a",
    "bgGutterAdd": "#091208",
    "bgGutterDel": "#120908",
    "bgEmpty": "#080808",
    "fgAdd": "#64b478",
    "fgDel": "#c86464",
    "fgDim": "#404040",
    "fgLnum": "#505050",
    "fgRule": "#282828",
    "fgStripe": "#1e1e1e",
    "fgSafeMuted": "#8b949e",
    "shikiTheme": "github-dark"
  }
}
```

`diffColors` overrides take priority over `diffTheme` presets, so you can start from a preset and tweak individual colors.

### Auto-Derive (Default Behavior)

When no `diffTheme` or `diffColors` is set, pi-diff **automatically derives** background colors from your pi theme's diff foreground colors and tool-state backgrounds. Added/context surfaces use `toolSuccessBg`; removed surfaces use `toolErrorBg`. This ensures diffs look good with any pi theme and terminal background — no configuration needed.

The auto-derive uses different intensity levels:

- **Line backgrounds**: 8–10% of the theme's diff fg color mixed into the matching tool-state background (subtle tint)
- **Word highlights**: 20–22% (more visible for changed characters)
- **Gutters**: 5–6% (subtler than line backgrounds)

### Color Resolution Order

For each color, pi-diff checks (highest priority first):

1. **Environment variable** — e.g. `DIFF_BG_ADD="#1a3320"` (backward compatible)
2. **`diffColors`** from `.pi/settings.json` (per-color hex overrides)
3. **`diffTheme` preset** from `.pi/settings.json` (named preset bundle)
4. **Auto-derived** from pi theme's `toolDiffAdded`/`toolDiffRemoved` colors
5. **Hardcoded fallback** (original defaults)

### Environment Variables

All settings are also controllable via environment variables. Add them to your shell profile or `.envrc`:

### Theme

| Variable     | Default       | Description                                                            |
| ------------ | ------------- | ---------------------------------------------------------------------- |
| `DIFF_THEME` | `github-dark` | Shiki theme name (e.g., `dracula`, `one-dark-pro`, `catppuccin-mocha`) |

### Colors

Override any diff color with hex `#RRGGBB` format:

| Variable             | Default   | Description                                 |
| -------------------- | --------- | ------------------------------------------- |
| `DIFF_BG_ADD`        | `#162620` | Background for added lines                  |
| `DIFF_BG_DEL`        | `#2d1919` | Background for removed lines                |
| `DIFF_BG_ADD_HL`     | `#234b32` | Word-level emphasis on added text           |
| `DIFF_BG_DEL_HL`     | `#502323` | Word-level emphasis on removed text         |
| `DIFF_BG_GUTTER_ADD` | `#12201a` | Gutter background for added lines           |
| `DIFF_BG_GUTTER_DEL` | `#261616` | Gutter background for removed lines         |
| `DIFF_FG_ADD`        | `#64b478` | Foreground for `+` signs and add indicators |
| `DIFF_FG_DEL`        | `#c86464` | Foreground for `-` signs and del indicators |

### Layout

| Variable                    | Default | Description                                 |
| --------------------------- | ------- | ------------------------------------------- |
| `DIFF_SPLIT_MIN_WIDTH`      | `150`   | Minimum terminal columns to use split view  |
| `DIFF_SPLIT_MIN_CODE_WIDTH` | `60`    | Minimum code columns per side in split view |

### Example `.envrc`

```bash
# Use a different Shiki theme
export DIFF_THEME="catppuccin-mocha"

# Brighter add backgrounds
export DIFF_BG_ADD="#1a3a25"
export DIFF_BG_ADD_HL="#2d6040"

# Allow split view on narrower terminals
export DIFF_SPLIT_MIN_WIDTH=120
```

## Architecture

```
src/
├── index.ts            # Extension entry point — wraps write/edit tools with diff rendering
├── core/               # Pure data layer: diff parsing, conflict detection, line resolution
│   ├── config.ts
│   ├── conflicts.ts
│   ├── diff.ts         # parseDiff, parsePatchFiles, resolveSepStyle
│   ├── replace.ts      # conservative apply_patch matcher and legacy helpers
│   ├── resolve-lines.ts
│   └── *.test.ts
└── review/             # Shared diff-rendering primitives used by the main extension
    ├── git.ts          # read git diffs from disk (execFileSync)
    └── hunk-preview.ts # renderSplit, renderUnified, theme helpers (re-exported via __testing)
```

### Key internals

All listed symbols are exposed under the `__testing` export for unit tests:

| Symbol                     | Source                  | Purpose                                                                 |
| -------------------------- | ----------------------- | ----------------------------------------------------------------------- |
| `parseDiff()`              | `core/diff.ts`          | Convert old/new content to structured `DiffLine[]` (added/removed/chars) |
| `parsePatchFiles()`        | `core/diff.ts`          | Parse a full `git apply` patch into per-file diff objects               |
| `resolveSepStyle()`        | `core/diff.ts`          | Pick the per-side separator style (` │ ` vs `╱`) for split view         |
| `getSepStyle()`            | `review/hunk-preview.ts`| Same as above for the shared rendering layer                            |
| `computeHunkBlocks()`      | `review/hunk-preview.ts`| Build add/remove/modify line blocks with column ranges for split view   |
| `renderSplit()`            | `review/hunk-preview.ts`| Side-by-side renderer with diagonal stripe fillers                      |
| `renderUnified()`          | `review/hunk-preview.ts`| Stacked single-column renderer                                          |
| `normalizeShikiContrast()` | `review/hunk-preview.ts` | Lighten/darken Shiki output to match the surrounding TUI theme        |

### Rendering constants

| Constant            | Value  | Description                                |
| ------------------- | ------ | ------------------------------------------ |
| `MAX_PREVIEW_LINES` | 60     | Max lines in edit preview (split view)     |
| `MAX_RENDER_LINES`  | 150    | Max lines in write result (unified view)   |
| `MAX_HL_CHARS`      | 80,000 | Skip syntax highlighting above this        |
| `CACHE_LIMIT`       | 192    | LRU cache entries for highlighted blocks   |
| `WORD_DIFF_MIN_SIM` | 0.15   | Minimum similarity for word-level emphasis |

## Exports

The extension exports a `__testing` object for unit testing:

```typescript
import { __testing } from "@heyhuynhgiabuu/pi-diff";

const { parseDiff, renderSplit, renderUnified, normalizeShikiContrast } =
  __testing;
```

## Development

```bash
git clone https://github.com/buddingnewinsights/pi-diff.git
cd pi-diff
npm install
npm run typecheck   # TypeScript validation
npm run lint        # Biome linting
npm test            # Run tests
```

### Load in pi for testing

```bash
# From the pi-diff directory
pi -e ./src/index.ts

# Or install globally
pi install .
```

## How pi Extensions Work

pi-diff is a **pi extension** — a TypeScript file that exports a default function receiving the pi API:

```typescript
export default function piDiffExtension(pi: ExtensionAPI): void {
  // Wrap the built-in write/edit tools with diff rendering
  const origWrite = createWriteTool(cwd);
  const origEdit = createEditTool(cwd);

  pi.registerTool({
    ...origWrite,
    name: "write",
    execute: async (...) => { /* wrap + diff */ },
    renderCall: (...) => { /* preview */ },
    renderResult: (...) => { /* render diff */ },
  });

}
```

Extensions can:

- **Register tools** — `pi.registerTool(definition)`
- **Listen to events** — `pi.on("session_start" | "input" | "tool_call" | "session_shutdown" | ...)`
  - `tool_call` handlers can `block: true` to prevent the call from running
- **Register commands** — `pi.registerCommand("/name", handler)`

See the [pi docs](https://pi.dev) for the full extension API.

## Design notes

How **hashline** (`hashline_read`, `hashline_edit`, `edit` `hashlineChanges`) relates to Pi’s stock **`edit`** tool (exact `edits[]`, bounded fuzzy match, loud failures) is documented in [`.pi/artifacts/DECISIONS.md`](.pi/artifacts/DECISIONS.md) (ADR-001). Summary: stock `edit` stays the portable default; hashline is an opt-in strict anchor path for agents that adopt it.

## License

MIT — [huynhgiabuu](https://github.com/buddingnewinsights)

---
_Source: https://npm.io/package/@heyhuynhgiabuu/pi-diff · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
