---
phase: 27-grep-and-blame-flags
plan: 02
type: execute
wave: 2
depends_on: ["27-01"]
files_modified:
  - tests/test_grep_flags.py
  - tests/test_blame_flags.py
autonomous: true

must_haves:
  truths:
    - "All 14 grep flag requirements have E2E test coverage"
    - "All 7 blame flag requirements have E2E test coverage"
    - "Tests verify flag translation and warning behavior"
    - "Tests follow established test patterns in codebase"
  artifacts:
    - path: "tests/test_grep_flags.py"
      provides: "E2E tests for GREP-01 through GREP-14"
      min_lines: 150
    - path: "tests/test_blame_flags.py"
      provides: "E2E tests for BLAM-01 through BLAM-07"
      min_lines: 80
  key_links:
    - from: "tests/test_grep_flags.py"
      to: "cmd_grep.py"
      via: "run_gitsl(['grep', ...]) calls"
      pattern: "run_gitsl.*grep"
    - from: "tests/test_blame_flags.py"
      to: "cmd_blame.py"
      via: "run_gitsl(['blame', ...]) calls"
      pattern: "run_gitsl.*blame"
---

<objective>
Create comprehensive E2E tests for all 21 grep and blame flag requirements (GREP-01 through GREP-14 and BLAM-01 through BLAM-07).

Purpose: Validate that flag translation, pass-through, and warning behavior works correctly in the extended handlers.

Output: Two test files following established codebase patterns with full requirement coverage.
</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/REQUIREMENTS.md
@.planning/phases/27-grep-and-blame-flags/27-RESEARCH.md
@.planning/phases/27-grep-and-blame-flags/27-01-SUMMARY.md
@tests/test_grep.py (existing pattern)
@tests/test_blame.py (existing pattern)
@tests/conftest.py (fixtures)
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create Grep Flag Tests</name>
  <files>tests/test_grep_flags.py</files>
  <action>
Create tests/test_grep_flags.py with E2E tests for all 14 GREP requirements.

Follow the established pattern from tests/test_grep.py:
- Use `sl_repo_with_commit` fixture for repository with committed files
- Use `run_gitsl` helper for command execution
- Mark tests with `pytest.mark.grep`

**Test structure:**

```python
"""E2E tests for git grep flags (GREP-01 through GREP-14)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl


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


class TestGrepPassThrough:
    """Tests for grep flags that pass through directly."""

    def test_grep_line_number_n(self, sl_repo_with_commit: Path):
        """GREP-01: git grep -n shows line numbers."""
        result = run_gitsl(["grep", "-n", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Output should contain filename:linenum:content format
        assert ":" in result.stdout

    def test_grep_line_number_long(self, sl_repo_with_commit: Path):
        """GREP-01: git grep --line-number shows line numbers."""
        result = run_gitsl(["grep", "--line-number", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

    def test_grep_case_insensitive_i(self, sl_repo_with_commit: Path):
        """GREP-02: git grep -i performs case-insensitive search."""
        result = run_gitsl(["grep", "-i", "TEST"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert "Test" in result.stdout or "test" in result.stdout.lower()

    def test_grep_files_only_l(self, sl_repo_with_commit: Path):
        """GREP-03: git grep -l shows only filenames."""
        result = run_gitsl(["grep", "-l", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert "README" in result.stdout
        # Should not contain the actual matched content
        lines = result.stdout.strip().split('\n')
        for line in lines:
            assert "Test" not in line or line.endswith('.md')

    def test_grep_word_match_w(self, sl_repo_with_commit: Path):
        """GREP-05: git grep -w matches whole words only."""
        result = run_gitsl(["grep", "-w", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

    def test_grep_fixed_strings_F(self, sl_repo_with_commit: Path):
        """GREP-14: git grep -F treats pattern as literal string."""
        result = run_gitsl(["grep", "-F", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0


class TestGrepContextLines:
    """Tests for grep context line flags."""

    def test_grep_after_context_A(self, sl_repo_with_commit: Path):
        """GREP-07: git grep -A shows trailing context."""
        result = run_gitsl(["grep", "-A", "1", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

    def test_grep_after_context_attached(self, sl_repo_with_commit: Path):
        """GREP-07: git grep -A1 with attached value."""
        result = run_gitsl(["grep", "-A1", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

    def test_grep_before_context_B(self, sl_repo_with_commit: Path):
        """GREP-08: git grep -B shows leading context."""
        result = run_gitsl(["grep", "-B", "1", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

    def test_grep_both_context_C(self, sl_repo_with_commit: Path):
        """GREP-09: git grep -C shows both context."""
        result = run_gitsl(["grep", "-C", "1", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0


class TestGrepTranslation:
    """Tests for grep flags that require translation."""

    def test_grep_invert_match_v(self, sl_repo_with_commit: Path):
        """GREP-06: git grep -v inverts match (translates to sl -V)."""
        # Search for pattern that exists
        result_match = run_gitsl(["grep", "Test"], cwd=sl_repo_with_commit)
        assert result_match.exit_code == 0

        # Invert should find lines NOT matching
        result_invert = run_gitsl(["grep", "-v", "Test"], cwd=sl_repo_with_commit)
        # Could be exit 0 if other lines exist, or exit 1 if all lines match
        # Key is that it doesn't error out (wrong flag would error)
        assert result_invert.exit_code in (0, 1)

    def test_grep_invert_match_long(self, sl_repo_with_commit: Path):
        """GREP-06: git grep --invert-match inverts match."""
        result = run_gitsl(["grep", "--invert-match", "NonExistent"], cwd=sl_repo_with_commit)
        # Should return matching (inverted from no-match) lines
        assert result.exit_code == 0

    def test_grep_quiet_q(self, sl_repo_with_commit: Path):
        """GREP-13: git grep -q suppresses output, uses exit code."""
        result = run_gitsl(["grep", "-q", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Quiet mode should produce no output
        assert result.stdout.strip() == ""


class TestGrepUnsupported:
    """Tests for grep flags that are unsupported (warn and skip)."""

    def test_grep_count_c_warning(self, sl_repo_with_commit: Path):
        """GREP-04: git grep -c warns about unsupported flag."""
        result = run_gitsl(["grep", "-c", "Test"], cwd=sl_repo_with_commit)
        # Should warn but still run (without the flag)
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()

    def test_grep_suppress_filename_h_warning(self, sl_repo_with_commit: Path):
        """GREP-10: git grep -h warns (cannot suppress filename)."""
        result = run_gitsl(["grep", "-h", "Test"], cwd=sl_repo_with_commit)
        # Should warn but still run
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()

    def test_grep_force_filename_H_noop(self, sl_repo_with_commit: Path):
        """GREP-11: git grep -H is no-op (already default)."""
        result = run_gitsl(["grep", "-H", "Test"], cwd=sl_repo_with_commit)
        # Should work without error (flag silently ignored)
        assert result.exit_code == 0

    def test_grep_only_matching_o_warning(self, sl_repo_with_commit: Path):
        """GREP-12: git grep -o warns about unsupported flag."""
        result = run_gitsl(["grep", "-o", "Test"], cwd=sl_repo_with_commit)
        # Should warn
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()
```

Ensure all 14 GREP requirements have at least one test.
  </action>
  <verify>
`pytest tests/test_grep_flags.py -v` - all tests pass
  </verify>
  <done>
tests/test_grep_flags.py created with coverage for:
- GREP-01: -n/--line-number (2 tests)
- GREP-02: -i/--ignore-case (1 test)
- GREP-03: -l/--files-with-matches (1 test)
- GREP-04: -c/--count warning (1 test)
- GREP-05: -w/--word-regexp (1 test)
- GREP-06: -v/--invert-match translation (2 tests)
- GREP-07: -A context (2 tests)
- GREP-08: -B context (1 test)
- GREP-09: -C context (1 test)
- GREP-10: -h warning (1 test)
- GREP-11: -H no-op (1 test)
- GREP-12: -o warning (1 test)
- GREP-13: -q quiet (1 test)
- GREP-14: -F fixed strings (1 test)
  </done>
</task>

<task type="auto">
  <name>Task 2: Create Blame Flag Tests</name>
  <files>tests/test_blame_flags.py</files>
  <action>
Create tests/test_blame_flags.py with E2E tests for all 7 BLAM requirements.

Follow the established pattern from tests/test_blame.py:
- Use `sl_repo_with_commit` fixture
- Use `run_gitsl` helper
- Mark tests with `pytest.mark.blame`

**Test structure:**

```python
"""E2E tests for git blame flags (BLAM-01 through BLAM-07)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl


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


class TestBlamePassThrough:
    """Tests for blame flags that pass through directly."""

    def test_blame_ignore_whitespace_w(self, sl_repo_with_commit: Path):
        """BLAM-01: git blame -w ignores all whitespace."""
        result = run_gitsl(["blame", "-w", "README.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert len(result.stdout) > 0

    def test_blame_show_number_n(self, sl_repo_with_commit: Path):
        """BLAM-07: git blame -n shows original line numbers."""
        result = run_gitsl(["blame", "-n", "README.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

    def test_blame_show_number_long(self, sl_repo_with_commit: Path):
        """BLAM-07: git blame --show-number shows line numbers."""
        result = run_gitsl(["blame", "--show-number", "README.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0


class TestBlameTranslation:
    """Tests for blame flags that require translation."""

    def test_blame_ignore_space_change_b(self, sl_repo_with_commit: Path):
        """BLAM-02: git blame -b translates to sl annotate --ignore-space-change."""
        # git -b means "ignore space changes" but sl -b means "blank SHA for boundary"
        # Our handler translates to --ignore-space-change
        result = run_gitsl(["blame", "-b", "README.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Should not show blank hashes (which would happen if we passed -b directly)
        # Verify we get normal annotate output
        assert len(result.stdout) > 0


class TestBlameUnsupported:
    """Tests for blame flags that are unsupported (warn and skip)."""

    def test_blame_line_range_L_warning(self, sl_repo_with_commit: Path):
        """BLAM-03: git blame -L warns about unsupported line range."""
        result = run_gitsl(["blame", "-L", "1,5", "README.md"], cwd=sl_repo_with_commit)
        # Should warn but still run without the flag
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()
        # Should still produce output (full file annotation)
        assert len(result.stdout) > 0 or result.exit_code == 0

    def test_blame_line_range_L_attached(self, sl_repo_with_commit: Path):
        """BLAM-03: git blame -L1,5 with attached value."""
        result = run_gitsl(["blame", "-L1,5", "README.md"], cwd=sl_repo_with_commit)
        # Should warn
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()

    def test_blame_show_email_e_warning(self, sl_repo_with_commit: Path):
        """BLAM-04: git blame -e warns about unsupported show email."""
        result = run_gitsl(["blame", "-e", "README.md"], cwd=sl_repo_with_commit)
        # Should warn
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()

    def test_blame_show_email_long_warning(self, sl_repo_with_commit: Path):
        """BLAM-04: git blame --show-email warns."""
        result = run_gitsl(["blame", "--show-email", "README.md"], cwd=sl_repo_with_commit)
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()

    def test_blame_porcelain_p_warning(self, sl_repo_with_commit: Path):
        """BLAM-05: git blame -p warns about unsupported porcelain."""
        result = run_gitsl(["blame", "-p", "README.md"], cwd=sl_repo_with_commit)
        # Should warn
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()

    def test_blame_porcelain_long_warning(self, sl_repo_with_commit: Path):
        """BLAM-05: git blame --porcelain warns."""
        result = run_gitsl(["blame", "--porcelain", "README.md"], cwd=sl_repo_with_commit)
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()

    def test_blame_long_hash_l_warning(self, sl_repo_with_commit: Path):
        """BLAM-06: git blame -l warns about semantic mismatch."""
        # CRITICAL: sl -l means "line number at first appearance" not "long hash"
        result = run_gitsl(["blame", "-l", "README.md"], cwd=sl_repo_with_commit)
        # Should warn - must not pass through (would change output meaning)
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()
```

Ensure all 7 BLAM requirements have at least one test.
  </action>
  <verify>
`pytest tests/test_blame_flags.py -v` - all tests pass
  </verify>
  <done>
tests/test_blame_flags.py created with coverage for:
- BLAM-01: -w ignore whitespace (1 test)
- BLAM-02: -b translation (1 test)
- BLAM-03: -L line range warning (2 tests)
- BLAM-04: -e/--show-email warning (2 tests)
- BLAM-05: -p/--porcelain warning (2 tests)
- BLAM-06: -l long hash warning (1 test)
- BLAM-07: -n/--show-number (2 tests)
  </done>
</task>

<task type="auto">
  <name>Task 3: Run Full Test Suite</name>
  <files>tests/test_grep_flags.py, tests/test_blame_flags.py</files>
  <action>
Run the complete test suite to verify all tests pass:

1. Run new grep flag tests:
   `pytest tests/test_grep_flags.py -v`

2. Run new blame flag tests:
   `pytest tests/test_blame_flags.py -v`

3. Run all grep and blame tests together:
   `pytest tests/test_grep.py tests/test_grep_flags.py tests/test_blame.py tests/test_blame_flags.py -v`

4. Run full test suite to ensure no regressions:
   `pytest -v`

If any tests fail, analyze the failure and fix either the test or the handler implementation.
  </action>
  <verify>
`pytest tests/test_grep_flags.py tests/test_blame_flags.py -v` - all tests pass
`pytest -v` - full suite passes with no regressions
  </verify>
  <done>
All tests pass:
- 14+ grep flag tests (GREP-01 through GREP-14)
- 11+ blame flag tests (BLAM-01 through BLAM-07)
- No regressions in existing tests
  </done>
</task>

</tasks>

<verification>
1. tests/test_grep_flags.py covers all 14 GREP requirements
2. tests/test_blame_flags.py covers all 7 BLAM requirements
3. All tests follow established codebase patterns
4. Tests verify:
   - Direct pass-through flags work correctly
   - Translation flags are handled (grep -v, blame -b)
   - Unsupported flags produce warnings
5. Full test suite passes with no regressions
</verification>

<success_criteria>
- [ ] tests/test_grep_flags.py created (150+ lines)
- [ ] tests/test_blame_flags.py created (80+ lines)
- [ ] All 14 GREP requirements have test coverage
- [ ] All 7 BLAM requirements have test coverage
- [ ] pytest tests/test_grep_flags.py passes
- [ ] pytest tests/test_blame_flags.py passes
- [ ] Full test suite passes (no regressions)
</success_criteria>

<output>
After completion, create `.planning/phases/27-grep-and-blame-flags/27-02-SUMMARY.md`
</output>
