# ChangeSet Model Reference

The ChangeSet domain model captures all file changes made by tools during the
Execute phase. It is the foundation for plan diff, review, and apply workflows.

## Overview

| Model | Purpose |
|---|---|
| `ChangeOperation` | Enum of operation types (CREATE, MODIFY, DELETE, RENAME) |
| `ChangeEntry` | Single recorded mutation with content hashes and metadata |
| `SpecChangeSet` | Collection of entries for a plan, with computed summaries |
| `ChangeSetStore` | Protocol for persisting and querying changesets |
| `InMemoryChangeSetStore` | In-memory implementation for M1 milestone |

## ChangeOperation

```python
from cleveragents.domain.models.core.change import ChangeOperation

ChangeOperation.CREATE   # "create" — new file
ChangeOperation.MODIFY   # "modify" — content changed
ChangeOperation.DELETE   # "delete" — file removed
ChangeOperation.RENAME   # "rename" — file moved/renamed
```

## ChangeEntry

Each `ChangeEntry` represents a single tool-caused mutation.

### Fields

| Field | Type | Description |
|---|---|---|
| `entry_id` | `str` | Auto-generated ULID uniquely identifying this entry |
| `plan_id` | `str` | ULID of the plan that owns this change |
| `resource_id` | `str` | ULID of the resource affected |
| `tool_name` | `str` | Namespaced tool name (e.g. `builtin/file-write`) |
| `operation` | `ChangeOperation` | Type of change |
| `path` | `str` | Repo-relative file path |
| `before_hash` | `str \| None` | SHA-256 of file before change (`None` for create) |
| `after_hash` | `str \| None` | SHA-256 of file after change (`None` for delete) |
| `before_mode` | `int \| None` | File mode before change |
| `after_mode` | `int \| None` | File mode after change |
| `timestamp` | `datetime` | UTC timestamp of the change |

### ULID Fields

The `entry_id` and `plan_id` fields use [ULIDs](https://github.com/ulid/spec)
(Universally Unique Lexicographically Sortable Identifiers). ULIDs are 128-bit
identifiers that encode a timestamp and random component, making them sortable
by creation time while remaining globally unique.

```
01ARZ3NDEKTSV4RRFFQ69G5FAV
└─────────┘└────────────┘
 timestamp    randomness
  (48 bit)    (80 bit)
```

### Example

```python
from cleveragents.domain.models.core.change import (
    ChangeEntry, ChangeOperation,
)

entry = ChangeEntry(
    plan_id="01HXYZ123456789ABCDEF",
    resource_id="01HXYZ000000000000001",
    tool_name="builtin/file-write",
    operation=ChangeOperation.CREATE,
    path="src/models/user.py",
    after_hash="e3b0c44298fc1c149afbf4c8996fb924...",
)
```

## SpecChangeSet

A `SpecChangeSet` groups all `ChangeEntry` records for a single plan execution.

### Fields

| Field | Type | Description |
|---|---|---|
| `changeset_id` | `str` | Auto-generated ULID for the changeset |
| `plan_id` | `str` | ULID of the plan |
| `entries` | `list[ChangeEntry]` | Ordered list of change entries |
| `created_at` | `datetime` | UTC timestamp when created |

### Computed Properties

| Property | Type | Description |
|---|---|---|
| `creates` | `int` | Count of CREATE entries |
| `modifies` | `int` | Count of MODIFY entries |
| `deletes` | `int` | Count of DELETE entries |
| `renames` | `int` | Count of RENAME entries |
| `paths_changed` | `set[str]` | Unique file paths affected |
| `resources_involved` | `set[str]` | Unique resource IDs |

### Example

```python
from cleveragents.domain.models.core.change import (
    ChangeEntry, ChangeOperation, SpecChangeSet,
)

cs = SpecChangeSet(
    plan_id="01HXYZ123456789ABCDEF",
    entries=[
        ChangeEntry(
            plan_id="01HXYZ123456789ABCDEF",
            resource_id="01HXYZ000000000000001",
            tool_name="builtin/file-write",
            operation=ChangeOperation.CREATE,
            path="src/new.py",
        ),
    ],
)

print(cs.creates)           # 1
print(cs.paths_changed)     # {'src/new.py'}
print(cs.summary())         # {'total': 1, 'creates': 1, ...}
```

## ChangeSetStore

The `ChangeSetStore` protocol defines the interface for changeset persistence:

```python
class ChangeSetStore(Protocol):
    def start(self, plan_id: str) -> str: ...
    def record(self, changeset_id: str, entry: ChangeEntry) -> None: ...
    def get(self, changeset_id: str) -> SpecChangeSet | None: ...
    def get_for_plan(self, plan_id: str) -> list[SpecChangeSet]: ...
    def summarize(self, changeset_id: str) -> dict: ...
```

### InMemoryChangeSetStore

The `InMemoryChangeSetStore` is the M1 implementation that stores changesets in
a plain Python dict. It is suitable for single-process tests and the M1 runtime.

```python
from cleveragents.domain.models.core.change import (
    ChangeEntry, ChangeOperation, InMemoryChangeSetStore,
)

store = InMemoryChangeSetStore()
cs_id = store.start("01HXYZ123456789ABCDEF")

store.record(cs_id, ChangeEntry(
    plan_id="01HXYZ123456789ABCDEF",
    resource_id="01HXYZ000000000000001",
    tool_name="builtin/file-write",
    operation=ChangeOperation.CREATE,
    path="src/new.py",
))

cs = store.get(cs_id)
print(store.summarize(cs_id))
```
