---
phase: 18-stash-operations
plan: 02
type: execute
wave: 2
depends_on: ["18-01"]
files_modified:
  - tests/test_stash.py
  - pytest.ini
autonomous: true

must_haves:
  truths:
    - "STASH-01: git stash saves uncommitted changes"
    - "STASH-02: git stash push saves changes"
    - "STASH-03: git stash -m saves with custom message"
    - "STASH-04: git stash pop restores and removes stash"
    - "STASH-05: git stash apply restores but keeps stash"
    - "STASH-06: git stash list shows all stashes"
    - "STASH-07: git stash drop removes most recent stash"
  artifacts:
    - path: "tests/test_stash.py"
      provides: "E2E tests for STASH-01 through STASH-07"
      min_lines: 100
    - path: "pytest.ini"
      provides: "Pytest marker for stash"
      contains: "stash"
  key_links:
    - from: "tests/test_stash.py"
      to: "conftest.run_gitsl"
      via: "import"
      pattern: "from conftest import run_gitsl"
    - from: "tests/test_stash.py"
      to: "sl_repo_with_commit"
      via: "fixture usage"
      pattern: "sl_repo_with_commit"
---

<objective>
Create E2E tests validating all 7 stash requirements.

Purpose: Verify that git stash commands correctly translate to Sapling shelve/unshelve equivalents, including the special handling for drop without arguments.

Output: test_stash.py covering all requirements with pytest marker registration.
</objective>

<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/18-stash-operations/18-RESEARCH.md
@.planning/phases/18-stash-operations/18-01-SUMMARY.md
@tests/conftest.py
@tests/test_branch.py
@tests/helpers/commands.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create E2E tests for stash command</name>
  <files>tests/test_stash.py</files>
  <action>
Create tests/test_stash.py following the pattern from test_branch.py:

```python
"""E2E tests for git stash command (STASH-01 through STASH-07)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl
from helpers.commands import run_command


sl_available = shutil.which("sl") is not None
pytestmark = [
    pytest.mark.skipif(not sl_available, reason="Sapling (sl) not installed"),
    pytest.mark.stash,
]


class TestStashBasic:
    """STASH-01: git stash translates to sl shelve."""

    def test_stash_saves_changes(self, sl_repo_with_commit: Path):
        """git stash saves pending changes."""
        # Create a modification
        test_file = sl_repo_with_commit / "README.md"
        test_file.write_text("modified content\n")

        # Verify dirty state
        status_before = run_command(["sl", "status"], cwd=sl_repo_with_commit)
        assert "M README.md" in status_before.stdout

        result = run_gitsl(["stash"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        # Verify clean state
        status_after = run_command(["sl", "status"], cwd=sl_repo_with_commit)
        assert "M README.md" not in status_after.stdout

        # Verify shelve exists
        shelves = run_command(["sl", "shelve", "--list"], cwd=sl_repo_with_commit)
        assert "default" in shelves.stdout

    def test_stash_nothing_to_stash(self, sl_repo_with_commit: Path):
        """git stash with no changes reports nothing to stash."""
        result = run_gitsl(["stash"], cwd=sl_repo_with_commit)
        # sl shelve with no changes exits with error
        assert result.exit_code != 0


class TestStashPush:
    """STASH-02: git stash push translates to sl shelve."""

    def test_stash_push(self, sl_repo_with_commit: Path):
        """git stash push saves pending changes."""
        test_file = sl_repo_with_commit / "README.md"
        test_file.write_text("modified content\n")

        result = run_gitsl(["stash", "push"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        status = run_command(["sl", "status"], cwd=sl_repo_with_commit)
        assert "M README.md" not in status.stdout


class TestStashMessage:
    """STASH-03: git stash -m translates to sl shelve -m."""

    def test_stash_with_message(self, sl_repo_with_commit: Path):
        """git stash -m saves with custom message."""
        test_file = sl_repo_with_commit / "README.md"
        test_file.write_text("modified content\n")

        result = run_gitsl(["stash", "-m", "my custom message"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        shelves = run_command(["sl", "shelve", "--list"], cwd=sl_repo_with_commit)
        assert "my custom message" in shelves.stdout

    def test_stash_push_with_message(self, sl_repo_with_commit: Path):
        """git stash push -m also works."""
        test_file = sl_repo_with_commit / "README.md"
        test_file.write_text("modified content\n")

        result = run_gitsl(["stash", "push", "-m", "push message"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        shelves = run_command(["sl", "shelve", "--list"], cwd=sl_repo_with_commit)
        assert "push message" in shelves.stdout


class TestStashPop:
    """STASH-04: git stash pop translates to sl unshelve."""

    def test_stash_pop_restores_and_removes(self, sl_repo_with_commit: Path):
        """git stash pop restores changes and removes stash."""
        # Setup: create and stash changes
        test_file = sl_repo_with_commit / "README.md"
        test_file.write_text("modified content\n")
        run_command(["sl", "shelve"], cwd=sl_repo_with_commit)

        # Verify stash exists
        shelves_before = run_command(["sl", "shelve", "--list"], cwd=sl_repo_with_commit)
        assert "default" in shelves_before.stdout

        result = run_gitsl(["stash", "pop"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        # Changes restored
        assert test_file.read_text() == "modified content\n"

        # Stash removed
        shelves_after = run_command(["sl", "shelve", "--list"], cwd=sl_repo_with_commit)
        assert "default" not in shelves_after.stdout

    def test_stash_pop_no_stash_error(self, sl_repo_with_commit: Path):
        """git stash pop with no stashes returns error."""
        result = run_gitsl(["stash", "pop"], cwd=sl_repo_with_commit)
        assert result.exit_code != 0


class TestStashApply:
    """STASH-05: git stash apply translates to sl unshelve --keep."""

    def test_stash_apply_restores_and_keeps(self, sl_repo_with_commit: Path):
        """git stash apply restores changes but keeps stash."""
        # Setup: create and stash changes
        test_file = sl_repo_with_commit / "README.md"
        test_file.write_text("modified content\n")
        run_command(["sl", "shelve"], cwd=sl_repo_with_commit)

        result = run_gitsl(["stash", "apply"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        # Changes restored
        assert test_file.read_text() == "modified content\n"

        # Stash still exists
        shelves = run_command(["sl", "shelve", "--list"], cwd=sl_repo_with_commit)
        assert "default" in shelves.stdout

    def test_stash_apply_no_stash_error(self, sl_repo_with_commit: Path):
        """git stash apply with no stashes returns error."""
        result = run_gitsl(["stash", "apply"], cwd=sl_repo_with_commit)
        assert result.exit_code != 0


class TestStashList:
    """STASH-06: git stash list translates to sl shelve --list."""

    def test_stash_list_shows_shelves(self, sl_repo_with_commit: Path):
        """git stash list shows existing stashes."""
        # Create a stash
        test_file = sl_repo_with_commit / "README.md"
        test_file.write_text("modified content\n")
        run_command(["sl", "shelve", "-m", "test stash"], cwd=sl_repo_with_commit)

        result = run_gitsl(["stash", "list"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert "test stash" in result.stdout

    def test_stash_list_empty(self, sl_repo_with_commit: Path):
        """git stash list with no stashes returns empty output."""
        result = run_gitsl(["stash", "list"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() == ""

    def test_stash_list_multiple(self, sl_repo_with_commit: Path):
        """git stash list shows multiple stashes."""
        test_file = sl_repo_with_commit / "README.md"

        # Create first stash
        test_file.write_text("first modification\n")
        run_command(["sl", "shelve", "-m", "first stash"], cwd=sl_repo_with_commit)

        # Create second stash
        test_file.write_text("second modification\n")
        run_command(["sl", "shelve", "-m", "second stash"], cwd=sl_repo_with_commit)

        result = run_gitsl(["stash", "list"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert "first stash" in result.stdout
        assert "second stash" in result.stdout


class TestStashDrop:
    """STASH-07: git stash drop translates to sl shelve --delete."""

    def test_stash_drop_removes_most_recent(self, sl_repo_with_commit: Path):
        """git stash drop removes most recent stash."""
        # Create a stash
        test_file = sl_repo_with_commit / "README.md"
        test_file.write_text("modified content\n")
        run_command(["sl", "shelve"], cwd=sl_repo_with_commit)

        result = run_gitsl(["stash", "drop"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        # Stash removed
        shelves = run_command(["sl", "shelve", "--list"], cwd=sl_repo_with_commit)
        assert shelves.stdout.strip() == ""

    def test_stash_drop_no_stash_error(self, sl_repo_with_commit: Path):
        """git stash drop with no stashes returns git-compatible error."""
        result = run_gitsl(["stash", "drop"], cwd=sl_repo_with_commit)
        assert result.exit_code == 1
        assert "No stash entries found" in result.stderr

    def test_stash_drop_multiple_removes_most_recent(self, sl_repo_with_commit: Path):
        """git stash drop with multiple stashes removes most recent only."""
        test_file = sl_repo_with_commit / "README.md"

        # Create first stash
        test_file.write_text("first modification\n")
        run_command(["sl", "shelve", "-m", "first stash"], cwd=sl_repo_with_commit)

        # Create second stash
        test_file.write_text("second modification\n")
        run_command(["sl", "shelve", "-m", "second stash"], cwd=sl_repo_with_commit)

        # Drop should remove most recent (second)
        result = run_gitsl(["stash", "drop"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        # First stash should still exist
        shelves = run_command(["sl", "shelve", "--list"], cwd=sl_repo_with_commit)
        assert "first stash" in shelves.stdout
        # Most recent (listed first) was the second, so it should be gone
        # Note: sl lists most recent first
```

Use sl_repo_with_commit fixture which provides a repo with README.md already committed (required for shelve operations).
  </action>
  <verify>
    - File exists: `ls tests/test_stash.py`
    - Syntax check: `python -m py_compile tests/test_stash.py`
    - Run tests: `pytest tests/test_stash.py -v`
  </verify>
  <done>
    - tests/test_stash.py exists with tests for STASH-01 through STASH-07
    - All tests pass when run with pytest
    - Critical test verifies drop without args uses git-compatible error message
  </done>
</task>

<task type="auto">
  <name>Task 2: Register pytest marker and run full test suite</name>
  <files>pytest.ini</files>
  <action>
Add pytest marker for stash to pytest.ini under the markers section:

```ini
    stash: tests for git stash command
```

Add this after existing markers (alphabetically, after "status").

Then run the full test suite to verify:
1. All new Phase 18 tests pass
2. No regressions in existing tests
  </action>
  <verify>
    - Check markers: `grep stash pytest.ini`
    - Run with marker: `pytest -m stash --collect-only`
    - Run Phase 18 tests: `pytest tests/test_stash.py -v`
    - Run full suite: `pytest`
  </verify>
  <done>
    - pytest.ini has stash marker registered
    - pytest --collect-only shows stash tests are collected
    - All Phase 18 tests pass
    - Full test suite passes (no regressions)
  </done>
</task>

</tasks>

<verification>
Run from project root:

```bash
# Run all Phase 18 tests
pytest tests/test_stash.py -v

# Verify markers work
pytest -m stash -v

# Run full test suite to ensure no regressions
pytest
```

All tests should pass. Verify that:
1. STASH-01: git stash saves changes
2. STASH-02: git stash push saves changes
3. STASH-03: git stash -m saves with message
4. STASH-04: git stash pop restores and removes
5. STASH-05: git stash apply restores but keeps
6. STASH-06: git stash list shows stashes
7. STASH-07: git stash drop removes most recent with git-compatible error
</verification>

<success_criteria>
1. tests/test_stash.py exists with tests for all 7 STASH requirements
2. All tests pass when run with pytest
3. pytest marker is registered
4. Full test suite passes (no regressions)
5. Drop without stashes returns exit code 1 with "No stash entries found" message
</success_criteria>

<output>
After completion, create `.planning/phases/18-stash-operations/18-02-SUMMARY.md`
</output>
