Compare commits

...

1 Commits

Author SHA1 Message Date
HAL9000 8840b4a105 docs(modules): add git worktree sandbox module documentation
CI / lint (pull_request) Successful in 35s
CI / quality (pull_request) Successful in 31s
CI / typecheck (pull_request) Successful in 55s
CI / security (pull_request) Successful in 47s
CI / build (pull_request) Successful in 35s
CI / helm (pull_request) Successful in 36s
CI / push-validation (pull_request) Successful in 24s
CI / integration_tests (pull_request) Successful in 4m29s
CI / e2e_tests (pull_request) Successful in 4m27s
CI / unit_tests (pull_request) Successful in 5m38s
CI / docker (pull_request) Successful in 24s
CI / coverage (pull_request) Successful in 12m27s
CI / status-check (pull_request) Successful in 2s
Comprehensive module reference for GitWorktreeSandbox (v3.5.0, PR #5998):
- Lifecycle state machine diagram with complete state transitions
- Key classes: GitWorktreeSandbox, SandboxContext, CommitResult, SandboxStatus
- Branch naming convention with sanitisation examples
- Error handling table including ValueError for git_timeout <= 0
- Rollback behaviour (pre-commit vs post-commit)
- Integration with PlanLifecycleService pseudocode
- Apply Summary output format
- Testing guidance referencing actual BDD feature files:
  features/git_worktree_sandbox.feature
  features/git_worktree_apply.feature
  features/git_worktree_coverage_boost.feature
- Updated CHANGELOG.md with entry under [Unreleased] ### Added
- Updated CONTRIBUTORS.md with documentation contribution record

ISSUES CLOSED: #8051
2026-04-14 10:50:14 +00:00
3 changed files with 226 additions and 141 deletions
+9
View File
@@ -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_<N>` tag system. Scenarios whose referenced bugs were already fixed
+1
View File
@@ -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.
+216 -141
View File
@@ -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-<plan_id>` 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-<plan_id>
│ Creates temporary worktree at /tmp/ca-sandbox-<plan_id>-*/
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 <branch> into original branch
sandbox.cleanup() → CLEANED_UP
git worktree remove --force
git branch -D cleveragents/plan-<plan_id>
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-<sanitised_plan_id>`.
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-<sanitised_plan_id>`.
| 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 <pre_merge_sha>`), 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