---
phase: 21-rev-parse-expansion
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - cmd_rev_parse.py
  - tests/test_rev_parse.py
autonomous: true

must_haves:
  truths:
    - "git rev-parse --show-toplevel returns repository root path"
    - "git rev-parse --git-dir returns .sl or .hg directory path"
    - "git rev-parse --is-inside-work-tree returns true inside repo, false outside"
    - "git rev-parse --abbrev-ref HEAD returns current bookmark name or HEAD if detached"
    - "git rev-parse --verify <ref> validates reference exists and returns full hash"
    - "git rev-parse --symbolic <ref> returns input in symbolic form"
  artifacts:
    - path: "cmd_rev_parse.py"
      provides: "Rev-parse command with expanded flag support"
      contains: "_handle_show_toplevel"
      min_lines: 80
    - path: "tests/test_rev_parse.py"
      provides: "E2E tests for all rev-parse requirements"
      contains: "TestRevParseShowToplevel"
      min_lines: 100
  key_links:
    - from: "cmd_rev_parse.py"
      to: "sl root"
      via: "subprocess for --show-toplevel"
      pattern: 'sl.*root'
    - from: "cmd_rev_parse.py"
      to: "sl log"
      via: "subprocess for --abbrev-ref and --verify"
      pattern: 'sl.*log.*-T'
---

<objective>
Expand git rev-parse support to handle common flags used by tools and scripts for repository metadata queries.

Purpose: Tools like VSCode, IDE plugins, CI scripts, and shell prompts rely on rev-parse flags to detect repository state, find the root directory, and get current branch information. Currently gitsl only supports `--short HEAD`.

Output: Updated cmd_rev_parse.py with handlers for 6 new flag patterns (REVP-01 through REVP-06) and comprehensive E2E test 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/phases/21-rev-parse-expansion/21-RESEARCH.md

# Existing files to modify:
@cmd_rev_parse.py
@tests/test_rev_parse.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Implement rev-parse flag handlers</name>
  <files>cmd_rev_parse.py</files>
  <action>
Expand `cmd_rev_parse.py` to handle the following flags. Add helper functions for each flag type:

1. **Add os.path import at top:**
```python
import os.path
```

2. **Add helper functions:**

```python
def _handle_show_toplevel() -> int:
    """REVP-01: --show-toplevel returns repository root path."""
    result = subprocess.run(
        ["sl", "root"],
        capture_output=True,
        text=True
    )
    if result.returncode == 0:
        print(result.stdout.strip())
    else:
        sys.stderr.write(result.stderr)
    return result.returncode


def _handle_git_dir() -> int:
    """REVP-02: --git-dir returns .sl or .hg directory path."""
    result = subprocess.run(
        ["sl", "root"],
        capture_output=True,
        text=True
    )
    if result.returncode == 0:
        root = result.stdout.strip()
        # Check for .sl first (newer Sapling), then .hg
        sl_dir = os.path.join(root, ".sl")
        hg_dir = os.path.join(root, ".hg")
        if os.path.isdir(sl_dir):
            print(sl_dir)
        elif os.path.isdir(hg_dir):
            print(hg_dir)
        else:
            # Fallback to .sl (expected location)
            print(sl_dir)
        return 0
    else:
        sys.stderr.write(result.stderr)
    return result.returncode


def _handle_is_inside_work_tree() -> int:
    """REVP-03: --is-inside-work-tree returns true/false."""
    result = subprocess.run(
        ["sl", "root"],
        capture_output=True,
        text=True
    )
    if result.returncode == 0:
        print("true")
    else:
        print("false")
    # Note: git always returns 0 for this flag, even outside repo
    return 0


def _handle_abbrev_ref(ref: str) -> int:
    """REVP-04: --abbrev-ref HEAD returns current bookmark name."""
    if ref.upper() == "HEAD":
        result = subprocess.run(
            ["sl", "log", "-r", ".", "-T", "{activebookmark}"],
            capture_output=True,
            text=True
        )
        if result.returncode == 0:
            bookmark = result.stdout.strip()
            if bookmark:
                print(bookmark)
            else:
                # No active bookmark - equivalent to detached HEAD
                print("HEAD")
            return 0
        else:
            sys.stderr.write(result.stderr)
            return result.returncode
    else:
        # For other refs, just echo back the ref name
        print(ref)
        return 0


def _handle_verify(ref: str) -> int:
    """REVP-05: --verify validates object reference exists."""
    result = subprocess.run(
        ["sl", "log", "-r", ref, "-T", "{node}", "-l", "1"],
        capture_output=True,
        text=True
    )
    if result.returncode == 0:
        node = result.stdout.strip()
        if node:
            print(node)
            return 0
    # Match git's error message format
    sys.stderr.write(f"fatal: Needed a single revision\n")
    return 128  # Match git's exit code


def _handle_symbolic(ref: str) -> int:
    """REVP-06: --symbolic outputs ref in symbolic form."""
    # Symbolic means output the input form (e.g., HEAD stays HEAD)
    print(ref)
    return 0
```

3. **Update handle() function to dispatch to handlers:**

```python
def handle(parsed: ParsedCommand) -> int:
    """
    Handle 'git rev-parse' command.

    Supports:
    - --short HEAD: returns short commit hash (7 chars)
    - --show-toplevel: returns repository root path
    - --git-dir: returns .sl directory path
    - --is-inside-work-tree: returns true/false
    - --abbrev-ref HEAD: returns current bookmark name
    - --verify <ref>: validates object reference
    - --symbolic <ref>: outputs in symbolic form
    """
    args = parsed.args

    # REVP-01: --show-toplevel
    if "--show-toplevel" in args:
        return _handle_show_toplevel()

    # REVP-02: --git-dir
    if "--git-dir" in args:
        return _handle_git_dir()

    # REVP-03: --is-inside-work-tree
    if "--is-inside-work-tree" in args:
        return _handle_is_inside_work_tree()

    # REVP-04: --abbrev-ref
    if "--abbrev-ref" in args:
        idx = args.index("--abbrev-ref")
        ref = args[idx + 1] if idx + 1 < len(args) else "HEAD"
        return _handle_abbrev_ref(ref)

    # REVP-05: --verify
    if "--verify" in args:
        idx = args.index("--verify")
        if idx + 1 < len(args):
            return _handle_verify(args[idx + 1])
        else:
            sys.stderr.write("fatal: --verify requires a revision\n")
            return 128

    # REVP-06: --symbolic
    if "--symbolic" in args:
        idx = args.index("--symbolic")
        ref = args[idx + 1] if idx + 1 < len(args) else "HEAD"
        return _handle_symbolic(ref)

    # Existing: --short HEAD pattern (either order)
    if "--short" in args and "HEAD" in args:
        result = subprocess.run(
            ["sl", "whereami"],
            capture_output=True,
            text=True
        )
        if result.returncode == 0:
            # sl whereami returns 40 chars, git rev-parse --short returns 7
            short_hash = result.stdout.strip()[:7]
            print(short_hash)
        else:
            sys.stderr.write(result.stderr)
        return result.returncode

    # Unsupported rev-parse variants
    sys.stderr.write("gitsl: rev-parse flag not supported. Supported: --show-toplevel, --git-dir, --is-inside-work-tree, --abbrev-ref HEAD, --verify, --symbolic, --short HEAD\n")
    return 1
```
  </action>
  <verify>
Run `python -c "import cmd_rev_parse"` - no import errors.

Inspect cmd_rev_parse.py and verify:
- Has 6 helper functions: _handle_show_toplevel, _handle_git_dir, _handle_is_inside_work_tree, _handle_abbrev_ref, _handle_verify, _handle_symbolic
- handle() dispatches to each based on flag presence
- Existing --short HEAD functionality preserved
  </verify>
  <done>
cmd_rev_parse.py expanded with handlers for all 6 new rev-parse flags (REVP-01 through REVP-06). Each flag dispatches to a dedicated helper function.
  </done>
</task>

<task type="auto">
  <name>Task 2: Add E2E tests for all rev-parse flags</name>
  <files>tests/test_rev_parse.py</files>
  <action>
Add comprehensive test classes to `tests/test_rev_parse.py` for each requirement:

```python
class TestRevParseShowToplevel:
    """REVP-01: git rev-parse --show-toplevel returns repo root."""

    def test_show_toplevel_returns_absolute_path(self, sl_repo_with_commit: Path):
        """--show-toplevel returns absolute repository root path."""
        result = run_gitsl(["rev-parse", "--show-toplevel"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        output = result.stdout.strip()
        assert output == str(sl_repo_with_commit)

    def test_show_toplevel_from_subdirectory(self, sl_repo_with_commit: Path):
        """--show-toplevel works from subdirectory."""
        subdir = sl_repo_with_commit / "subdir"
        subdir.mkdir()
        result = run_gitsl(["rev-parse", "--show-toplevel"], cwd=subdir)
        assert result.exit_code == 0
        assert result.stdout.strip() == str(sl_repo_with_commit)

    def test_show_toplevel_fails_outside_repo(self, tmp_path: Path):
        """--show-toplevel fails outside repository."""
        result = run_gitsl(["rev-parse", "--show-toplevel"], cwd=tmp_path)
        assert result.exit_code != 0


class TestRevParseGitDir:
    """REVP-02: git rev-parse --git-dir returns .sl/.hg directory."""

    def test_git_dir_returns_vcs_directory(self, sl_repo_with_commit: Path):
        """--git-dir returns .sl or .hg directory path."""
        result = run_gitsl(["rev-parse", "--git-dir"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        output = result.stdout.strip()
        # Should end with .sl or .hg
        assert output.endswith(".sl") or output.endswith(".hg")
        # Should be inside repo root
        assert output.startswith(str(sl_repo_with_commit))

    def test_git_dir_fails_outside_repo(self, tmp_path: Path):
        """--git-dir fails outside repository."""
        result = run_gitsl(["rev-parse", "--git-dir"], cwd=tmp_path)
        assert result.exit_code != 0


class TestRevParseIsInsideWorkTree:
    """REVP-03: git rev-parse --is-inside-work-tree returns true/false."""

    def test_is_inside_work_tree_returns_true(self, sl_repo_with_commit: Path):
        """--is-inside-work-tree returns 'true' inside repository."""
        result = run_gitsl(["rev-parse", "--is-inside-work-tree"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() == "true"

    def test_is_inside_work_tree_returns_false_outside(self, tmp_path: Path):
        """--is-inside-work-tree returns 'false' outside repository."""
        result = run_gitsl(["rev-parse", "--is-inside-work-tree"], cwd=tmp_path)
        # Note: git returns exit code 0 even outside repo
        assert result.exit_code == 0
        assert result.stdout.strip() == "false"


class TestRevParseAbbrevRef:
    """REVP-04: git rev-parse --abbrev-ref HEAD returns bookmark name."""

    def test_abbrev_ref_returns_bookmark_name(self, sl_repo_with_commit: Path):
        """--abbrev-ref HEAD returns current bookmark name."""
        # Create and activate a bookmark
        run_command(["sl", "bookmark", "my-feature"], cwd=sl_repo_with_commit)

        result = run_gitsl(["rev-parse", "--abbrev-ref", "HEAD"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() == "my-feature"

    def test_abbrev_ref_returns_head_when_detached(self, sl_repo_with_commit: Path):
        """--abbrev-ref HEAD returns 'HEAD' when no bookmark active."""
        # Deactivate any bookmark by going to a commit without bookmark
        run_command(["sl", "bookmark", "-d", "master"], cwd=sl_repo_with_commit, check=False)

        result = run_gitsl(["rev-parse", "--abbrev-ref", "HEAD"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Should return "HEAD" or empty bookmark name when detached
        output = result.stdout.strip()
        assert output in ("HEAD", "")


class TestRevParseVerify:
    """REVP-05: git rev-parse --verify validates object reference."""

    def test_verify_valid_ref_returns_hash(self, sl_repo_with_commit: Path):
        """--verify with valid ref returns full hash."""
        result = run_gitsl(["rev-parse", "--verify", "HEAD"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        output = result.stdout.strip()
        # Should be 40-character hex hash
        assert len(output) == 40
        assert all(c in "0123456789abcdef" for c in output)

    def test_verify_invalid_ref_fails(self, sl_repo_with_commit: Path):
        """--verify with invalid ref returns error."""
        result = run_gitsl(["rev-parse", "--verify", "nonexistent-ref-xyz"], cwd=sl_repo_with_commit)
        assert result.exit_code != 0
        assert "fatal" in result.stderr.lower() or result.exit_code == 128

    def test_verify_bookmark_returns_hash(self, sl_repo_with_commit: Path):
        """--verify with bookmark name returns commit hash."""
        run_command(["sl", "bookmark", "test-bookmark"], cwd=sl_repo_with_commit)

        result = run_gitsl(["rev-parse", "--verify", "test-bookmark"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert len(result.stdout.strip()) == 40


class TestRevParseSymbolic:
    """REVP-06: git rev-parse --symbolic outputs in symbolic form."""

    def test_symbolic_head_returns_head(self, sl_repo_with_commit: Path):
        """--symbolic HEAD returns 'HEAD'."""
        result = run_gitsl(["rev-parse", "--symbolic", "HEAD"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() == "HEAD"

    def test_symbolic_ref_returns_ref(self, sl_repo_with_commit: Path):
        """--symbolic with ref name returns that ref name."""
        result = run_gitsl(["rev-parse", "--symbolic", "master"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() == "master"
```

Ensure the import for `run_command` is present at the top (from conftest):
```python
from conftest import run_gitsl, run_command
```

If run_command is not available, use subprocess directly or check conftest.py for the correct import.
  </action>
  <verify>
Run all rev-parse tests:
```bash
cd /Users/vmakaev/NonWork/gitsl && python -m pytest tests/test_rev_parse.py -v
```
All tests should pass.
  </verify>
  <done>
- 3 tests for REVP-01 (--show-toplevel)
- 2 tests for REVP-02 (--git-dir)
- 2 tests for REVP-03 (--is-inside-work-tree)
- 2 tests for REVP-04 (--abbrev-ref HEAD)
- 3 tests for REVP-05 (--verify)
- 2 tests for REVP-06 (--symbolic)
- REVP-07 already covered by existing tests (--short HEAD)
  </done>
</task>

</tasks>

<verification>
Run full test suite to ensure no regressions:
```bash
cd /Users/vmakaev/NonWork/gitsl && python -m pytest tests/ -v
```

Specifically verify rev-parse requirements:
```bash
cd /Users/vmakaev/NonWork/gitsl && python -m pytest tests/test_rev_parse.py -v
```
</verification>

<success_criteria>
1. REVP-01: `git rev-parse --show-toplevel` returns repository root path (test passes)
2. REVP-02: `git rev-parse --git-dir` returns .sl or .hg directory path (test passes)
3. REVP-03: `git rev-parse --is-inside-work-tree` returns true/false correctly (test passes)
4. REVP-04: `git rev-parse --abbrev-ref HEAD` returns bookmark name or HEAD (test passes)
5. REVP-05: `git rev-parse --verify <ref>` validates references (test passes)
6. REVP-06: `git rev-parse --symbolic <ref>` returns symbolic form (test passes)
7. REVP-07: Existing `--short HEAD` tests continue to pass
8. All existing tests continue to pass (no regressions)
9. Full test suite passes: `python -m pytest tests/ -v`
</success_criteria>

<output>
After completion, create `.planning/phases/21-rev-parse-expansion/21-01-SUMMARY.md`
</output>
