Feature: Git worktree sandbox coverage boost
  As a developer
  I want to exercise uncovered edge cases in the git worktree sandbox
  So that code coverage is maximised for git_worktree.py

  # --- _sanitise_branch_name edge case ---

  Scenario: Sanitise branch name with all-special-chars falls back to sandbox
    When gwtcb _sanitise_branch_name is called with "!!!"
    Then gwtcb the result should be "sandbox"

  # --- create fails when path is not repo root ---

  Scenario: Create fails when path is not the repository root
    Given a gwtcb sandbox pointed at a non-root subdirectory
    When gwtcb create is called for plan "plan-001"
    Then a gwtcb SandboxCreationError should be raised
    And the gwtcb sandbox should be in the "errored" state

  # --- create times out ---

  Scenario: Create raises SandboxCreationError on timeout
    Given a gwtcb sandbox with mocked _run_git that times out
    When gwtcb create is called expecting a timeout error
    Then a gwtcb SandboxCreationError should be raised with message "timed out"
    And the gwtcb sandbox should be in the "errored" state

  # --- get_path with None worktree_path ---

  Scenario: get_path raises SandboxStateError when worktree_path is None
    Given a gwtcb sandbox in CREATED state with None worktree_path
    When gwtcb get_path is called with "some/file.py"
    Then a gwtcb SandboxStateError should be raised with message "Worktree path not set"

  # --- commit with uninitialised worktree ---

  Scenario: Commit raises SandboxStateError when worktree not initialised
    Given a gwtcb sandbox in ACTIVE state with no worktree or branch
    When gwtcb commit is called with message "should fail"
    Then a gwtcb SandboxStateError should be raised with message "Worktree not initialised"

  # --- commit with modified files in diff output ---

  Scenario: Commit parses modified files in diff output
    Given a gwtcb sandbox ready to commit with modified-file diff output
    When gwtcb commit is called with message "modify files"
    Then the gwtcb commit result should include changed file "src/modified.py"
    And the gwtcb commit result should include added file "src/new.py"
    And the gwtcb commit result should include deleted file "src/old.py"

  # --- commit timeout ---

  Scenario: Commit raises SandboxCommitError on timeout
    Given a gwtcb sandbox in ACTIVE state ready to commit
    And gwtcb _run_git is mocked to raise TimeoutExpired on commit
    When gwtcb commit is called expecting a timeout error
    Then a gwtcb SandboxCommitError should be raised with message "timed out"
    And the gwtcb sandbox should be in the "errored" state

  # --- commit CalledProcessError ---

  Scenario: Commit raises SandboxCommitError on CalledProcessError
    Given a gwtcb sandbox in ACTIVE state ready to commit
    And gwtcb _run_git is mocked to raise CalledProcessError on commit
    When gwtcb commit is called expecting a process error
    Then a gwtcb SandboxCommitError should be raised with message "Failed to commit"
    And the gwtcb sandbox should be in the "errored" state

  # --- rollback with uninitialised worktree ---

  Scenario: Rollback raises SandboxStateError when worktree not initialised
    Given a gwtcb sandbox in ACTIVE state with no worktree or base commit
    When gwtcb rollback is called expecting a state error
    Then a gwtcb SandboxStateError should be raised with message "Worktree not initialised"

  # --- rollback timeout ---

  Scenario: Rollback raises SandboxRollbackError on timeout
    Given a gwtcb sandbox in ACTIVE state ready to rollback
    And gwtcb _run_git is mocked to raise TimeoutExpired on rollback
    When gwtcb rollback is called expecting a timeout error
    Then a gwtcb SandboxRollbackError should be raised with message "timed out"
    And the gwtcb sandbox should be in the "errored" state

  # --- rollback CalledProcessError ---

  Scenario: Rollback raises SandboxRollbackError on CalledProcessError
    Given a gwtcb sandbox in ACTIVE state ready to rollback
    And gwtcb _run_git is mocked to raise CalledProcessError on rollback
    When gwtcb rollback is called expecting a process error
    Then a gwtcb SandboxRollbackError should be raised with message "Failed to rollback"
    And the gwtcb sandbox should be in the "errored" state

  # --- cleanup with worktree remove failure ---

  Scenario: Cleanup falls back to shutil when git worktree remove fails
    Given a gwtcb sandbox with a worktree directory that exists
    And gwtcb _run_git is mocked to fail on worktree remove
    When gwtcb cleanup is called
    Then the gwtcb sandbox should be in the "cleaned_up" state

  # --- cleanup with branch delete failure ---

  Scenario: Cleanup warns and continues when branch delete fails
    Given a gwtcb sandbox with a branch name set but no worktree directory
    And gwtcb _run_git is mocked to fail on branch delete
    When gwtcb cleanup is called
    Then the gwtcb sandbox should be in the "cleaned_up" state

  # --- create: timeout after parent dir is created (TOCTOU fix coverage) ---

  Scenario: Create cleans up parent dir on timeout during git worktree add
    Given a gwtcb sandbox with mocked _run_git that times out on worktree add
    When gwtcb create is called expecting a timeout error
    Then a gwtcb SandboxCreationError should be raised with message "timed out"
    And the gwtcb sandbox should be in the "errored" state

  # --- create: CalledProcessError after parent dir is created (TOCTOU fix coverage) ---

  Scenario: Create cleans up parent dir on CalledProcessError during git worktree add
    Given a gwtcb sandbox with mocked _run_git that fails on worktree add
    When gwtcb create is called expecting a process error
    Then a gwtcb SandboxCreationError should be raised
    And the gwtcb sandbox should be in the "errored" state

  # --- commit: timeout with parent dir set (TOCTOU fix coverage) ---

  Scenario: Commit cleans up parent dir on timeout
    Given a gwtcb sandbox in ACTIVE state ready to commit with parent dir set
    And gwtcb _run_git is mocked to raise TimeoutExpired on commit
    When gwtcb commit is called expecting a timeout error
    Then a gwtcb SandboxCommitError should be raised with message "timed out"
    And the gwtcb sandbox should be in the "errored" state

  # --- rollback: timeout with parent dir set (TOCTOU fix coverage) ---

  Scenario: Rollback cleans up parent dir on timeout
    Given a gwtcb sandbox in ACTIVE state ready to rollback with parent dir set
    And gwtcb _run_git is mocked to raise TimeoutExpired on rollback
    When gwtcb rollback is called expecting a timeout error
    Then a gwtcb SandboxRollbackError should be raised with message "timed out"
    And the gwtcb sandbox should be in the "errored" state

  # --- cleanup: OSError when removing parent dir (TOCTOU fix coverage) ---

  Scenario: Cleanup continues when parent dir removal raises OSError
    Given a gwtcb sandbox with a parent dir that raises OSError on removal
    When gwtcb cleanup is called
    Then the gwtcb sandbox should be in the "cleaned_up" state
