Files
cleveragents-core/docs/reference/plan_apply.md
T
2026-02-25 10:05:10 +00:00

215 lines
6.6 KiB
Markdown

# Plan Diff & Apply Reference
This document describes the diff review and apply integration features
added in D0b.apply.
## CLI Commands
### `agents plan diff <plan_id>`
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 <plan_id>`
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 <id> 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: <details>"`
3. `error_details` includes:
- `merge_conflict`: Description of the conflict
- `sandbox_rollback`: Set to `"pending"` for cleanup
**Recovery steps:**
1. Review error details and recovery hints via `agents plan errors <id>`
2. Review conflict details via `agents plan status <id>`
3. Re-run execute phase after resolving conflicts: `agents plan execute <id>`
4. Or fix validation issues and retry apply
See [Error Recovery Reference](error_recovery.md) for the full error
classification, retry policy, and recovery hint system.
### Processing State Flow
```
Execute/COMPLETE
|
v
Apply/QUEUED --> Apply/PROCESSING --> Apply/APPLIED (terminal success)
|
+--> Apply/ERRORED (merge failure)
+--> Apply/CONSTRAINED (invariant violation)
```
### Validation-Gated Apply
The apply pipeline gates commits on validation results. Before any
file changes are committed, `apply_with_validation_gate()` checks
the validation summary (either from the plan or supplied externally)
and blocks apply if any **required** validations have failed.
#### Outcomes
| Outcome | Description |
|---------|-------------|
| `applied` | All required validations passed; changes committed |
| `constrained` | Required validations failed; apply blocked |
| `already_applied` | Plan was already in a terminal applied state |
| `blocked_empty` | ChangeSet was empty and `allow_empty` not set |
#### Flow
```
apply_with_validation_gate(plan_id)
|
+-- Plan in terminal state? --> already_applied / constrained
|
+-- Empty ChangeSet? --> blocked_empty (unless allow_empty)
|
+-- required_failed > 0? --> constrained (with actionable message)
|
+-- All passed --> persist_apply_summary() --> complete_apply()
--> ApplyResult(outcome="applied")
```
#### `ApplyResult` Model
| Field | Type | Description |
|-------|------|-------------|
| `outcome` | `ApplyOutcome` | One of the outcomes above |
| `plan_id` | `str` | Plan ULID |
| `message` | `str` | Human-readable result message |
| `files_changed` | `int` | Number of files changed during apply |
| `validations_total` | `int` | Total validations evaluated |
| `validations_passed` | `int` | Required validations that passed |
| `validations_failed` | `int` | Required validations that failed |
#### External Validation Summary
You can supply a validation summary from an external source (e.g. a
CI pipeline) to override the plan's stored summary:
```python
result = service.apply_with_validation_gate(
plan_id,
validation_summary={"total": 3, "required_passed": 2, "required_failed": 1},
)
```
## 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="...")
# Validation-gated apply
from cleveragents.application.services.plan_apply_service import ApplyOutcome, ApplyResult
result = service.apply_with_validation_gate(plan_id, allow_empty=False)
if result.outcome == ApplyOutcome.APPLIED:
print(f"Applied: {result.files_changed} files changed")
elif result.outcome == ApplyOutcome.CONSTRAINED:
print(f"Blocked: {result.message}")
```