# Plan Diff & Apply Reference This document describes the diff review and apply integration features added in D0b.apply. ## CLI Commands ### `agents plan diff ` Show the ChangeSet produced during Execute as a unified diff, grouped by resource path. **Options:** | Flag | Description | |------|-------------| | `--format`, `-f` | Output format: `rich` (default), `plain`, `json`, `yaml` | **Examples:** ```bash # Rich output with coloured operation labels agents plan diff 01HXYZ... # Plain unified-diff output agents plan diff 01HXYZ... --format plain # Machine-readable JSON agents plan diff 01HXYZ... --format json ``` **Output fields (JSON/YAML):** | Field | Type | Description | |-------|------|-------------| | `changeset_id` | string | ULID of the ChangeSet | | `plan_id` | string | ULID of the plan | | `total_changes` | int | Number of change entries | | `summary` | object | `{creates, modifies, deletes, renames, paths_changed, resources_involved}` | | `entries` | list | Per-file entries with `path`, `operation`, hashes, timestamps | ### `agents plan artifacts ` Show plan artifacts: ChangeSet metadata, sandbox references, file change list, and validation results. **Options:** | Flag | Description | |------|-------------| | `--format`, `-f` | Output format: `rich` (default), `plain`, `json`, `yaml` | **Output fields (JSON/YAML):** | Field | Type | Description | |-------|------|-------------| | `plan_id` | string | Plan ULID | | `phase` | string | Current lifecycle phase | | `processing_state` | string | Current processing state | | `changeset_id` | string | ChangeSet ULID (null if Execute not complete) | | `sandbox_refs` | list | Active sandbox reference IDs | | `changeset_summary` | object | Summary counts from SpecChangeSet | | `files_changed` | list | `{path, operation}` per changed file | | `validation_summary` | object | Validation results (if available) | | `apply_summary` | object | Files changed count, validations run (after apply) | ## Apply Integration ### Empty ChangeSet Guard The apply pipeline checks whether the ChangeSet has any entries before proceeding. If the ChangeSet is empty, apply is blocked with a clear message: ``` Plan has an empty ChangeSet. No changes to apply. Use --allow-empty to override. ``` Set `--allow-empty` to bypass this check for plans that intentionally produce no file changes (e.g. validation-only plans). ### Apply Summary Persistence When apply completes, the following metadata is stored in the plan: - `apply_files_changed`: Number of files written/modified/deleted - `apply_validations_run`: Number of validation checks executed - `apply_completed_at`: ISO-8601 timestamp of apply completion This metadata is visible via `agents plan status` and `agents plan artifacts`. ### Merge Failure Handling When a sandbox merge fails during apply: 1. The plan transitions to `ERRORED` processing state 2. `error_message` is set to `"Merge failed:
"` 3. `error_details` includes: - `merge_conflict`: Description of the conflict - `sandbox_rollback`: Set to `"pending"` for cleanup **Recovery steps:** 1. Review conflict details via `agents plan status ` 2. Re-run execute phase after resolving conflicts: `agents plan execute ` 3. Or fix validation issues and retry apply ### Processing State Flow ``` Execute/COMPLETE | v Apply/QUEUED --> Apply/PROCESSING --> Apply/APPLIED (terminal success) | +--> Apply/ERRORED (merge failure) +--> Apply/CONSTRAINED (invariant violation) ``` ## Service API ### `PlanApplyService` ```python from cleveragents.application.services.plan_apply_service import PlanApplyService service = PlanApplyService( lifecycle_service=lifecycle, changeset_store=store, # optional InMemoryChangeSetStore ) # Generate diff diff_text = service.diff(plan_id, fmt="json") # Get artifacts artifacts = service.artifacts(plan_id, fmt="json") # Guard empty changeset service.guard_empty_changeset(plan_id, allow_empty=False) # Persist apply summary service.persist_apply_summary(plan_id, files_changed=5, validations_run=3) # Handle merge failure service.handle_merge_failure(plan_id, conflict_details="...") ```