Feature: Git worktree sandbox lifecycle As a developer I want a sandbox that isolates plan changes in a git worktree So that the original repository is unmodified until changes are committed Background: Given a gwt test git repository is initialised # --- Creation --- 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 Scenario: Creating a sandbox with empty plan_id raises ValueError When a gwt sandbox is created with empty plan_id Then a gwt ValueError should be raised with message "plan_id cannot be empty" Scenario: Creating a sandbox with empty resource_id raises ValueError When a gwt sandbox is prepared with empty resource_id Then a gwt ValueError should be raised with message "resource_id cannot be empty" Scenario: Creating a sandbox with empty original_path raises ValueError When a gwt sandbox is prepared with empty original_path Then a gwt ValueError should be raised with message "original_path cannot be empty" Scenario: Creating a sandbox with non-positive timeout raises ValueError When a gwt sandbox is prepared with zero timeout Then a gwt ValueError should be raised with message "git_timeout must be positive" Scenario: Creating a sandbox on a non-git directory raises SandboxCreationError Given a gwt non-git directory When a gwt sandbox is created on the non-git directory for plan "plan-001" Then a gwt SandboxCreationError should be raised # --- Path resolution --- Scenario: Resolve a path in the worktree When a gwt sandbox is created for plan "plan-001" And the gwt path "src/main.py" is resolved Then the gwt resolved path should be inside the worktree And the gwt sandbox should be in the "active" state Scenario: Path traversal is rejected When a gwt sandbox is created for plan "plan-001" And the gwt path "../etc/passwd" is resolved Then a gwt ValueError should be raised with message "Path traversal not allowed" Scenario: Resolving a path on a cleaned-up sandbox raises SandboxStateError When a gwt sandbox is created for plan "plan-001" And the gwt sandbox is cleaned up And the gwt path "file.txt" is resolved on a cleaned-up sandbox Then a gwt SandboxStateError should be raised # --- Commit --- Scenario: Commit with no changes produces empty result When a gwt sandbox is created for plan "plan-001" And the gwt sandbox is committed with message "empty commit" Then the gwt commit result should indicate success And the gwt commit result should have 0 changed files Scenario: Commit with changes merges back to original branch When a gwt sandbox is created for plan "plan-001" And a gwt file "new_file.txt" is created in the worktree with content "hello" And the gwt sandbox is committed with message "add new file" Then the gwt commit result should indicate success And the gwt commit result should have 1 added files And the gwt file "new_file.txt" should exist in the original repo Scenario: Commit with modified file reports changes When a gwt sandbox is created for plan "plan-001" And the gwt existing file "README.md" is modified in the worktree And the gwt sandbox is committed with message "modify readme" Then the gwt commit result should indicate success And the gwt commit result should have 1 changed files Scenario: Commit on a cleaned-up sandbox raises SandboxStateError When a gwt sandbox is created for plan "plan-001" And the gwt sandbox is cleaned up And the gwt sandbox commit is attempted on cleaned-up sandbox Then a gwt SandboxStateError should be raised # --- Rollback --- Scenario: Rollback discards worktree changes When a gwt sandbox is created for plan "plan-001" And a gwt file "temp.txt" is created in the worktree with content "temporary" And the gwt path "temp.txt" is resolved And the gwt sandbox is rolled back Then the gwt sandbox should be in the "rolled_back" state And the gwt file "temp.txt" should not exist in the worktree Scenario: Rollback on a non-active sandbox raises SandboxStateError When a gwt sandbox is created for plan "plan-001" And the gwt sandbox rollback is attempted on created sandbox Then a gwt SandboxStateError should be raised Scenario: Rollback from COMMITTED undoes the merge on original branch When a gwt sandbox is created for plan "plan-rb-commit" And a gwt file "committed_file.txt" is created in the worktree with content "committed-data" And the gwt sandbox is committed with message "add committed_file" Then the gwt sandbox should be in the "committed" state And the gwt file "committed_file.txt" should exist in the original repo When the gwt sandbox is rolled back Then the gwt sandbox should be in the "rolled_back" state And the gwt file "committed_file.txt" should not exist in the original repo # --- Cleanup --- Scenario: Cleanup removes worktree and branch When a gwt sandbox is created for plan "plan-001" And the gwt sandbox is cleaned up Then the gwt sandbox should be in the "cleaned_up" state And the gwt sandbox worktree path should not exist And the gwt sandbox branch should not exist Scenario: Cleanup is idempotent When a gwt sandbox is created for plan "plan-001" And the gwt sandbox is cleaned up And the gwt sandbox is cleaned up again Then the gwt sandbox should be in the "cleaned_up" state # --- Branch sanitisation --- Scenario: Branch name is sanitised from plan ID When a gwt sandbox is created for plan "plan with spaces!@#" Then the gwt sandbox branch name should be safe for git # --- Protocol properties --- Scenario: Sandbox has a unique ULID identifier When a gwt sandbox is instantiated Then the gwt sandbox_id should be a valid ULID And the gwt sandbox status should be "pending" And the gwt sandbox context should be None