Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

This chapter covers git-paw’s internal architecture: module structure, data flow, and key design decisions.

Module Diagram

┌─────────────────────────────────────────────────┐
│                    main.rs                       │
│              (entry point, dispatch)             │
├────────┬──────────┬───────────┬─────────────────┤
│        │          │           │                  │
│   cli.rs    interactive.rs  config.rs   error.rs │
│  (clap)    (dialoguer UI)   (TOML)    (PawError) │
│        │          │           │                  │
├────────┴──────────┴───────────┴─────────────────┤
│                                                   │
│   detect.rs      git.rs      tmux.rs  session.rs │
│  (PATH scan)   (worktrees)  (builder)  (JSON)    │
│                                                   │
└───────────────────────────────────────────────────┘

Module Responsibilities

ModuleFilePurpose
CLIsrc/cli.rsArgument parsing with clap v4 derive macros. Defines all subcommands, flags, and help text.
Detectionsrc/detect.rsScans PATH for 8 known AI CLI binaries. Resolves custom CLIs from config. Merges and deduplicates.
Gitsrc/git.rsValidates git repos, lists branches (local + remote, deduplicated), creates/removes worktrees, derives safe directory names.
Tmuxsrc/tmux.rsBuilder pattern for tmux operations. Creates sessions, splits panes, sends commands, applies tiled layout, sets pane titles.
Sessionsrc/session.rsPersists session state to JSON files under ~/.local/share/git-paw/sessions/. Atomic writes, crash recovery.
Configsrc/config.rsParses TOML from global (~/.config/git-paw/config.toml) and per-repo (.git-paw/config.toml). Merges with repo-wins semantics.
Interactivesrc/interactive.rsTerminal prompts via dialoguer. Mode picker, branch multi-select, CLI picker. Skips prompts when flags are provided.
Errorsrc/error.rsPawError enum with thiserror. Actionable error messages and distinct exit codes.

Start Flow

The start command is the primary flow. Here’s what happens step by step:

git paw start
     │
     ▼
┌─ Check for existing session ──────────────────────┐
│                                                     │
│  Session active + tmux alive?  ──yes──► Reattach   │
│         │ no                                        │
│  Session saved + tmux dead?   ──yes──► Recover     │
│         │ no                                        │
│  No session                   ──────► Fresh start  │
└─────────────────────────────────────────────────────┘
     │
     ▼ (fresh start)
┌─ Validate git repo ─────────────────────────────────┐
│  git.validate_repo() → repo root path               │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Load config ────────────────────────────────────────┐
│  config.load_config() → merged PawConfig             │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Detect CLIs ────────────────────────────────────────┐
│  detect.detect_clis() → Vec<CliInfo>                 │
│  (auto-detected + custom, deduplicated)              │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Interactive selection ──────────────────────────────┐
│  interactive.run_selection()                          │
│  → Vec<(branch, cli)> mappings                       │
│  (skipped if --cli + --branches provided)            │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Create worktrees ───────────────────────────────────┐
│  git.create_worktree() for each branch               │
│  → ../project-branch-name/ directories               │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Build tmux session ────────────────────────────────┐
│  TmuxSessionBuilder                                  │
│    .session_name("paw-project")                      │
│    .pane(branch, worktree, cli) × N                  │
│    .mouse(true)                                      │
│    .build() → TmuxSession with command sequence      │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Save session state ────────────────────────────────┐
│  session.save_session() → atomic JSON write          │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Attach ─────────────────────────────────────────────┐
│  tmux.attach() → user enters tmux session            │
└──────────────────────────────────────────────────────┘

Worktree Lifecycle

Git worktrees are the foundation of git-paw’s parallel workflow.

Creation

For a project named my-app and branch feature/auth-flow:

my-app/                         ← main repo (current directory)
my-app-feature-auth-flow/       ← worktree (created by git-paw)
my-app-feat-api/                ← worktree (created by git-paw)

Worktrees are created as siblings of the main repo directory. The naming convention is <project>-<sanitized-branch> where slashes become hyphens.

Lifecycle states

create_worktree()          stop              start (recover)
     │                      │                     │
     ▼                      ▼                     ▼
  [exists on disk]  →  [still on disk]  →  [reused as-is]
                                                  │
                                            purge │
                                                  ▼
                                          [removed from disk]

Key points:

  • Stop preserves worktrees — uncommitted work survives
  • Recover reuses existing worktrees — no data loss
  • Purge removes worktrees — git worktree remove followed by prune

Session State

Session state is persisted as JSON under ~/.local/share/git-paw/sessions/:

{
  "session_name": "paw-my-app",
  "repo_path": "/Users/you/projects/my-app",
  "project_name": "my-app",
  "created_at": "2025-01-15T10:30:00Z",
  "status": "active",
  "worktrees": [
    {
      "branch": "feat/auth",
      "worktree_path": "/Users/you/projects/my-app-feat-auth",
      "cli": "claude"
    },
    {
      "branch": "feat/api",
      "worktree_path": "/Users/you/projects/my-app-feat-api",
      "cli": "gemini"
    }
  ]
}

Atomic writes

Session state is written atomically: write to a temporary file, then rename. This prevents corruption if the process is killed mid-write.

Effective status

The on-disk status may not reflect reality (e.g., tmux was killed externally). git-paw checks the actual tmux state:

File statustmux alive?Effective status
activeYesActive (reattach)
activeNoStopped (recover)
stoppedN/AStopped (recover)
No fileN/ANo session

Tmux Builder Pattern

The tmux module uses a builder pattern that accumulates operations as data structures rather than immediately executing shell commands. This enables:

  • Testability — generate commands without executing them
  • Dry run — print the plan without side effects
  • Atomicity — validate the full plan before running anything
#![allow(unused)]
fn main() {
TmuxSessionBuilder::new()
    .session_name("paw-my-app")
    .pane(PaneSpec { branch, worktree_path, cli_command })
    .pane(PaneSpec { ... })
    .mouse(true)
    .build()
    // → TmuxSession { name, commands: Vec<TmuxCommand> }
}

The built TmuxSession can be inspected, printed (dry run), or executed.

Error Strategy

All errors flow through PawError (defined with thiserror). Each variant carries an actionable message telling the user what went wrong and how to fix it. No panics in non-test code — all Result propagation.

Exit codes:

  • 0 — success
  • 1 — operational error
  • 2 — user cancelled