# Git Autonomy Protocol - Technical Design Document

**Version:** 1.0
**Author:** Grid Git Operator Program
**Date:** 2026-01-23

---

## Executive Summary

The Git Autonomy Protocol enables The Grid to manage git operations fully autonomously while maintaining safety guarantees. This document specifies the technical design for automatic branch creation, atomic commits, PR creation, conflict resolution, and safe push operations.

---

## Table of Contents

1. [Design Principles](#design-principles)
2. [Architecture Overview](#architecture-overview)
3. [Branch Management](#branch-management)
4. [Commit Protocol](#commit-protocol)
5. [Push Automation](#push-automation)
6. [Conflict Resolution](#conflict-resolution)
7. [PR Automation](#pr-automation)
8. [Safety Guarantees](#safety-guarantees)
9. [Integration Points](#integration-points)
10. [Configuration](#configuration)
11. [Error Handling](#error-handling)
12. [Future Enhancements](#future-enhancements)

---

## Design Principles

### 1. Safety First
No autonomous operation should risk data loss or corrupt git history. Protected branch rules are enforced at the protocol level, not by convention.

### 2. Atomic Operations
Each git operation is atomic and recoverable. Partial commits or incomplete pushes leave the repository in a known good state.

### 3. Transparency
All git operations are logged and visible. Users can always see what The Grid did to their repository.

### 4. Graceful Degradation
When autonomous operation fails (conflicts, auth issues), the system gracefully falls back to manual intervention with clear guidance.

### 5. Convention Over Configuration
Sensible defaults enable zero-configuration usage while allowing customization for power users.

---

## Architecture Overview

### Component Diagram

```
+-------------------+
|   Master Control  |
|   (Orchestrator)  |
+--------+----------+
         |
         | spawns
         v
+-------------------+     +------------------+
|   Git Operator    |<--->|  Local Git Repo  |
|   (Agent)         |     +------------------+
+--------+----------+            |
         |                       | remote
         | uses                  v
         v               +------------------+
+-------------------+    |  GitHub/GitLab   |
|  /grid:branch     |    |  (Remote)        |
|  (Command)        |    +------------------+
+-------------------+
```

### Responsibility Matrix

| Component | Responsibilities |
|-----------|------------------|
| Master Control | Session lifecycle, spawning operators |
| Git Operator | All git operations, safety enforcement |
| /grid:branch | User-facing branch management commands |
| Executor | Requests commits via Git Operator |

---

## Branch Management

### Automatic Branch Creation

When a Grid session starts on a protected branch, the system automatically creates a feature branch:

```python
def ensure_feature_branch(cluster_name: str) -> str:
    """Ensure we're on a feature branch, create if needed."""

    current = get_current_branch()

    # Already on feature branch
    if not is_protected(current):
        return current

    # Generate branch name from cluster
    slug = slugify(cluster_name)  # "React Todo App" -> "react-todo-app"
    branch_name = f"grid/{slug}"

    # Handle existing branch with same name
    if branch_exists(branch_name):
        branch_name = f"{branch_name}-{timestamp()}"

    # Create and switch
    git_checkout_b(branch_name)

    return branch_name
```

### Branch Naming Algorithm

```python
def generate_branch_name(context: GridContext) -> str:
    """Generate appropriate branch name for context."""

    prefix = config.get("branch_prefix", "grid/")

    if context.type == "cluster":
        # Full project work
        base = slugify(context.cluster_name)
    elif context.type == "quick":
        # Quick task
        base = slugify(context.task_description[:30])
    elif context.type == "debug":
        # Debug session
        base = f"fix-{slugify(context.symptoms[0][:20])}"
    else:
        # Fallback
        base = f"work-{date_slug()}"

    # Ensure uniqueness
    candidate = f"{prefix}{base}"
    if branch_exists(candidate):
        candidate = f"{candidate}-{short_hash()}"

    return candidate
```

### Protected Branch Detection

```python
PROTECTED_BRANCHES = ["main", "master", "production", "release/*"]

def is_protected(branch: str) -> bool:
    """Check if branch is protected."""

    # Check config overrides
    protected = config.get("protected_branches", PROTECTED_BRANCHES)

    for pattern in protected:
        if pattern.endswith("/*"):
            # Wildcard match
            prefix = pattern[:-2]
            if branch.startswith(prefix):
                return True
        elif branch == pattern:
            return True

    return False
```

---

## Commit Protocol

### Atomic Commits per Thread

Each thread in a Grid plan produces exactly one commit:

```python
def commit_thread(thread: Thread, files: List[str], message: CommitMessage) -> str:
    """Create atomic commit for a single thread."""

    # Validate files exist
    for file in files:
        if not os.path.exists(file):
            raise CommitError(f"File not found: {file}")

    # Stage files individually (NEVER git add .)
    for file in files:
        git_add(file)

    # Verify staged matches expected
    staged = get_staged_files()
    if set(staged) != set(files):
        raise CommitError(f"Staged files mismatch. Expected: {files}, Got: {staged}")

    # Create commit
    commit_hash = git_commit(format_message(message))

    return commit_hash
```

### Commit Message Format

```python
@dataclass
class CommitMessage:
    type: str       # feat, fix, refactor, etc.
    scope: str      # block-01, quick, debug
    subject: str    # Brief description (50 chars)
    body: List[str] # Bullet points of changes

def format_message(msg: CommitMessage) -> str:
    """Format commit message according to convention."""

    # Header line
    header = f"{msg.type}({msg.scope}): {msg.subject}"

    if len(header) > 72:
        raise CommitError(f"Header too long: {len(header)} chars (max 72)")

    # Body
    body_lines = [f"- {line}" for line in msg.body]
    body = "\n".join(body_lines)

    return f"{header}\n\n{body}"
```

### Commit Types Taxonomy

| Type | Description | Example |
|------|-------------|---------|
| `feat` | New feature or capability | `feat(auth): add JWT token refresh` |
| `fix` | Bug fix | `fix(api): handle null user response` |
| `refactor` | Code restructure, no behavior change | `refactor(utils): simplify date formatting` |
| `perf` | Performance improvement | `perf(db): add index on user_id` |
| `test` | Test additions/modifications | `test(auth): add login flow tests` |
| `docs` | Documentation only | `docs(api): update endpoint descriptions` |
| `chore` | Tooling, deps, config | `chore(deps): upgrade react to 19` |
| `style` | Formatting, no code change | `style(lint): apply prettier` |

---

## Push Automation

### Push Timing Strategies

```python
class PushStrategy(Enum):
    IMMEDIATE = "immediate"  # After every commit
    WAVE = "wave"            # After each wave completes
    BLOCK = "block"          # After each block completes
    MANUAL = "manual"        # Never auto-push

def should_push(strategy: PushStrategy, event: GridEvent) -> bool:
    """Determine if we should push based on strategy and event."""

    match strategy:
        case PushStrategy.IMMEDIATE:
            return event.type == "commit"
        case PushStrategy.WAVE:
            return event.type == "wave_complete"
        case PushStrategy.BLOCK:
            return event.type == "block_complete"
        case PushStrategy.MANUAL:
            return False
```

### Safe Push Protocol

```python
def safe_push(branch: str, remote: str = "origin") -> PushResult:
    """Push with safety checks."""

    # Pre-flight checks
    if is_protected(branch):
        raise PushError("Cannot push to protected branch")

    # Fetch current remote state
    git_fetch(remote, branch)

    # Analyze divergence
    divergence = analyze_divergence(branch, f"{remote}/{branch}")

    match divergence:
        case Divergence.UP_TO_DATE:
            return PushResult(status="nothing_to_push")

        case Divergence.AHEAD:
            # Safe to push
            git_push(remote, branch)
            return PushResult(status="pushed")

        case Divergence.BEHIND:
            # Need to pull first
            git_pull_rebase(remote, branch)
            git_push(remote, branch)
            return PushResult(status="pulled_and_pushed")

        case Divergence.DIVERGED:
            # Attempt auto-merge
            result = attempt_auto_merge(remote, branch)
            if result.success:
                git_push(remote, branch)
                return PushResult(status="merged_and_pushed")
            else:
                return PushResult(status="conflict", conflicts=result.conflicts)
```

### Divergence Analysis

```python
class Divergence(Enum):
    UP_TO_DATE = "up_to_date"
    AHEAD = "ahead"
    BEHIND = "behind"
    DIVERGED = "diverged"

def analyze_divergence(local: str, remote: str) -> Divergence:
    """Analyze how local and remote have diverged."""

    try:
        base = git_merge_base(local, remote)
    except GitError:
        # Remote doesn't exist yet
        return Divergence.AHEAD

    local_head = git_rev_parse(local)
    remote_head = git_rev_parse(remote)

    if local_head == remote_head:
        return Divergence.UP_TO_DATE
    elif local_head == base:
        return Divergence.BEHIND
    elif remote_head == base:
        return Divergence.AHEAD
    else:
        return Divergence.DIVERGED
```

---

## Conflict Resolution

### Conflict Detection

```python
def detect_conflicts(ours: str, theirs: str) -> List[ConflictFile]:
    """Detect which files would conflict in a merge."""

    base = git_merge_base(ours, theirs)

    # Use git merge-tree for dry-run conflict detection
    result = git_merge_tree(base, ours, theirs)

    conflicts = []
    for file in parse_merge_tree_output(result):
        if file.has_conflict:
            conflicts.append(ConflictFile(
                path=file.path,
                ours_change=file.ours_change,
                theirs_change=file.theirs_change,
                conflict_type=classify_conflict(file)
            ))

    return conflicts
```

### Conflict Classification

```python
class ConflictType(Enum):
    BOTH_MODIFIED = "both_modified"    # Same file changed differently
    DELETE_MODIFY = "delete_modify"    # One deleted, one modified
    ADD_ADD = "add_add"                # Both added same file
    RENAME_RENAME = "rename_rename"    # Both renamed differently

def classify_conflict(file: ConflictFile) -> ConflictType:
    """Classify the type of conflict for resolution strategy."""

    if file.ours_deleted and file.theirs_modified:
        return ConflictType.DELETE_MODIFY
    elif file.theirs_deleted and file.ours_modified:
        return ConflictType.DELETE_MODIFY
    elif file.ours_added and file.theirs_added:
        return ConflictType.ADD_ADD
    elif file.ours_renamed and file.theirs_renamed:
        return ConflictType.RENAME_RENAME
    else:
        return ConflictType.BOTH_MODIFIED
```

### Auto-Resolution Strategies

```python
def attempt_auto_resolve(conflict: ConflictFile) -> Optional[Resolution]:
    """Attempt to auto-resolve a conflict if safe."""

    # Only auto-resolve non-destructive cases
    match conflict.type:
        case ConflictType.BOTH_MODIFIED:
            # Check if changes are in different regions
            if non_overlapping_changes(conflict):
                return merge_non_overlapping(conflict)
            else:
                return None  # Manual resolution needed

        case ConflictType.ADD_ADD:
            # Both added same file - check if identical
            if files_identical(conflict.ours, conflict.theirs):
                return Resolution(keep="ours", reason="identical")
            else:
                return None

        case _:
            return None  # All other cases need manual resolution
```

### Manual Resolution Workflow

```python
def request_manual_resolution(conflicts: List[ConflictFile]) -> ConflictCheckpoint:
    """Create checkpoint for manual conflict resolution."""

    return ConflictCheckpoint(
        type="conflict",
        conflicts=[
            {
                "file": c.path,
                "ours": c.ours_change,
                "theirs": c.theirs_change,
                "type": c.conflict_type.value
            }
            for c in conflicts
        ],
        options=[
            Option("keep_ours", "Keep our changes, discard theirs"),
            Option("keep_theirs", "Keep their changes, discard ours"),
            Option("manual", "Open files and resolve manually"),
            Option("abort", "Abort merge, keep current state")
        ],
        instructions="""
        To resolve manually:
        1. Open conflicting files
        2. Look for <<<<<<< and >>>>>>> markers
        3. Edit to desired state
        4. Remove conflict markers
        5. Stage resolved files: git add {file}
        6. Continue: git merge --continue
        """
    )
```

---

## PR Automation

### PR Content Generation

```python
def generate_pr_content(branch: str, base: str = "main") -> PRContent:
    """Generate PR title, body, and metadata."""

    # Gather commit information
    commits = git_log(f"{base}..{branch}", format="%s")
    commit_details = git_log(f"{base}..{branch}", format="%H %s")
    files_changed = git_diff_stat(base, branch)

    # Determine PR type from commits
    pr_type = infer_pr_type(commits)

    # Generate title
    if len(commits) == 1:
        title = commits[0]
    else:
        title = f"{pr_type}: {summarize_commits(commits)}"

    # Generate body
    body = generate_pr_body(
        commits=commit_details,
        files_changed=files_changed,
        grid_metadata=read_grid_state()
    )

    return PRContent(title=title, body=body, labels=infer_labels(commits))
```

### PR Body Template

```python
PR_BODY_TEMPLATE = """
## Summary
{summary}

## Changes
{commit_list}

## Files Changed
{files_stat}

## Grid Session
- **Cluster:** {cluster_name}
- **Blocks completed:** {blocks_completed}
- **Total commits:** {commit_count}

## Test Plan
{test_plan}

## Screenshots
{screenshots}

---
*Automatically generated by The Grid*
"""

def generate_pr_body(commits, files_changed, grid_metadata) -> str:
    """Fill in PR body template."""

    summary = generate_summary(commits)
    commit_list = format_commit_list(commits)

    # Extract test plan from Grid summaries
    test_plan = extract_test_plan(grid_metadata)

    # Check for screenshots
    screenshots = find_screenshots()
    screenshot_section = format_screenshots(screenshots) if screenshots else "_No UI changes_"

    return PR_BODY_TEMPLATE.format(
        summary=summary,
        commit_list=commit_list,
        files_stat=files_changed,
        cluster_name=grid_metadata.get("cluster", "N/A"),
        blocks_completed=grid_metadata.get("blocks_completed", "N/A"),
        commit_count=len(commits),
        test_plan=test_plan,
        screenshots=screenshot_section
    )
```

### GitHub CLI Integration

```python
def create_pr(content: PRContent, draft: bool = False) -> PRResult:
    """Create PR using GitHub CLI."""

    # Ensure branch is pushed
    ensure_pushed()

    # Check for existing PR
    existing = gh_pr_view()
    if existing:
        return PRResult(
            status="exists",
            number=existing.number,
            url=existing.url
        )

    # Create PR
    args = [
        "gh", "pr", "create",
        "--title", content.title,
        "--body", content.body,
        "--base", content.base
    ]

    if draft:
        args.append("--draft")

    if content.labels:
        args.extend(["--label", ",".join(content.labels)])

    result = subprocess.run(args, capture_output=True, text=True)

    if result.returncode != 0:
        raise PRError(result.stderr)

    # Parse PR URL from output
    pr_url = result.stdout.strip()
    pr_number = int(pr_url.split("/")[-1])

    return PRResult(status="created", number=pr_number, url=pr_url)
```

---

## Safety Guarantees

### Forbidden Operations Matrix

| Operation | Condition | Allowed |
|-----------|-----------|---------|
| `git push --force` | Never automatic | NO |
| `git push --force` | Explicit user confirmation | YES |
| `git reset --hard` | On unshared local branch | YES |
| `git reset --hard` | On shared/pushed branch | NO |
| `git branch -D` | Merged branches | YES |
| `git branch -D` | Unmerged branches | CONFIRM |
| Commit to main | Never | NO |
| Commit to feature | Always | YES |

### Safety Enforcement

```python
class SafetyGuard:
    """Enforces safety rules for git operations."""

    FORBIDDEN_FLAGS = ["--force", "-f", "--force-with-lease", "--no-verify"]

    def validate_command(self, command: List[str]) -> ValidationResult:
        """Validate a git command before execution."""

        # Check for forbidden flags
        for flag in self.FORBIDDEN_FLAGS:
            if flag in command:
                if not self.has_explicit_user_confirmation():
                    return ValidationResult(
                        allowed=False,
                        reason=f"Forbidden flag: {flag}",
                        requires_confirmation=True
                    )

        # Check for protected branch operations
        if self.is_protected_branch_operation(command):
            return ValidationResult(
                allowed=False,
                reason="Operation on protected branch"
            )

        return ValidationResult(allowed=True)

    def is_protected_branch_operation(self, command: List[str]) -> bool:
        """Check if command operates on protected branch."""

        if command[:2] == ["git", "checkout"] and len(command) > 2:
            target = command[2]
            if is_protected(target):
                return False  # Checkout is OK

        if command[:2] == ["git", "commit"]:
            current = get_current_branch()
            if is_protected(current):
                return True

        return False
```

### Audit Logging

```python
def audit_git_operation(operation: str, args: List[str], result: Any):
    """Log git operation for audit trail."""

    entry = {
        "timestamp": datetime.now().isoformat(),
        "operation": operation,
        "args": args,
        "result": str(result),
        "branch": get_current_branch(),
        "commit": get_current_commit(),
        "user": os.environ.get("USER", "unknown")
    }

    # Append to audit log
    audit_path = ".grid/git_audit.jsonl"
    with open(audit_path, "a") as f:
        f.write(json.dumps(entry) + "\n")
```

---

## Integration Points

### Master Control Integration

```python
# MC spawns Git Operator at session start
def start_grid_session(request: str):
    # ... planning ...

    # Ensure feature branch
    Task(
        prompt=f"""
First, read ~/.claude/agents/grid-git-operator.md for your role.

SESSION START

Cluster: {cluster_name}

Ensure we're on an appropriate feature branch.
Report branch status.
""",
        description="Git: Branch setup"
    )
```

### Executor Integration

```python
# Executor requests commit after thread completion
def complete_thread(thread: Thread, files: List[str]):
    # ... implementation work ...

    # Commit via Git Operator
    Task(
        prompt=f"""
First, read ~/.claude/agents/grid-git-operator.md for your role.

COMMIT THREAD

Thread: {thread.name}
Files: {files}
Type: {thread.commit_type}
Scope: {thread.block_id}
Description: {thread.description}

Create atomic commit.
""",
        description=f"Git: Commit {thread.name}"
    )
```

### Wave Completion Hook

```python
# After wave completes, trigger push
def on_wave_complete(wave: Wave):
    if config.get("auto_push") == "wave":
        Task(
            prompt=f"""
First, read ~/.claude/agents/grid-git-operator.md for your role.

WAVE COMPLETE

Wave: {wave.number}
Commits: {wave.commits}

Push to remote if configured.
""",
            description=f"Git: Push wave {wave.number}"
        )
```

---

## Configuration

### .grid/config.json Schema

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "git": {
      "type": "object",
      "properties": {
        "auto_branch": {
          "type": "boolean",
          "default": true,
          "description": "Automatically create feature branch from protected branches"
        },
        "branch_prefix": {
          "type": "string",
          "default": "grid/",
          "description": "Prefix for auto-created branches"
        },
        "auto_push": {
          "type": "string",
          "enum": ["immediate", "wave", "block", "manual"],
          "default": "wave",
          "description": "When to automatically push"
        },
        "auto_pr": {
          "type": "boolean",
          "default": false,
          "description": "Automatically create PR on session complete"
        },
        "protected_branches": {
          "type": "array",
          "items": { "type": "string" },
          "default": ["main", "master", "production"],
          "description": "Branches that require PRs"
        },
        "default_base": {
          "type": "string",
          "default": "main",
          "description": "Default base branch for PRs"
        },
        "commit_signing": {
          "type": "boolean",
          "default": false,
          "description": "Sign commits with GPG"
        },
        "wip_commits": {
          "type": "boolean",
          "default": true,
          "description": "Create WIP commits on session pause"
        }
      }
    }
  }
}
```

### Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `GRID_GIT_AUTO_PUSH` | Push strategy | `wave` |
| `GRID_GIT_AUTO_PR` | Auto-create PRs | `false` |
| `GRID_GIT_BRANCH_PREFIX` | Branch name prefix | `grid/` |
| `GRID_GIT_PROTECTED` | Comma-separated protected branches | `main,master` |

---

## Error Handling

### Error Categories

```python
class GitError(Exception):
    """Base class for git errors."""
    pass

class ProtectedBranchError(GitError):
    """Attempted operation on protected branch."""
    pass

class ConflictError(GitError):
    """Merge conflict detected."""
    def __init__(self, conflicts: List[ConflictFile]):
        self.conflicts = conflicts

class AuthenticationError(GitError):
    """Git authentication failed."""
    pass

class RemoteError(GitError):
    """Remote operation failed."""
    pass
```

### Error Recovery

```python
def handle_git_error(error: GitError) -> ErrorRecovery:
    """Determine recovery strategy for git error."""

    match error:
        case ProtectedBranchError():
            return ErrorRecovery(
                action="create_branch",
                message="Cannot commit to protected branch. Creating feature branch."
            )

        case ConflictError() as e:
            return ErrorRecovery(
                action="checkpoint",
                message="Merge conflicts detected.",
                checkpoint=request_manual_resolution(e.conflicts)
            )

        case AuthenticationError():
            return ErrorRecovery(
                action="checkpoint",
                message="Git authentication required.",
                checkpoint=request_auth_action()
            )

        case RemoteError():
            return ErrorRecovery(
                action="retry",
                message="Remote operation failed. Will retry.",
                retry_after=30
            )
```

---

## Future Enhancements

### Planned Features

1. **Worktree Support**
   - Parallel development on multiple branches
   - Each Grid session in isolated worktree

2. **Stacked PRs**
   - Create chains of dependent PRs
   - Auto-update stack on changes

3. **Bisect Integration**
   - Automated git bisect for bug hunting
   - Integration with Grid Debugger

4. **Hooks System**
   - Pre-commit hooks for Grid validation
   - Post-commit hooks for notifications

5. **Team Collaboration**
   - Branch locking during Grid sessions
   - Real-time collaboration awareness

### Research Areas

1. **Semantic Merge**
   - Use AST analysis for smarter conflict resolution
   - Language-aware merging

2. **Predictive Conflicts**
   - Warn about potential conflicts before they happen
   - Suggest preemptive rebases

3. **AI-Assisted Resolution**
   - LLM-powered conflict resolution suggestions
   - Context-aware merge decisions

---

## Appendix A: Command Reference

### Git Operator Commands

| Command | Description |
|---------|-------------|
| `BRANCH CHECK` | Analyze current git state |
| `CREATE BRANCH {name}` | Create new feature branch |
| `COMMIT THREAD` | Create atomic commit for thread |
| `PUSH` | Push current branch to remote |
| `CREATE PR` | Create pull request |
| `RESOLVE CONFLICT` | Handle merge conflicts |

### /grid:branch Commands

| Command | Description |
|---------|-------------|
| `/grid:branch` | Show branch status |
| `/grid:branch create {name}` | Create feature branch |
| `/grid:branch switch {name}` | Switch branches |
| `/grid:branch pr` | Create PR |
| `/grid:branch cleanup` | Delete merged branches |
| `/grid:branch list` | List Grid branches |
| `/grid:branch sync` | Sync with main |

---

## Appendix B: Commit Message Examples

### Feature Commits

```
feat(block-01): implement user authentication

- Add JWT token generation with RS256
- Create login and logout endpoints
- Implement refresh token rotation
- Add password hashing with bcrypt
```

### Fix Commits

```
fix(api): handle null user response gracefully

- Add null check before accessing user properties
- Return 404 instead of 500 for missing users
- Add test case for null user scenario
```

### Refactor Commits

```
refactor(utils): simplify date formatting utilities

- Extract common date patterns to constants
- Replace moment.js with native Intl.DateTimeFormat
- Remove unused timezone conversion functions
```

---

*End of Technical Design Document*
