---
phase: 24-status-and-add-flags
plan: 02
type: execute
wave: 2
depends_on:
  - 24-01
files_modified:
  - tests/test_status_flags.py
  - tests/test_add_flags.py
autonomous: true

must_haves:
  truths:
    - "All 10 phase requirements (STAT-01 through STAT-05, ADD-01 through ADD-05) have test coverage"
    - "Tests verify correct flag translation behavior"
    - "Tests verify warning messages for unsupported features"
    - "Tests run successfully in CI environment"
  artifacts:
    - path: "tests/test_status_flags.py"
      provides: "E2E tests for STAT-01 through STAT-05"
      min_lines: 100
    - path: "tests/test_add_flags.py"
      provides: "E2E tests for ADD-01 through ADD-05"
      min_lines: 80
  key_links:
    - from: "tests/test_status_flags.py"
      to: "cmd_status.py"
      via: "run_gitsl(['status', '--ignored'])"
      pattern: "run_gitsl.*status.*--ignored"
    - from: "tests/test_add_flags.py"
      to: "cmd_add.py"
      via: "run_gitsl(['add', '-n'])"
      pattern: "run_gitsl.*add.*-n"
---

<objective>
Create comprehensive E2E tests for all 10 Status and Add Flags requirements (STAT-01 through STAT-05 and ADD-01 through ADD-05).

Purpose: Validate correct flag translation and behavior for status and add commands.

Output: Two new test files with full coverage of phase requirements.
</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/STATE.md
@.planning/phases/24-status-and-add-flags/24-01-SUMMARY.md

# Test patterns to follow
@tests/test_status.py
@tests/test_add.py
@tests/test_diff_flags.py
@tests/conftest.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create tests/test_status_flags.py for STAT-01 through STAT-05</name>
  <files>tests/test_status_flags.py</files>
  <action>
Create new test file `tests/test_status_flags.py` with E2E tests for all 5 status requirements.

**Test structure following existing patterns:**

```python
"""
E2E tests for git status flags (STAT-01 through STAT-05).

Tests status flag translation to Sapling equivalents.
"""

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.status_flags,
]


# ============================================================
# Fixtures for ignored files
# ============================================================


@pytest.fixture
def sl_repo_with_ignored(sl_repo: Path) -> Path:
    """Sapling repo with an ignored file."""
    # Create .gitignore (sl respects .gitignore)
    (sl_repo / ".gitignore").write_text("*.ignored\n")
    run_command(["sl", "add", ".gitignore"], cwd=sl_repo)
    run_command(["sl", "commit", "-m", "Add gitignore"], cwd=sl_repo)

    # Create ignored file
    (sl_repo / "test.ignored").write_text("ignored content\n")

    return sl_repo


@pytest.fixture
def sl_repo_with_bookmark(sl_repo_with_commit: Path) -> Path:
    """Sapling repo with a named bookmark."""
    run_command(["sl", "bookmark", "feature-branch"], cwd=sl_repo_with_commit)
    return sl_repo_with_commit


# ============================================================
# STAT-01: --ignored shows ignored files
# ============================================================


class TestStatusIgnored:
    """STAT-01: --ignored translates to sl status -i."""

    def test_ignored_flag_shows_ignored_files(self, sl_repo_with_ignored: Path):
        """--ignored includes ignored files in output."""
        result = run_gitsl(["status", "--ignored"], cwd=sl_repo_with_ignored)

        assert result.exit_code == 0
        assert "test.ignored" in result.stdout

    def test_ignored_with_porcelain(self, sl_repo_with_ignored: Path):
        """--ignored with --porcelain shows !! for ignored files."""
        result = run_gitsl(["status", "--ignored", "--porcelain"], cwd=sl_repo_with_ignored)

        assert result.exit_code == 0
        assert "!! test.ignored" in result.stdout

    def test_without_ignored_flag(self, sl_repo_with_ignored: Path):
        """Without --ignored, ignored files are not shown."""
        result = run_gitsl(["status", "--porcelain"], cwd=sl_repo_with_ignored)

        assert result.exit_code == 0
        assert "test.ignored" not in result.stdout


# ============================================================
# STAT-02: -b/--branch adds branch info
# ============================================================


class TestStatusBranch:
    """STAT-02: -b/--branch adds branch info to output."""

    def test_branch_flag_with_short(self, sl_repo_with_bookmark: Path):
        """-b with --short shows branch header."""
        result = run_gitsl(["status", "-b", "--short"], cwd=sl_repo_with_bookmark)

        assert result.exit_code == 0
        assert "## feature-branch" in result.stdout

    def test_branch_long_flag(self, sl_repo_with_bookmark: Path):
        """--branch works same as -b."""
        result = run_gitsl(["status", "--branch", "--short"], cwd=sl_repo_with_bookmark)

        assert result.exit_code == 0
        assert "## feature-branch" in result.stdout

    def test_branch_flag_with_porcelain(self, sl_repo_with_bookmark: Path):
        """-b with --porcelain shows branch header."""
        result = run_gitsl(["status", "-b", "--porcelain"], cwd=sl_repo_with_bookmark)

        assert result.exit_code == 0
        assert "## feature-branch" in result.stdout

    def test_sb_combined_flags(self, sl_repo_with_bookmark: Path):
        """-sb (common git shorthand) shows branch and short status."""
        # Note: Combined flags like -sb may need special handling
        # For now, test -s -b separately
        result = run_gitsl(["status", "-s", "-b"], cwd=sl_repo_with_bookmark)

        assert result.exit_code == 0
        assert "## feature-branch" in result.stdout


# ============================================================
# STAT-03: -v/--verbose
# ============================================================


class TestStatusVerbose:
    """STAT-03: -v/--verbose passes through."""

    def test_verbose_flag(self, sl_repo_with_commit: Path):
        """-v flag passes through to sl status."""
        # Modify a file to have something to show
        (sl_repo_with_commit / "README.md").write_text("Modified\n")

        result = run_gitsl(["status", "-v"], cwd=sl_repo_with_commit)

        # Should succeed (may show different format than git)
        assert result.exit_code == 0

    def test_verbose_long_flag(self, sl_repo_with_commit: Path):
        """--verbose flag passes through."""
        (sl_repo_with_commit / "README.md").write_text("Modified\n")

        result = run_gitsl(["status", "--verbose"], cwd=sl_repo_with_commit)

        assert result.exit_code == 0


# ============================================================
# STAT-04: Porcelain status code verification
# ============================================================


class TestPorcelainStatusCodes:
    """STAT-04: Verify --porcelain covers all status codes."""

    def test_all_status_codes_covered(self, sl_repo_with_commit: Path):
        """Verify all sl status codes map to git porcelain codes."""
        # M (modified) -> ' M'
        (sl_repo_with_commit / "README.md").write_text("Modified\n")
        result = run_gitsl(["status", "--porcelain"], cwd=sl_repo_with_commit)
        assert " M README.md" in result.stdout

    def test_added_status_code(self, sl_repo: Path):
        """A (added) -> 'A '."""
        (sl_repo / "new.txt").write_text("new\n")
        run_command(["sl", "add", "new.txt"], cwd=sl_repo)

        result = run_gitsl(["status", "--porcelain"], cwd=sl_repo)
        assert "A  new.txt" in result.stdout

    def test_removed_status_code(self, sl_repo_with_commit: Path):
        """R (removed) -> 'D '."""
        run_command(["sl", "rm", "README.md"], cwd=sl_repo_with_commit)

        result = run_gitsl(["status", "--porcelain"], cwd=sl_repo_with_commit)
        assert "D  README.md" in result.stdout

    def test_missing_status_code(self, sl_repo_with_commit: Path):
        """! (missing) -> ' D'."""
        (sl_repo_with_commit / "README.md").unlink()

        result = run_gitsl(["status", "--porcelain"], cwd=sl_repo_with_commit)
        assert " D README.md" in result.stdout

    def test_untracked_status_code(self, sl_repo: Path):
        """? (unknown) -> '??'."""
        (sl_repo / "untracked.txt").write_text("untracked\n")

        result = run_gitsl(["status", "--porcelain"], cwd=sl_repo)
        assert "?? untracked.txt" in result.stdout

    def test_ignored_status_code(self, sl_repo_with_ignored: Path):
        """I (ignored) -> '!!' with --ignored flag."""
        result = run_gitsl(["status", "--porcelain", "--ignored"], cwd=sl_repo_with_ignored)
        assert "!! test.ignored" in result.stdout


# ============================================================
# STAT-05: -u/--untracked-files modes
# ============================================================


class TestStatusUntrackedModes:
    """STAT-05: -u/--untracked-files controls untracked file display."""

    def test_untracked_no_mode(self, sl_repo: Path):
        """-uno suppresses untracked files."""
        (sl_repo / "untracked.txt").write_text("content\n")

        result = run_gitsl(["status", "-uno", "--porcelain"], cwd=sl_repo)

        assert result.exit_code == 0
        assert "??" not in result.stdout

    def test_untracked_no_with_space(self, sl_repo: Path):
        """-u no (with space) suppresses untracked files."""
        (sl_repo / "untracked.txt").write_text("content\n")

        result = run_gitsl(["status", "-u", "no", "--porcelain"], cwd=sl_repo)

        assert result.exit_code == 0
        assert "??" not in result.stdout

    def test_untracked_files_no_long_form(self, sl_repo: Path):
        """--untracked-files=no suppresses untracked files."""
        (sl_repo / "untracked.txt").write_text("content\n")

        result = run_gitsl(["status", "--untracked-files=no", "--porcelain"], cwd=sl_repo)

        assert result.exit_code == 0
        assert "??" not in result.stdout

    def test_untracked_normal_mode(self, sl_repo: Path):
        """-unormal shows untracked files (default behavior)."""
        (sl_repo / "untracked.txt").write_text("content\n")

        result = run_gitsl(["status", "-unormal", "--porcelain"], cwd=sl_repo)

        assert result.exit_code == 0
        assert "?? untracked.txt" in result.stdout

    def test_untracked_all_mode(self, sl_repo: Path):
        """-uall shows all untracked files."""
        (sl_repo / "untracked.txt").write_text("content\n")

        result = run_gitsl(["status", "-uall", "--porcelain"], cwd=sl_repo)

        assert result.exit_code == 0
        assert "?? untracked.txt" in result.stdout

    def test_untracked_no_still_shows_tracked_changes(self, sl_repo_with_commit: Path):
        """-uno still shows tracked file changes."""
        # Create untracked file
        (sl_repo_with_commit / "untracked.txt").write_text("untracked\n")
        # Modify tracked file
        (sl_repo_with_commit / "README.md").write_text("Modified\n")

        result = run_gitsl(["status", "-uno", "--porcelain"], cwd=sl_repo_with_commit)

        assert result.exit_code == 0
        assert " M README.md" in result.stdout
        assert "untracked.txt" not in result.stdout
```
  </action>
  <verify>
```bash
pytest tests/test_status_flags.py -v
```
All tests should pass.
  </verify>
  <done>
tests/test_status_flags.py covers STAT-01 (--ignored), STAT-02 (-b/--branch), STAT-03 (-v/--verbose), STAT-04 (porcelain code verification), and STAT-05 (-u modes).
  </done>
</task>

<task type="auto">
  <name>Task 2: Create tests/test_add_flags.py for ADD-01 through ADD-05</name>
  <files>tests/test_add_flags.py</files>
  <action>
Create new test file `tests/test_add_flags.py` with E2E tests for all 5 add requirements.

**Test structure:**

```python
"""
E2E tests for git add flags (ADD-01 through ADD-05).

Tests add flag translation to Sapling equivalents.
"""

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.add_flags,
]


# ============================================================
# Fixtures
# ============================================================


@pytest.fixture
def sl_repo_with_ignored(sl_repo: Path) -> Path:
    """Sapling repo with ignored file setup."""
    (sl_repo / ".gitignore").write_text("*.ignored\n")
    run_command(["sl", "add", ".gitignore"], cwd=sl_repo)
    run_command(["sl", "commit", "-m", "Add gitignore"], cwd=sl_repo)

    # Create ignored file
    (sl_repo / "test.ignored").write_text("ignored content\n")

    return sl_repo


# ============================================================
# ADD-01: -A/--all -> addremove verification
# ============================================================


class TestAddAllVerification:
    """ADD-01: Verify -A/--all to addremove translation."""

    def test_add_A_stages_new_files(self, sl_repo: Path):
        """-A stages new files via addremove."""
        (sl_repo / "new1.txt").write_text("new1\n")
        (sl_repo / "new2.txt").write_text("new2\n")

        result = run_gitsl(["add", "-A"], cwd=sl_repo)
        assert result.exit_code == 0

        status = run_command(["sl", "status"], cwd=sl_repo)
        assert "A new1.txt" in status.stdout
        assert "A new2.txt" in status.stdout

    def test_add_all_marks_deleted(self, sl_repo_with_commit: Path):
        """--all marks deleted files for removal."""
        (sl_repo_with_commit / "README.md").unlink()

        result = run_gitsl(["add", "--all"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

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

    def test_add_A_with_pathspec(self, sl_repo: Path):
        """-A with pathspec limits scope."""
        subdir = sl_repo / "subdir"
        subdir.mkdir()
        (subdir / "sub.txt").write_text("sub\n")
        (sl_repo / "root.txt").write_text("root\n")

        result = run_gitsl(["add", "-A", "subdir/"], cwd=sl_repo)
        assert result.exit_code == 0

        status = run_command(["sl", "status"], cwd=sl_repo)
        assert "A subdir/sub.txt" in status.stdout
        # Root file should still be untracked
        assert "? root.txt" in status.stdout


# ============================================================
# ADD-02: -u/--update verification
# ============================================================


class TestAddUpdateVerification:
    """ADD-02: Verify -u/--update emulation."""

    def test_add_u_ignores_untracked(self, sl_repo_with_commit: Path):
        """-u does NOT stage untracked files."""
        (sl_repo_with_commit / "untracked.txt").write_text("untracked\n")
        (sl_repo_with_commit / "README.md").write_text("modified\n")

        result = run_gitsl(["add", "-u"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        status = run_command(["sl", "status"], cwd=sl_repo_with_commit)
        assert "? untracked.txt" in status.stdout

    def test_add_update_marks_deleted(self, sl_repo_with_commit: Path):
        """--update marks deleted tracked files for removal."""
        (sl_repo_with_commit / "README.md").unlink()

        result = run_gitsl(["add", "--update"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

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

    def test_add_u_with_pathspec(self, sl_repo_with_commit: Path):
        """-u with pathspec respects path filter."""
        # Create and commit a file in subdir
        subdir = sl_repo_with_commit / "subdir"
        subdir.mkdir()
        subfile = subdir / "tracked.txt"
        subfile.write_text("sub content\n")
        run_command(["sl", "add", "subdir/tracked.txt"], cwd=sl_repo_with_commit)
        run_command(["sl", "commit", "-m", "Add subdir file"], cwd=sl_repo_with_commit)

        # Delete both files
        (sl_repo_with_commit / "README.md").unlink()
        subfile.unlink()

        # Run -u only on subdir
        result = run_gitsl(["add", "-u", "subdir/"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        status = run_command(["sl", "status"], cwd=sl_repo_with_commit)
        assert "R subdir/tracked.txt" in status.stdout
        assert "! README.md" in status.stdout  # Still deleted, not removed


# ============================================================
# ADD-03: --dry-run/-n preview
# ============================================================


class TestAddDryRun:
    """ADD-03: --dry-run/-n shows what would be added."""

    def test_dry_run_short_flag(self, sl_repo: Path):
        """-n shows preview without adding."""
        (sl_repo / "newfile.txt").write_text("content\n")

        result = run_gitsl(["add", "-n", "newfile.txt"], cwd=sl_repo)
        assert result.exit_code == 0

        # File should still be untracked
        status = run_command(["sl", "status"], cwd=sl_repo)
        assert "? newfile.txt" in status.stdout

    def test_dry_run_long_flag(self, sl_repo: Path):
        """--dry-run shows preview without adding."""
        (sl_repo / "newfile.txt").write_text("content\n")

        result = run_gitsl(["add", "--dry-run", "newfile.txt"], cwd=sl_repo)
        assert result.exit_code == 0

        # File should still be untracked
        status = run_command(["sl", "status"], cwd=sl_repo)
        assert "? newfile.txt" in status.stdout

    def test_dry_run_with_all(self, sl_repo: Path):
        """-n works with -A."""
        (sl_repo / "new1.txt").write_text("new1\n")
        (sl_repo / "new2.txt").write_text("new2\n")

        result = run_gitsl(["add", "-A", "-n"], cwd=sl_repo)
        assert result.exit_code == 0

        # Files should still be untracked
        status = run_command(["sl", "status"], cwd=sl_repo)
        assert "? new1.txt" in status.stdout
        assert "? new2.txt" in status.stdout

    def test_dry_run_with_update(self, sl_repo_with_commit: Path):
        """-n works with -u."""
        (sl_repo_with_commit / "README.md").unlink()

        result = run_gitsl(["add", "-u", "-n"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        # File should still be missing, not removed
        status = run_command(["sl", "status"], cwd=sl_repo_with_commit)
        assert "! README.md" in status.stdout


# ============================================================
# ADD-04: -f/--force warning
# ============================================================


class TestAddForce:
    """ADD-04: -f/--force prints warning (not supported)."""

    def test_force_short_flag_warning(self, sl_repo_with_ignored: Path):
        """-f prints warning about limitation."""
        result = run_gitsl(["add", "-f", "test.ignored"], cwd=sl_repo_with_ignored)

        # Should have warning in stderr
        assert "warning" in result.stderr.lower() or "not" in result.stderr.lower()

    def test_force_long_flag_warning(self, sl_repo_with_ignored: Path):
        """--force prints warning about limitation."""
        result = run_gitsl(["add", "--force", "test.ignored"], cwd=sl_repo_with_ignored)

        # Should have warning in stderr
        assert "warning" in result.stderr.lower() or "not" in result.stderr.lower()


# ============================================================
# ADD-05: -v/--verbose shows files
# ============================================================


class TestAddVerbose:
    """ADD-05: -v/--verbose shows files as they are added."""

    def test_verbose_short_flag(self, sl_repo: Path):
        """-v shows files being added."""
        (sl_repo / "newfile.txt").write_text("content\n")

        result = run_gitsl(["add", "-v", "newfile.txt"], cwd=sl_repo)

        assert result.exit_code == 0
        # Output should mention the file
        assert "newfile.txt" in result.stdout or "newfile.txt" in result.stderr

    def test_verbose_long_flag(self, sl_repo: Path):
        """--verbose shows files being added."""
        (sl_repo / "newfile.txt").write_text("content\n")

        result = run_gitsl(["add", "--verbose", "newfile.txt"], cwd=sl_repo)

        assert result.exit_code == 0

    def test_verbose_multiple_files(self, sl_repo: Path):
        """-v shows all files being added."""
        (sl_repo / "file1.txt").write_text("content1\n")
        (sl_repo / "file2.txt").write_text("content2\n")

        result = run_gitsl(["add", "-v", "file1.txt", "file2.txt"], cwd=sl_repo)

        assert result.exit_code == 0
        # Verify files were actually added
        status = run_command(["sl", "status"], cwd=sl_repo)
        assert "A file1.txt" in status.stdout
        assert "A file2.txt" in status.stdout

    def test_verbose_with_dry_run(self, sl_repo: Path):
        """-v with -n shows what would be added."""
        (sl_repo / "newfile.txt").write_text("content\n")

        result = run_gitsl(["add", "-v", "-n", "newfile.txt"], cwd=sl_repo)

        assert result.exit_code == 0
        # File should still be untracked
        status = run_command(["sl", "status"], cwd=sl_repo)
        assert "? newfile.txt" in status.stdout
```
  </action>
  <verify>
```bash
pytest tests/test_add_flags.py -v
```
All tests should pass.
  </verify>
  <done>
tests/test_add_flags.py covers ADD-01 (-A verification), ADD-02 (-u verification), ADD-03 (--dry-run), ADD-04 (--force warning), and ADD-05 (--verbose).
  </done>
</task>

</tasks>

<verification>
Run all new and existing tests:

```bash
# New tests
pytest tests/test_status_flags.py tests/test_add_flags.py -v

# Ensure no regressions
pytest tests/test_status.py tests/test_add.py -v

# Full test suite
pytest --tb=short
```

All tests should pass with no failures.
</verification>

<success_criteria>
1. tests/test_status_flags.py exists with tests for STAT-01 through STAT-05
2. tests/test_add_flags.py exists with tests for ADD-01 through ADD-05
3. All 10 requirements have at least one test covering the happy path
4. Warning behavior is tested for unsupported features (ADD-04)
5. All existing tests continue to pass
6. `pytest` runs with 0 failures
</success_criteria>

<output>
After completion, create `.planning/phases/24-status-and-add-flags/24-02-SUMMARY.md`
</output>
