# Diff Review

The diff review module builds reviewable diff artifacts from `SpecChangeSet`
records, grouping changes by resource and producing unified diff text for
CLI display.

## Overview

When a plan accumulates changes through tool executions, the diff review
system transforms the raw `ChangeEntry` records into a structured
`ReviewArtifact` suitable for human review before applying changes.

## Domain Models

### DiffEntry

A single diff entry for CLI review. Contains:

| Field | Type | Description |
|---|---|---|
| `resource_id` | `str` | Resource ULID |
| `resource_name` | `str` | Human-readable resource name |
| `path` | `str` | File path |
| `change_type` | `str` | Operation type (create/modify/delete/rename) |
| `before_content` | `str \| None` | Content before the change |
| `after_content` | `str \| None` | Content after the change |
| `before_hash` | `str \| None` | SHA-256 before |
| `after_hash` | `str \| None` | SHA-256 after |
| `file_mode` | `int \| None` | File permission mode |
| `is_binary` | `bool` | Whether the file is binary |
| `is_rename` | `bool` | Whether this is a rename/move |
| `rename_from` | `str \| None` | Original path for renames |
| `rename_to` | `str \| None` | Destination path for renames |
| `diff_text` | `str` | Unified diff text |
| `truncated` | `bool` | Whether the diff text was truncated |

### ResourceDiffGroup

Groups diff entries for a single resource. Provides summary counts
via properties: `added`, `modified`, `deleted`, `renamed`.

### ReviewArtifact

Complete diff review artifact for a plan. Contains grouped diff entries,
total change count, and truncation status.

## DiffBuilder

`DiffBuilder` takes a `SpecChangeSet` and optional content maps to produce
a `ReviewArtifact`.

```python
from cleveragents.domain.models.core.diff_review import DiffBuilder

builder = DiffBuilder(max_diff_size=50_000, context_lines=3)
artifact = builder.build(
    changeset,
    before_contents={"src/main.py": "old content"},
    after_contents={"src/main.py": "new content"},
    resource_names={"RES001": "my-resource"},
)
```

### Features

- **Unified diff generation** using `difflib.unified_diff`
- **Binary detection** via null byte checking
- **Rename detection** using `SequenceMatcher` path similarity (>0.6)
  combined with matching content hashes
- **Truncation** when total diff text exceeds `max_diff_size` (applies
  uniformly to all entry types including renames)
- **Stable ordering** by resource ID, then path within each group

## DiffSerializer

Renders a `ReviewArtifact` to various output formats:

- `to_plain(artifact)` — plain text for terminal output
- `to_json(artifact)` — structured JSON for programmatic use
- `to_rich(artifact)` — rich markup with `[bold]`, `[green]`, `[red]` tags

## Constants

| Name | Default | Description |
|---|---|---|
| `DEFAULT_MAX_DIFF_SIZE` | 50,000 | Maximum total diff text characters |
| `RENAME_SIMILARITY_THRESHOLD` | 0.6 | Minimum path similarity for rename detection |
