diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cf791e12..f17ec9359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,15 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added +- **Git Worktree Sandbox Module Documentation** (#8051): Added comprehensive + module reference page `docs/modules/git-worktree-sandbox.md` covering the + `GitWorktreeSandbox` class introduced in v3.5.0 (PR #5998). Documents the + lifecycle state machine, key classes (`GitWorktreeSandbox`, `SandboxContext`, + `CommitResult`, `SandboxStatus`), branch naming convention, error handling + table, rollback behaviour (pre/post-commit), integration with + `PlanLifecycleService`, Apply Summary output format, and BDD testing guidance + referencing `features/git_worktree_sandbox.feature`. + - **TDD Issue-Capture Test Activation** (#7025): Replaced 234 bare `@skip` tags across 82 Behave feature files with the correct `@tdd_expected_fail @tdd_issue @tdd_issue_` tag system. Scenarios whose referenced bugs were already fixed diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 31d4c882f..82db933d9 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,4 +16,5 @@ Below are some of the specific details of various contributions. * Brent E. Edwards has contributed quality assurance, test coverage, and CI pipeline improvements. * HAL 9000 has contributed automated implementation, bug fixes, and feature development as part of the CleverAgents automation pool. * HAL 9000 has contributed the plan concurrency race-condition fix (#7989): wired `LockService` into the plan lifecycle, guarding `execute_plan()` and `apply_plan()` with plan-level advisory locks and unique per-invocation owner identities to prevent silent concurrent state corruption. +* HAL 9000 has contributed the Git Worktree Sandbox module documentation (#8051): comprehensive module reference page covering lifecycle state machine, key classes, branch naming, error handling, rollback behaviour, and BDD testing guidance for the `GitWorktreeSandbox` introduced in v3.5.0. * This project was made possible thanks to considerable donation of time, money, and resources by CleverThis, Inc. diff --git a/docs/modules/git-worktree-sandbox.md b/docs/modules/git-worktree-sandbox.md index e10c5a208..6aad37ab4 100644 --- a/docs/modules/git-worktree-sandbox.md +++ b/docs/modules/git-worktree-sandbox.md @@ -1,190 +1,265 @@ -# Git Worktree Sandbox +# Git Worktree Sandbox Module -**Package:** `cleveragents.infrastructure.sandbox.git_worktree` -**Introduced:** Unreleased (issue #4454) +**Package:** `cleveragents.infrastructure.sandbox` +**Introduced:** v3.5.0 (PR #5998) -The git worktree sandbox isolates plan modifications in a dedicated git -branch and worktree. On commit, changes are merged back to the original -branch via `git merge`. On rollback, the worktree is discarded entirely. -This replaces the previous flat `shutil.copy2` apply strategy for -git-checkout resources. +The git worktree sandbox provides isolated, branch-based staging for plan +modifications against git-checkout resources. Changes are committed to a +dedicated branch inside a temporary worktree and merged back to the original +branch only when the plan is applied — leaving the working tree untouched +until the operator explicitly approves. + +For the sandbox protocol interface, see +[`docs/api/core.md`](../api/core.md). +For the plan lifecycle that invokes the sandbox, see +[`docs/architecture.md`](../architecture.md#plan-lifecycle). --- ## Purpose -When a plan applies changes to a git-checkout resource, the changes must -be isolated from the working tree until the user approves them. The git -worktree sandbox achieves this by: +When a plan executes against a `git-checkout` resource, the LLM writes +files to an isolated git worktree rather than directly to the project +directory. This guarantees that: -1. Creating a new branch `cleveragents/plan-` from the current HEAD. -2. Creating a temporary git worktree at a system temp directory. -3. Letting the actor write changes inside the worktree. -4. On commit: staging all changes, committing them in the worktree, then - merging the sandbox branch back into the original branch. -5. On rollback: resetting the worktree (and optionally the original branch) - to the base commit and discarding the worktree. +- The original working tree is never modified until `plan apply` is run. +- Rollback is a single `git reset --hard` — no file-by-file undo. +- The diff between the base commit and the sandbox branch is always + available via `plan diff`. +- Concurrent plans on the same repository do not interfere with each other. -Non-git projects fall back to the original flat file copy strategy. - ---- - -## `GitWorktreeSandbox` - -```python -from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox - -sandbox = GitWorktreeSandbox( - resource_id="01HZ...", - original_path="/path/to/repo", -) -``` - -Implements the `Sandbox` protocol. - -**Constructor parameters:** - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `resource_id` | `str` | — | Identifier of the resource being sandboxed | -| `original_path` | `str` | — | Path to the git repository root | -| `git_timeout` | `int` | `30` | Timeout in seconds for git commands | +Non-git projects fall back to the original flat `shutil.copy2` strategy. --- ## Lifecycle ``` -PENDING ──► create() ──► CREATED ──► get_path() ──► ACTIVE - │ - commit() ─┤─► COMMITTED ──► cleanup() ──► CLEANED_UP - │ - rollback() ─┴─► ROLLED_BACK ──► cleanup() ──► CLEANED_UP +GitWorktreeSandbox(resource_id, original_path) + │ + ▼ +sandbox.create(plan_id) → CREATED + │ Creates branch cleveragents/plan- + │ Creates temporary worktree at /tmp/ca-sandbox--*/ + │ + ▼ +sandbox.get_path("src/x.py") → ACTIVE + │ Translates resource-relative path to worktree-absolute path + │ Actor writes files here + │ + ▼ +sandbox.commit("message") → COMMITTED + │ git add -A && git commit in worktree + │ git merge into original branch + │ + ▼ +sandbox.cleanup() → CLEANED_UP + git worktree remove --force + git branch -D cleveragents/plan- + git worktree prune ``` -### `create(plan_id) → SandboxContext` +At any point before `commit()`, calling `rollback()` resets the worktree +to the base commit and transitions to `ROLLED_BACK`. After `commit()`, +`rollback()` also resets the original branch to the pre-merge commit, +fully undoing the merge. -Creates the worktree and branch. +--- + +## Key Classes + +### `GitWorktreeSandbox` ```python -ctx = sandbox.create(plan_id="plan-01HZ...") -# ctx.sandbox_path — path to the worktree -# ctx.metadata["branch"] — e.g. "cleveragents/plan-plan-01HZ..." -# ctx.metadata["base_commit"] — HEAD SHA at creation time -``` +from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox -Raises `SandboxCreationError` if the path is not a git repository root or -if `git worktree add` fails. - -### `get_path(resource_path) → str` - -Translates a resource-relative path to an absolute path inside the -worktree. Rejects `..` path traversal attempts. - -```python +sandbox = GitWorktreeSandbox( + resource_id="my-repo", + original_path="/path/to/project", + git_timeout=30, # optional; default 30 s +) +ctx = sandbox.create(plan_id="01JXYZ") worktree_file = sandbox.get_path("src/main.py") -# → "/tmp/ca-sandbox-plan-01HZ.../src/main.py" +# ... actor writes to worktree_file ... +result = sandbox.commit("feat: implement feature X") +sandbox.cleanup() ``` -### `commit(message=None) → CommitResult` +| Parameter | Type | Description | +|-----------|------|-------------| +| `resource_id` | `str` | Identifier of the resource being sandboxed | +| `original_path` | `str` | Absolute path to the git repository root | +| `git_timeout` | `int` | Timeout in seconds for each git command (default: 30) | -Stages all changes in the worktree, commits them, then merges the sandbox -branch into the original branch. +### `SandboxContext` -```python -result = sandbox.commit("feat: implement new feature") -# result.success — True on success -# result.commit_ref — SHA of the sandbox commit -# result.changed_files — list of modified file paths -# result.added_files — list of newly added file paths -# result.deleted_files — list of deleted file paths -``` +Returned by `create()`. Contains: -If there are no staged changes, returns a `CommitResult` with empty file -lists and no new commit. +| Attribute | Type | Description | +|-----------|------|-------------| +| `sandbox_id` | `str` | ULID uniquely identifying this sandbox instance | +| `sandbox_path` | `str` | Absolute path to the worktree directory | +| `original_path` | `str` | Absolute path to the original repository | +| `resource_id` | `str` | Resource being sandboxed | +| `plan_id` | `str` | Plan that owns this sandbox | +| `created_at` | `datetime` | Creation timestamp | +| `metadata` | `dict` | Extra info: `strategy`, `branch`, `original_branch`, `base_commit`, `worktree_path` | -Raises `SandboxCommitError` if the commit or merge fails. +### `CommitResult` -### `rollback()` +Returned by `commit()`. Contains: -Discards all worktree changes. +| Attribute | Type | Description | +|-----------|------|-------------| +| `sandbox_id` | `str` | Sandbox identifier | +| `success` | `bool` | Whether the commit and merge succeeded | +| `commit_ref` | `str \| None` | SHA of the commit in the worktree branch | +| `changed_files` | `list[str]` | Modified files | +| `added_files` | `list[str]` | New files | +| `deleted_files` | `list[str]` | Deleted files | +| `error` | `str \| None` | Error message if `success=False` | +| `timestamp` | `datetime` | Commit timestamp | -- From `ACTIVE`: resets the worktree branch to the base commit. -- From `COMMITTED`: also resets the original branch to the pre-merge commit - (fully undoes the merge). +### `SandboxStatus` -!!! warning "Multi-worktree safety" - Rolling back from `COMMITTED` executes `git reset --hard` on the - original branch. If other git worktrees or external processes are - tracking the same branch, they will be affected. - -### `cleanup()` - -Removes the worktree directory and deletes the sandbox branch. Idempotent -— safe to call multiple times. - ---- - -## Apply Summary - -When `plan apply` uses the git worktree strategy, the CLI displays a -spec-aligned Apply Summary: +Lifecycle state machine: ``` -╭─ Apply Summary ──────────────────────────────────────╮ -│ Plan ID: plan-01HZ... │ -│ Artifacts: 3 files changed │ -│ Insertions: +142 │ -│ Deletions: -17 │ -│ Project: local/my-project │ -│ Timestamp: 2026-04-09 17:30:00 │ -╰──────────────────────────────────────────────────────╯ -╭─ Sandbox Cleanup ────────────────────────────────────╮ -│ ✓ Worktree removed │ -│ ✓ Branch deleted │ -╰──────────────────────────────────────────────────────╯ -✓ OK Changes applied +PENDING → CREATED → ACTIVE → COMMITTED → CLEANED_UP + ↘ ROLLED_BACK ↗ + ↘ ERRORED ``` --- -## Error Types - -| Exception | When Raised | -|-----------|-------------| -| `SandboxCreationError` | `git worktree add` fails or path is not a git root | -| `SandboxCommitError` | `git commit` or `git merge` fails | -| `SandboxRollbackError` | `git reset --hard` fails during rollback | -| `SandboxStateError` | Method called in an invalid lifecycle state | - -All exceptions are importable from -`cleveragents.infrastructure.sandbox.protocol`. - ---- - ## Branch Naming -Branch names are sanitised from the plan ID: +The sandbox branch is named `cleveragents/plan-`. +The sanitiser replaces any character outside `[a-zA-Z0-9/_.-]` with a +hyphen and strips leading/trailing hyphens. For example: -- Only alphanumeric characters, hyphens, underscores, slashes, and dots - are kept. -- Runs of disallowed characters are collapsed into a single hyphen. -- The final branch name has the form `cleveragents/plan-`. +| Plan ID | Branch | +|---------|--------| +| `01JXYZ` | `cleveragents/plan-01JXYZ` | +| `plan with spaces` | `cleveragents/plan-plan-with-spaces` | --- -## Fallback for Non-Git Projects +## Error Handling -`PlanLifecycleService` detects whether the resource is a git-checkout type. -If not, it falls back to the original flat file copy strategy -(`shutil.copy2`). The git worktree sandbox is only used for resources -where `resource_type_name` is `git-checkout` or `git`. +| Exception | When raised | +|-----------|-------------| +| `SandboxCreationError` | `git worktree add` fails or path is not a git root | +| `SandboxCommitError` | `git commit` or `git merge` fails | +| `SandboxRollbackError` | `git reset --hard` fails during rollback | +| `SandboxStateError` | Method called in an invalid lifecycle state | +| `ValueError` | Empty `resource_id`, `original_path`, or `plan_id`; path traversal attempt; `git_timeout <= 0` | + +All git commands are wrapped with a configurable timeout. +`subprocess.TimeoutExpired` is caught and re-raised as the appropriate +`Sandbox*Error`. --- -## Related +## Rollback Behaviour -- [Architecture — Plan Lifecycle](../architecture.md#plan-lifecycle) -- [ADR-015 Sandbox & Checkpoint](../adr/ADR-015-sandbox-and-checkpoint.md) -- [ADR-038 Cross-Mechanism Sandbox Coordination](../adr/ADR-038-cross-mechanism-sandbox-coordination.md) -- [`cleveragents.infrastructure.sandbox`](../api/core.md) +### Before `commit()` (ACTIVE → ROLLED_BACK) + +Resets the worktree branch to the base commit and runs `git clean -fd` to +remove untracked files. The original branch is untouched. + +### After `commit()` (COMMITTED → ROLLED_BACK) + +1. Resets the **original branch** to the pre-merge commit (`git reset --hard `), undoing the merge. +2. Resets the **worktree branch** to the base commit. + +> **Warning:** Rolling back from `COMMITTED` executes `git reset --hard` +> on the original branch. If other git worktrees or external processes +> track the same branch, they will be affected. + +--- + +## Integration with Plan Lifecycle + +`PlanLifecycleService` creates a `GitWorktreeSandbox` automatically when +the plan's linked resource is a `git-checkout` type: + +```python +# Pseudocode — see PlanLifecycleService.execute_plan() +if resource.type == "git-checkout": + sandbox = GitWorktreeSandbox(resource.id, resource.path) + ctx = sandbox.create(plan.id) + # Actor writes to ctx.sandbox_path + result = sandbox.commit(f"plan({plan.id}): apply changes") + sandbox.cleanup() +``` + +The Apply Summary printed to the terminal includes: + +- Plan ID +- Artifacts (added/changed/deleted file counts) +- Insertions and deletions (from `git diff --stat`) +- Project name and timestamp +- Sandbox cleanup confirmation +- `✓ OK Changes applied` footer + +--- + +## Apply Summary Output + +``` +╭─ Apply Summary ──────────────────────────────────────────────────────────╮ +│ Plan ID : 01JXYZ │ +│ Project : my-project │ +│ Timestamp : 2026-04-09 14:33:34 │ +│ Artifacts : 3 added, 2 changed, 0 deleted │ +│ Diff : +127 / -43 │ +╰───────────────────────────────────────────────────────────────────────────╯ + +╭─ Sandbox Cleanup ─────────────────────────────────────────────────────────╮ +│ ✓ Worktree removed │ +│ ✓ Branch cleveragents/plan-01JXYZ deleted │ +╰───────────────────────────────────────────────────────────────────────────╯ + +✓ OK Changes applied +``` + +--- + +## Testing + +The git worktree sandbox is covered by BDD scenarios in the following +Behave feature files: + +- `features/git_worktree_sandbox.feature` — core lifecycle, path resolution, + commit, rollback, cleanup, and error handling +- `features/git_worktree_apply.feature` — end-to-end apply flow via + `PlanLifecycleService` +- `features/git_worktree_coverage_boost.feature` — additional edge-case + coverage for branch naming, timeout handling, and concurrent sandboxes + +Example scenario from `features/git_worktree_sandbox.feature`: + +```gherkin +Scenario: Create a git worktree sandbox + When a gwt sandbox is created for plan "plan-001" + Then the gwt sandbox should be in the "created" state + And the gwt sandbox context should reference plan "plan-001" + And the gwt sandbox context should have strategy metadata "git_worktree" + And the gwt sandbox worktree path should exist +``` + +Run the BDD suite with: + +``` +nox -s unit_tests +``` + +--- + +## Related Documentation + +- [Architecture Overview](../architecture.md) — plan lifecycle and sandbox role +- [ADR-015 Sandbox and Checkpoint](../adr/ADR-015-sandbox-and-checkpoint.md) — design rationale +- [Shell Safety Module](shell-safety.md) — another infrastructure safety component +- [Invariant Reconciliation Module](invariant-reconciliation.md) — phase-transition gating