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

Introduction

git-paw (Parallel AI Worktrees) orchestrates multiple AI coding CLI sessions across git worktrees from a single terminal using tmux.

Working with AI coding assistants like Claude, Codex, or Antigravity is powerful — but what if you could run them in parallel across multiple branches at once? That’s exactly what git-paw does.

The Problem

You have a feature branch, a bugfix branch, and a refactoring branch. You want an AI assistant working on each one simultaneously. Normally you’d need to:

  1. Open multiple terminals
  2. Create git worktrees manually
  3. Navigate to each worktree
  4. Launch your AI CLI in each one
  5. Juggle between them

The Solution

With git-paw, you run a single command:

git paw

git-paw will:

  • Detect which AI CLIs you have installed (Claude, Codex, Antigravity, Aider, etc.)
  • Prompt you to pick branches and a CLI (or different CLIs per branch)
  • Create git worktrees for each selected branch
  • Launch a tmux session with one pane per branch, each running your chosen AI CLI
  • Persist the session state so you can stop, resume, or recover after crashes

Key Features

  • One command to go from zero to parallel AI sessions
  • Smart start — reattaches to active sessions, recovers crashed ones, or launches fresh
  • Live branch-set editing (v0.6.0) — git paw add and git paw remove attach or detach a single agent mid-session, no stop/purge/restart, the other agents untouched
  • Per-branch CLI selection — use Claude on one branch and Codex on another
  • Session persistence — stop and resume without losing your place
  • Custom CLI support — register any AI CLI binary, not just the built-in ones
  • Presets — save branch + CLI combos in config for one-command launch
  • Non-interactive mode — pass --cli and --branches flags for scripting
  • Dry run — preview what git-paw will do before it does it

How It Works

┌──────────────────────────────────────────────────────┐
│                    tmux session                       │
│  ┌────────────────────┐  ┌────────────────────────┐  │
│  │  feat/auth → claude │  │  feat/api → claude     │  │
│  │                     │  │                        │  │
│  │  (git worktree)     │  │  (git worktree)        │  │
│  │                     │  │                        │  │
│  ├────────────────────┤  ├────────────────────────┤  │
│  │  fix/bug → codex    │  │  refactor/db → aider   │  │
│  │                     │  │                        │  │
│  │  (git worktree)     │  │  (git worktree)        │  │
│  │                     │  │                        │  │
│  └────────────────────┘  └────────────────────────┘  │
└──────────────────────────────────────────────────────┘

Each pane runs in its own git worktree, so there are no branch conflicts. Your AI assistants work independently and in parallel.

Requirements

  • Git 2.5+ (worktree support)
  • tmux 1.8+
  • At least one AI coding CLI installed (see Supported AI CLIs)
  • macOS or Linux (Windows via WSL only)

Next Steps

Machine-Readable Docs (for Agents)

This site — https://bearicorn.github.io/git-paw/ — also publishes an agent-friendly discovery surface so an LLM can find and target the right page without crawling every chapter:

  • llms.txt — an llmstxt.org-style index listing every page with a one-line summary and its absolute URL.
  • sitemap.xml and robots.txt — standard crawl and discovery files.
  • Per-page metadata — each page embeds a <meta name="description"> and a JSON block (title, canonical URL, section anchor ids) in its <head>, so an agent can identify a page and jump to a section from a single fetch.

These artifacts are generated deterministically from the documentation sources on every build, so they never drift from the published content.

Installation

Prerequisites

Before installing git-paw, ensure you have:

  • Git 2.5 or later (worktree support)
  • tmux 1.8 or later

git paw doctor checks both versions for you.

Installing tmux

macOS:

brew install tmux

Ubuntu / Debian:

sudo apt install tmux

Fedora:

sudo dnf install tmux

Arch Linux:

sudo pacman -S tmux

Install git-paw

cargo install git-paw

Via Homebrew

brew install bearicorn/tap/git-paw

Shell installer

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/bearicorn/git-paw/releases/latest/download/git-paw-installer.sh | sh

From source

git clone https://github.com/bearicorn/git-paw.git
cd git-paw
cargo install --path .

Verify installation

git-paw --version

You should see output like:

git-paw 0.16.0

Since git-paw is named with the git- prefix, git recognizes it as a subcommand. Both of these work:

git-paw --help
git paw --help

Shell Completions

git paw completions <shell> prints a completion script to stdout, generated from the CLI definition. Install it through your shell’s standard mechanism:

bash:

git paw completions bash > /etc/bash_completion.d/git-paw
# or, for a user-local install:
git paw completions bash > ~/.local/share/bash-completion/completions/git-paw

zsh:

git paw completions zsh > "${fpath[1]}/_git-paw"

fish:

git paw completions fish > ~/.config/fish/completions/git-paw.fish

Open a new shell (or re-source your shell config) to pick up the completions.

Platform Support

PlatformSupport
macOS (ARM / Apple Silicon)Full support
macOS (x86_64 / Intel)Full support
Linux (x86_64)Full support
Linux (ARM64 / aarch64)Full support
WindowsWSL only

Windows (WSL)

git-paw requires tmux, which is not natively available on Windows. Use Windows Subsystem for Linux (WSL):

# Install WSL (PowerShell as admin)
wsl --install

# Then inside WSL:
sudo apt install tmux
cargo install git-paw

All git-paw features work inside WSL. Your AI CLIs must also be installed within the WSL environment.

Install an AI CLI

git-paw needs at least one AI coding CLI installed. See Supported AI CLIs for the full list. Some popular options:

# Claude Code
npm install -g @anthropic-ai/claude-code

# OpenAI Codex
npm install -g @openai/codex

# Aider
pip install aider-chat

Next Steps

Quick Start: Same CLI Mode

This walkthrough shows how to launch git-paw with the same AI CLI on all branches — the most common workflow.

Prerequisites

  • git-paw installed
  • tmux installed
  • At least one AI CLI installed (e.g., claude)
  • A git repository with multiple branches

Step 1: Navigate to your repo

cd ~/projects/my-app

Step 2: Launch git-paw

git paw

If neither your repo nor your global config has a [supervisor] section, git-paw first asks Start in supervisor mode? [y/N] — answer n (the default) for this walkthrough.

Step 3: Select branches

A multi-select list of all your branches (sorted by name) appears with type-to-filter search:

Select branches (type to filter, ctrl-u to clear, space to toggle, enter to confirm)
filter:
  [x] feat/api
  [x] feat/auth
  [ ] fix/typo
  [ ] main
> [x] refactor/db

Type to narrow the list (case-insensitive substring match), use arrow keys to navigate, Space to toggle, Ctrl-U to clear the filter, and Enter to confirm. Branches you toggle stay selected even if a later filter hides them.

Step 4: Select your mode

git-paw presents a mode picker:

CLI assignment mode:
> Same CLI for all branches
  Different CLI per branch

Select Same CLI for all branches and press Enter.

Step 5: Select your CLI

Pick which AI CLI to use on all selected branches. Detected CLIs are listed by display name, with the binary in parentheses:

Select AI CLI for all branches:
  Antigravity (agy)
> Claude (claude)
  Codex (codex)

Step 6: git-paw does the rest

git-paw now:

  1. Creates a git worktree for each selected branch
  2. Creates a tmux session named paw-my-app
  3. Opens one pane per branch
  4. Launches your chosen CLI in each pane
  5. Saves the session state for later recovery

The worktrees land under .git-paw/worktrees/ (for example .git-paw/worktrees/feat-auth/) in a repo set up with git paw init, or beside the repo (../my-app-feat-auth/) when no worktree_placement is configured — see Worktree Placement. Nothing else is printed on success: git-paw attaches you straight to the session. (From a non-interactive terminal it prints Session 'paw-my-app' started in detached mode. and a tmux attach -t paw-my-app hint instead.)

You’re now inside a tmux session with three panes, each running Claude in its own worktree. Each pane’s border label shows its pane index and branch (with [broker] enabled = true, the dashboard takes pane 0 and the agents start at pane 1):

┌─── 0: feat/api ───────────────┬─── 1: feat/auth ──────────────┐
│                                │                                │
│  Claude is ready to help...    │  Claude is ready to help...    │
│                                │                                │
├─── 2: refactor/db ────────────┴────────────────────────────────┤
│                                                                 │
│  Claude is ready to help...                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Mouse mode is enabled by default — click a pane to switch to it, or drag borders to resize.

Non-interactive shortcut

Skip all prompts by passing flags:

git paw start --cli claude --branches feat/auth,feat/api,refactor/db

What’s next

  • Switch panes: Click with mouse, or use Ctrl-b then arrow keys
  • Detach: Press Ctrl-b d to detach from tmux (session keeps running)
  • Reattach: Run git paw attach (or git paw again — it detects the active session and reattaches)
  • Stop: Run git paw stop to kill tmux but keep worktrees
  • Purge: Run git paw purge to remove everything

See the User Guide for the full details.

Quick Start: Per-Branch CLI Mode

This walkthrough shows how to assign different AI CLIs to different branches — useful when you want to compare AI assistants or use specialized tools for specific tasks.

Scenario

You have three branches and want to use:

  • Claude for the auth feature (complex logic)
  • Codex for the API work (lots of boilerplate)
  • Aider for the database refactor (incremental edits)

Step 1: Launch git-paw

cd ~/projects/my-app
git paw

(If neither your repo nor your global config has a [supervisor] section, git-paw first asks Start in supervisor mode? [y/N] — answer n.)

Step 2: Select branches

Select branches (type to filter, ctrl-u to clear, space to toggle, enter to confirm)
filter:
  [x] feat/api
  [x] feat/auth
  [ ] main
> [x] refactor/db

Type to filter the list (case-insensitive substring match); selections persist across filter changes, and Ctrl-U clears the filter.

Step 3: Select per-branch mode

CLI assignment mode:
  Same CLI for all branches
> Different CLI per branch

Step 4: Assign a CLI to each branch

git-paw prompts you for each selected branch in turn (in the order they are listed above):

Select CLI for feat/api:
  Aider (aider)
  Claude (claude)
> Codex (codex)
Select CLI for feat/auth:
  Aider (aider)
> Claude (claude)
  Codex (codex)
Select CLI for refactor/db:
> Aider (aider)
  Claude (claude)
  Codex (codex)

Step 5: Watch it launch

git-paw creates a worktree per branch, opens the paw-my-app tmux session with each branch’s chosen CLI in its own pane, and attaches you to it — nothing else is printed on success.

Each pane’s border label shows its pane index and branch (with [broker] enabled = true, the dashboard takes pane 0 and the agents start at pane 1):

┌─── 0: feat/api ───────────────┬─── 1: feat/auth ──────────────┐
│                                │                                │
│  Codex is ready...             │  Claude is ready to help...    │
│                                │                                │
├─── 2: refactor/db ────────────┴────────────────────────────────┤
│                                                                 │
│  Aider v0.x loaded...                                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Comparing approaches

Per-branch mode is great for:

  • A/B testing AI assistants — give the same task to Claude and Codex, compare results
  • Specialization — use a code-generation-focused tool for boilerplate and a reasoning-focused tool for complex logic
  • Trying new tools — test a new AI CLI on one branch while using your trusted tool on others

Recovery

Session state captures the per-branch CLI assignments. If your terminal closes or tmux crashes:

git paw

git-paw detects the saved session, recreates tmux, and relaunches each branch with its assigned CLI — no re-selection needed.

Next Steps

  • User Guide — session management, presets, dry-run, and more
  • Configuration — save per-branch presets in config

Quick Start: Orchestrator Mode

This walkthrough shows how to launch git-paw in supervisor mode and drive it from your own agent session, the orchestrator. Several coding agents work in parallel; the orchestrator verifies them, runs tests between merges, and publishes a final summary.

What Orchestrator Mode Does

Supervisor mode with a spec flag (git paw start --supervisor --from-all-specs) launches every pending spec as its own coding agent in a tmux pane, with the dashboard above them. (Bare git paw start --supervisor, with no spec flag, opens the interactive branch picker instead.)

The orchestrator runs outside the tmux session. No orchestrator runs inside the session. The orchestrator is your own agent session (any agent CLI you like), run at the repository root and outside tmux. It loads the exported orchestrator skill and the per-session context, then drives the run through the bundled helpers (.git-paw/scripts/sweep.sh, broker.sh) or git-paw’s MCP server. It runs the configured test_command between merges, walks branches in topological order based on declared dependencies, and publishes a final summary status when the run is complete.

It does not approve permission prompts. Whether a command runs without asking is your agent CLI’s decision, and anything it prompts for is escalated to you. For a run that proceeds without stopping, pre-approve the commands in that CLI’s own allow rules, or use agent_approval = "full-auto" inside a sandbox.

Prerequisites

  • The standard git-paw prerequisites: git-paw installed, tmux, an AI CLI on PATH, and a git repo.
  • [broker] enabled = true in your config. Supervisor mode requires the coordination broker.
  • A [supervisor] section is recommended but optional. If it is missing, --supervisor runs on built-in defaults, and a plain git paw start asks Start in supervisor mode?. No orchestrator CLI is needed to launch: the coding agents’ CLI resolves from --cli, then default_cli, then [supervisor].cli, and the launch fails only when none of them is set.
  • A specs directory configured via [specs] (default: specs/) containing at least one pending spec.

Smallest Runnable Example

1. Initialize the repository

git paw init

git paw init writes .git-paw/config.toml (asking whether to enable supervisor mode by default and, if you do, for the test command) and installs the bundled helpers under .git-paw/scripts/. It also exports two skills and adds one pointer section:

  • .git-paw/skills/orchestrator/SKILL.md is the orchestrator skill, rendered with your gate commands. It is the procedure your agent follows.
  • .git-paw/skills/cli-setup/SKILL.md holds the consent rules for git paw setup-cli.
  • A section between <!-- git-paw-orchestrator:start --> and <!-- git-paw-orchestrator:end --> is added to your root AGENTS.md. It tells any agent reading the file that, when a git-paw session is active, it is that session’s orchestrator and should read the two files above.

Both skills follow the standard skill-directory layout (a SKILL.md with name and description frontmatter), so a skill-aware agent CLI can load them directly. Copy or symlink them into your CLI’s own skill directory if it needs them there. .git-paw/skills/ is gitignored, and git paw start re-renders both files, so they never lag your config or the binary. Re-running git paw init reports Updated only for a file whose content changed.

2. Create one OpenSpec-format spec

mkdir -p specs/add-greeting
cat > specs/add-greeting/tasks.md <<'MD'
## Add a greeting helper

- [ ] Add `pub fn greet(name: &str) -> String` to `src/lib.rs`.
- [ ] Add a unit test asserting `greet("paw") == "hello, paw"`.
- [ ] Run `cargo test` and make sure it passes.
MD

3. Configure broker + supervisor

.git-paw/config.toml:

[broker]
enabled = true
port = 9119

[specs]
dir = "specs"
type = "openspec"

[supervisor]
enabled = true
cli = "claude"
test_command = "cargo test"
agent_approval = "auto"

4. Launch

git paw start --supervisor --from-all-specs

git-paw creates a worktree for each spec and opens a tmux session. The dashboard is in pane 0 (a full-width top row). There is one coding agent per spec in the panes below it, from pane 1 onwards. The launch then prints the attach command and the two orchestrator paths:

Supervisor session 'paw-myproject' launched with 1 coding agent(s).
Orchestrator context: .git-paw/AGENTS.local.md (point your orchestrating agent session at it)
Orchestrator skill:   .git-paw/skills/orchestrator/SKILL.md
Attach with:  tmux attach -t paw-myproject

.git-paw/AGENTS.local.md is the short session context. It holds the session name, the broker URL, the helper paths, any [governance] documents and, under --unattended, the drive-loop directive, and it points at the skill. It is the only file that carries this session’s values. The exported skill carries none, so the same SKILL.md serves every session.

5. Load the skill in your own agent

Open your own agent session at the repository root, outside tmux. Have it load .git-paw/skills/orchestrator/SKILL.md and read .git-paw/AGENTS.local.md. An agent that reads the root AGENTS.md finds both through the pointer section. Over MCP, get_skill with the name orchestrator serves the same skill (supervisor is accepted as an alias).

From there the orchestrator:

  1. Follows events. It blocks on the broker’s event cursor (the MCP wait_for_events tool, or .git-paw/scripts/sweep.sh events), carrying the returned cursor forward. It inspects the session when an event or a stalled wave says to look, so it never polls pane captures on a timer. get_session_status and sweep.sh status give the roster at any time.
  2. Verifies. It runs the five gates against each branch that reports done. It publishes a verdict only through the credential-carrying helpers: sweep.sh verified <agent> <message>, sweep.sh feedback-gate <agent> <gate> <message> or sweep.sh conflict-ack <agent-a> <agent-b> <file>. In the root session, the MCP publish_verified and publish_feedback tools do the same.
  3. Answers. It replies to a worker’s agent.question with sweep.sh answer <agent> <text> [re].
  4. Merges. Once every expected agent is verified, it fast-forwards each branch in topological order, runs the test command after each merge, and announces each advance with sweep.sh advanced-main <merged-branch> <summary>. In an attended run it may confirm the merge order with you first.

When every branch has merged (or been skipped), it publishes a final agent.status summarising which branches merged and which were skipped, and why.

6. Pair it with --unattended

git paw start --unattended --from-all-specs

--unattended adds an in-process drive loop: it keeps the coding-agent panes moving and escalates every live permission prompt to the broker, approving none. Your orchestrator reads those escalations through the event surface and relays each prompt to you. It runs sweep.sh approve <pane> only when you say so. In an unattended run the orchestrator merges as soon as every expected agent is verified, without asking first. It escalates only on a conflict (a non-fast-forward branch or a dependency cycle), an exhausted correction cycle, or a regression. If the loop exits on its heartbeat, re-run git paw start --unattended to re-attach a drive loop to the still-live session.

Your driving session can itself be remote-controlled from a phone through its own agent CLI. git-paw needs no extra topology for that. See Remote control.

Authoring a Change from Your Own Agent

An orchestrator connected over MCP can write the OpenSpec change it then launches, even without file tools of its own. Repository writes are off by default; you opt in per repository:

  1. Enable the switch in the repository’s own .git-paw/config.toml (the user-level config cannot enable it):

    [mcp]
    allow_repo_writes = true
    
  2. Restart the MCP server at the repository root. The switch is read once at startup, and the write tools exist only for a server in the main working tree.

  3. Scaffold the change with create_spec and a kebab-case name, such as add-greeting.

  4. Write each artifact with write_spec_artifact: proposal.md, one specs/<capability>/spec.md per capability, tasks.md, and anything else your workflow uses.

  5. Check the structure with validate_spec, and fix what it reports.

  6. Run your project’s own spec validator. validate_spec checks only a subset of the rules.

  7. Review git diff and commit yourself, following your project’s conventions. The tools never stage or commit.

See Spec authoring for the tools’ parameters and the confinement rules.

Skipping Supervisor for One Session

If your project sets [supervisor] enabled = true (the recommended setup for active development), every git paw start launches in supervisor mode. To skip it for a single session, pass --no-supervisor. This suits a quick debug-only run or a one-off branch flip, and you don’t need to edit the config:

# Project config says [supervisor] enabled = true, but we want a plain session
git paw start --no-supervisor --cli claude --branches feat/quick-fix

--no-supervisor is the highest-precedence step in the supervisor mode resolution chain. It wins over both [supervisor] enabled = true in config and any prompt. It is mutually exclusive with --supervisor; passing both fails at parse time.

The flag only affects the current session. Your config is untouched, so the next git paw start returns to supervisor mode as configured.

Key Config Knobs

The full reference lives in Configuration → Supervisor. The fields you will reach for most often:

FieldWhat it controls
[supervisor].enabledDefault to supervisor mode without passing --supervisor.
[supervisor].cliThe orchestrator’s CLI, named in its context (falls back to default_cli); also the coding agents’ last CLI fallback. No pane runs it.
[supervisor].test_commandCommand run after each agent reports done and again after every merge. Rendered into the exported skill.
[supervisor].agent_approval"manual", "auto", or "full-auto". Translates into CLI permission flags for the coding agents.
[supervisor].approvalThe orchestrator’s own level, reserved for git-paw-initiated orchestrator invocations. Still parsed; it changes no pane command.
[supervisor].manual_approvals_logRecords every prompt an --unattended run escalates, for git paw approvals to aggregate (default on).

[supervisor.auto_approve] and [supervisor.common_dev_allowlist] are deprecated and have no effect. They still parse so old configs load, but each file declaring them warns once. Pre-approve routine commands in your agent CLI’s own allow rules instead. See Deprecated auto-approval tables.

See Configuration → Broker for [broker] settings and Configuration → Dashboard if you want the live broker-message panel turned on.

Broker Wire Format

Every coordination message the orchestrator and its agents exchange goes through the broker as a JSON envelope. The canonical example below comes from src/broker/messages.rs. The broker accepts an agent_id of supervisor or a lowercase two-part slug: letters, digits and -, with the parts joined by - or /. git-paw itself publishes the slug form, so branch feat/auth becomes feat-auth.

{"type": "agent.status", "agent_id": "feat-auth", "payload": {"status": "working", "modified_files": ["src/auth.rs"], "message": "wiring JWT verifier"}}

Nobody hand-writes these envelopes:

  • The coding agents publish theirs through .git-paw/scripts/broker.sh (status, artifact, blocked, question, intent) or the MCP publish tools.
  • The orchestrator publishes through .git-paw/scripts/sweep.sh (status-publish, verified, feedback-gate, conflict-ack, learn, answer, advanced-main) or broker.sh --agent supervisor (question, intent).
  • The filesystem watcher and the post-commit hook publish agent.status and agent.artifact automatically.

The rest of the message set is agent.blocked, agent.intent, agent.question, agent.answer, agent.feedback, agent.verified, agent.advanced-main and agent.learning, plus the broker-emitted supervisor.verify-now. See Agent Coordination for the full reference with payload-field semantics.

Where to Look When Things Go Wrong

When a run misbehaves, these places have everything you need:

  1. Dashboard pane (pane 0). With [broker] enabled = true the dashboard runs in pane 0 of the session and updates every second. Use it to spot which agent is blocked, which is working, and how long it has been since each agent’s last status update. See the Dashboard chapter for status-symbol meanings and controls.
  2. Your orchestrator session. The orchestrator runs in your own agent session outside tmux and reports there as it walks agents through the merge plan. Its broker messages also appear in the dashboard’s broker log.
  3. The orchestrator’s final status. When its merge loop completes, the orchestrator publishes a final agent.status (agent supervisor). It lists which branches merged cleanly, which were skipped and why, and any regressions it hit. It shows up in the dashboard’s broker log panel.
  4. <session_name>.broker.log. This plain-text audit trail has one line per message the broker handled ([<seq>] <unix-seconds> [<agent_id>] <message summary>).
    • It lives in git-paw’s sessions directory, not in .git-paw/. On Linux that is ~/.local/share/git-paw/sessions/<session_name>.broker.log (or under $XDG_DATA_HOME). On macOS it is ~/Library/Application Support/git-paw/sessions/<session_name>.broker.log.
    • The name carries the session, so two repositories each keep their own log.
    • Tail it live with tail -f to watch coordination as it happens. The file is appended every five seconds; see Agent Coordination for the message types.

To inspect an individual agent’s pane history, run .git-paw/scripts/sweep.sh capture <pane>, or tmux capture-pane -p -t <session>:0.<pane> against the session named in .git-paw/AGENTS.local.md. The agent panes are in window 0; the session’s second window, broker, hosts the broker.

Governance

If your project has existing governance docs (ADRs, DoD, security checklist, test strategy, constitution), point the orchestrator at them via the [governance] table in .git-paw/config.toml. When set, those paths are written into the session context (.git-paw/AGENTS.local.md), and the orchestrator consults each doc as a sub-step of its spec-audit step. Findings flow through the existing agent.feedback errors path, with no new wire format. See the Governance chapter for the full config, what the orchestrator checks per doc, and illustrative starting-point examples for each doc type.

What’s Next

User Guide

This guide covers the full range of git-paw features beyond the quick starts.

Starting Sessions

Interactive start

Running git paw (or git paw start) with no flags launches the interactive flow:

  1. Branch selection — multi-select with type-to-filter search (type to narrow, ctrl-u to clear, space to toggle, enter to confirm)
  2. Mode selection — same CLI for all branches, or different CLI per branch
  3. CLI selection — single pick (uniform) or per-branch assignment

Smart start behavior

git paw start inspects the current repo and decides what to do:

StateBehavior
Active tmux session existsReattaches immediately (with no TTY — a script — it refuses with an error naming the session instead)
Paused sessionRestarts the dashboard/broker and reattaches; the CLI panes are untouched
Stopped session (git paw stop, or a paused session whose tmux died), or a live tmux session whose agent panes have all dropped back to a shellRecovers: reuses worktrees, recreates tmux, relaunches CLIs (resuming conversations where the CLI supports it)
Stale receipt (says active, but its tmux session is gone — a crash or reboot)Clears the receipt the way git paw purge --stale does, then launches fresh
No sessionFull interactive launch

You never need to think about whether to “start” or “resume” — just run git paw.

Non-interactive start

Skip prompts with flags:

# Specify both CLI and branches — no prompts at all
git paw start --cli claude --branches feat/auth,feat/api

# Specify just CLI — still prompted for branches
git paw start --cli claude

# Specify just branches — still prompted for CLI
git paw start --branches feat/auth,feat/api

Using presets

Define named presets in your config (see Configuration):

git paw start --preset backend

This uses the branches and CLI defined in the [presets.backend] section of your config.

CLI Modes

Same CLI for all branches

The default mode. Every branch gets the same AI CLI. Best for:

  • Working on related features with your preferred tool
  • Batch processing branches with a single assistant

Different CLI per branch

Assign a different CLI to each branch. Best for:

  • Comparing AI assistants side by side
  • Using specialized tools for specific tasks
  • Trying a new CLI on one branch while keeping your usual tool on others

Session Management

Checking status

git paw status

Displays the current session state:

Session: paw-my-app
Status:  🟢 active
Tmux:    running

  feat/auth → claude (/path/to/my-app/.git-paw/worktrees/feat-auth)
  feat/api → claude (/path/to/my-app/.git-paw/worktrees/feat-api)
  refactor/db → aider (/path/to/my-app/.git-paw/worktrees/refactor-db)

With the broker enabled, a Broker: line shows its URL and whether it is responding. git paw status --json emits the same information machine-readably, including each worker’s cli_session_id.

Status indicators:

  • 🟢 active — tmux session is running
  • 🔵 paused — git paw pause detached it; CLI panes are still running
  • 🟡 stopped — session state saved and tmux not running, or tmux up but every agent pane exited (git paw start recovers)
  • 🔴 stale — the receipt claims active, but the tmux session is gone; git paw start self-heals it, git paw purge --stale clears it
  • No session — nothing saved for this repo

Reattaching

git paw attach

Reattaches your terminal to the session running for this repository — the same as tmux attach -t paw-<project>, without needing the exact session name. It errors with a pointer to git paw start when no session is running.

Stopping a session

git paw stop

This kills the tmux session but preserves:

  • Git worktrees (with any uncommitted work)
  • Session state file (branch/CLI assignments)

Run git paw later to recover the session with the same setup.

Purging a session

git paw purge

The nuclear option. Removes:

  • Tmux session
  • All git worktrees created by git-paw
  • Session state file

Requires confirmation. Use --force to skip:

git paw purge --force

Dry Run

Preview what git-paw will do without executing:

git paw start --dry-run

Or with flags:

git paw start --cli claude --branches feat/auth,feat/api --dry-run

This runs the detection, selection, and planning steps, then prints the session plan and exits without creating worktrees or tmux sessions.

Tmux Navigation

Once inside a git-paw tmux session:

ActionKeys
Switch paneClick with mouse, or Ctrl-b + arrow key
Resize paneDrag border with mouse, or Ctrl-b Ctrl-arrow
Detach (keep running)Ctrl-b d
Scroll upCtrl-b [ then arrow keys, q to exit
Zoom pane (fullscreen toggle)Ctrl-b z

Mouse mode is enabled by default, so clicking and dragging just works. You can disable it in your config.

One Session Per Repo

git-paw manages one session per repository. If you run git paw in a repo that already has a session, it reattaches rather than creating a second session.

To work with multiple repos simultaneously, open separate terminals and run git paw in each repo directory.

Shell Completions

git paw completions <shell> prints a completion script to stdout for your shell’s standard completion mechanism:

git paw completions bash > /etc/bash_completion.d/git-paw
git paw completions zsh > "${fpath[1]}/_git-paw"
git paw completions fish > ~/.config/fish/completions/git-paw.fish

See the CLI reference for the supported shells.

Session Lifecycle

This chapter covers what git paw start does to your worktrees on each launch (or relaunch), and how to control its behaviour when the defaults don’t fit.

Reattaching instead of forking

Before creating anything, git paw start looks up whether the current repository already has a live session (matched by repository path, not by project/session name) and reattaches to it instead of launching a fresh one. This applies uniformly whether the session is a bare launch or a supervisor-mode one ([supervisor] enabled = true in config, or --supervisor) — a supervisor-enabled repo’s bare git paw start still goes through the same guard before it ever reaches the supervisor auto-start flow.

  • Interactively, it attaches your terminal to the existing session — identical to running git paw attach.
  • Non-interactively (no TTY — a script, a cron job, an automated scanner), there is nothing to attach to. Rather than silently printing a hint and exiting successfully — which an automated caller could mistake for “a fresh session was launched” — start refuses with an actionable error naming the live session and how to attach to it.

The -N suffix (paw-<project>-2, -3, …) is reserved for genuinely distinct repositories that happen to share a project name (their directory basename); it is never applied as a result of re-running start against a repository that already has a session running.

Rebase on start

Every git paw start rebases each existing agent branch onto the repository’s default branch (whatever origin/HEAD tracks — typically main) before opening or reopening that branch’s worktree. This keeps agents starting from current main, which matters when the supervisor (you) advances main while agents are still working on their branches.

Why this is the default:

  • A supervisor session typically lands commits on main while one or more agents are running on feat/* branches. Without rebasing on start, every subsequent agent commit chains from the stale baseline, forcing a per-branch rebase at merge time.
  • The rebase is cheap when there’s nothing to do: git rebase exits zero with no rewrite when the branch is already up to date.
  • The branch ref is updated even in the resume case — git paw start on a session whose worktrees already exist on disk picks up the new main commits transparently.

Rebases that don’t happen:

  • A brand-new branch (one git-paw creates via git worktree add -b during this launch) is not rebased. It’s already at the current default-branch tip by construction.

Resolving the default branch without origin/HEAD

The rebase target is resolved by a single resolver shared by every launch path (start, add, and the rebase step itself), so start and a later resume can never disagree on which branch is “the default”. It prefers refs/remotes/origin/HEAD (whatever your remote’s default branch is — typically main); when that isn’t set (a repository with local-only commits and no remote, or a remote whose HEAD was never configured), it falls back deterministically instead of aborting:

  1. A local main branch, if one exists.
  2. Otherwise a local master branch, if one exists.
  3. Otherwise the currently checked-out branch.

This means a fresh launch and every subsequent resume resolve to the same default branch and neither aborts for a reason the other didn’t hit — push the default branch to origin and run git remote set-head origin <branch> if you’d rather pin it explicitly than rely on the fallback.

Opting out: --no-rebase

Pass --no-rebase when you want the pre-v0.6 behaviour — agent branches opened at their current SHA with no rebase attempt:

git paw start --no-rebase
git paw start --no-rebase --cli claude --branches feat/auth,feat/api
git paw start --no-rebase --supervisor   # combines with other flags

Use cases:

  • You are deliberately working against a stale baseline (e.g. an agent is reproducing a bug on a specific historical commit).
  • An external tool has pinned a pre-rebase SHA on the agent branch and rewriting history would break it.
  • You’re scripting against the pre-v0.6 launch contract and don’t want the new default behaviour.

Rebase conflicts

If the rebase hits a conflict — typically because both main and the agent branch modified the same line of the same file — git paw start:

  1. Runs git rebase --abort on the affected branch.
  2. Leaves the branch at its pre-rebase HEAD (no half-rebased state survives).
  3. Exits with an error like Error: rebase onto main failed: <git's stderr listing the conflicting files>.

When you see this error:

  1. cd into the affected agent’s worktree (by default at .git-paw/worktrees/<branch-slug>/; ../<project>-<branch-slug>/ with worktree_placement = "sibling").
  2. Inspect the conflicting files git named in the error.
  3. Either reconcile manually (e.g. git rebase main followed by resolving conflicts and git rebase --continue), or decide the change should land via a merge commit instead.
  4. Once the branch is in a clean state, rerun git paw start (with or without --no-rebase depending on what you decided).

The conflict-abort path is intentional and not configurable — leaving a branch half-rebased poisons every subsequent git operation against it, which is far worse for the agent in that pane than a clean error at launch time.

What rebase does NOT do

  • It does not git fetch or git pull for main. The rebase target is whatever origin/HEAD tracks locally; the user owns the remote-sync decision.
  • It does not leave a surviving worktree’s files behind its branch. The rebase runs inside the worktree that has the branch checked out, so on resume that worktree’s files move with the rebased HEAD. If git refuses the rebase there (for example, the worktree has uncommitted changes), it fails like a conflict — see above.
  • It does not rebase one agent branch onto another agent branch. Only feat/<change> onto the repository’s default branch.
  • It does not loop or retry on failure. One attempt, abort on conflict, surface the error.

Resume vs. fresh launch

The rebase step runs in both flows:

  • Fresh launch: branch exists locally, no worktree yet. Branch is rebased onto main, then the worktree is created at the rebased SHA.
  • Resume (worktree already on disk from a prior session): branch is rebased in the existing worktree, the existence check confirms the worktree is registered for that branch, and git paw start returns success with the worktree’s branch ref now pointing at the rebased HEAD.

If you’re debugging a launch and want to know whether the rebase modified anything, git rev-parse <branch> from the main repo before and after git paw start will show the SHA change (or lack thereof).

The broker host

When the broker is enabled, every launch path — git paw start, the spec-driven launch, the supervised launch, cold recovery and resume from pause — runs the broker in its own process, git paw __broker, in a detached tmux window named broker. The window sits outside the pane grid, so pane numbering is unchanged; switch to it to see the host’s output.

A launch opens the session, starts the host, and waits up to 10 seconds for it to answer before it registers the agents with it, saves the session, or sends any boot prompt. If the host exits or never answers — most often because another process holds the broker port — the launch fails with an error naming the port and saves no session, so nothing half-launched is recorded. The dashboard in pane 0 is a client of the host: it shows ⚠ broker unreachable while the host is down and reconnects on its own.

git paw pause closes the broker window (the dashboard stays); git paw stop ends the whole session, host included. Both flush the session’s broker log on the way out.

Cold recovery resumes worker context

When git paw start recovers a stopped session — including a paused session whose tmux server died, and a session still up in tmux whose every agent pane has dropped back to a bare shell (crashed CLIs) — a CLI that supports it is relaunched with its prior conversation context restored instead of starting cold. See Per-CLI cold resume for the mechanism (session-id assignment at launch, resume on recovery) and which CLIs it covers today.

A session whose panes dropped back to a bare shell reports 🟡 stopped with Tmux: running, never 🔴 stale — its tmux session is still there, so git paw purge --stale leaves it alone and start is the remedy.

A receipt that still claims active after its tmux session vanished (a crash, or a machine sleep that tore the tmux server down) is not recovered: git paw status reports it as 🔴 stale, and start clears it the way git paw purge --stale does, then launches fresh.

Adding and removing branches mid-session

Before v0.6.0 the branch set was frozen at git paw start time: changing it meant stop/purge + re-start, which destroyed every agent’s in-flight conversation. git paw add and git paw remove edit a running supervisor session’s branch set in place — the other agents keep working undisturbed.

git paw add — hot-attach an agent

git paw add feat/new-thing              # attach a worktree + pane, session's default CLI
git paw add feat/api --cli codex        # choose the CLI for the new pane
git paw add --from-spec add-export      # derive branch + CLI from a discovered spec

What it does:

  1. Resolves the branch and CLI — from the positional argument, or (with --from-spec) from a spec discovered in the configured [specs] format — OpenSpec, Markdown, Spec Kit, or Superpowers (same resolution as git paw start --specs NAME). An unknown spec name errors with the discovered candidate list; an unknown --cli errors with the detected CLI ids — and in both cases nothing is created.
  2. Refuses — before touching anything — a branch whose broker agent_id is already taken by an agent in the session. The id is the slugified branch name, so two different branches can map to one id: adding feat-x to a session already running feat/x fails naming both branches and the shared id feat-x, because the two agents would share one inbox. Rename the branch so the ids differ. (Only when the broker is enabled; with it off, nothing addresses agents by id.)
  3. Enforces the 25-agent cap before touching anything. Adding the 26th agent fails with the same “split into multiple sessions” message start uses.
  4. Creates the worktree (same naming and idempotent-create behaviour as start; re-adding an existing worktree reuses it) and attaches the agent through the same boot pipeline a start-time agent uses — the broker boot block, the sidecar spec body (.git-paw/AGENTS.local.md), and the initial prompt are byte-identical.
  5. Re-tiles the agent grid to the layout a start of that many agents would have produced. Existing panes keep their indices, so any in-flight send-keys targeting (supervisor sweeps) still lands on the right pane; the new pane gets the next index. The re-tile preserves every other agent’s pane — no existing agent loses its pane — and each agent row is rebalanced to equal width, so the added grid matches a start-time grid of the same count in both pane count and pane widths. After the re-tile, git-paw reconciles the session JSON against the live panes and warns if any agent has no live pane (a JSON↔tmux desync), so it is visible and recoverable rather than silent.
  6. Registers the branch in the session so subsequent status, stop, purge, and pause include it.

The new agent’s boot block is injected only once its CLI reaches an interactive ready state (the launch-readiness gate), not after a blind fixed sleep — the same protection git paw start applies.

The supervisor is not signalled directly. The new agent auto-registers with the broker (filesystem watcher + its own boot-block heartbeat) and the supervisor discovers it on its next sweep — no restart, at most one sweep-interval of latency.

Paused sessions: if the session is paused when you add, the new pane is created but its prompt is held unsubmitted — the agent stays paused with the rest of the session and begins on the next git paw start.

git paw remove — detach a single agent

git paw remove feat/done-thing          # close pane, remove worktree, drop from session
git paw remove feat/wip --force         # remove even with uncommitted changes
git paw remove feat/keep --keep-worktree  # detach pane only; leave worktree + branch on disk

What it does:

  1. Locates the agent by branch (errors with the live-agent list if it isn’t in the session).

  2. Uncommitted-work safety: refuses to delete a worktree with uncommitted changes, listing the changed files, unless you pass --force. This mirrors git worktree remove’s own safety. --keep-worktree skips the check entirely (nothing is deleted).

  3. Kills the agent’s pane and re-tiles the grid for the smaller agent count so it re-flows without leaving a hole. The target pane is resolved by mapping the removed branch’s worktree to a live pane via pane_current_path and killed by its tmux pane id — regardless of the process running in it (a bare shell from a failed/never-started CLI, a CLI, or anything else). Killing by resolved pane id (not by a position computed from the session JSON) guarantees only the removed agent’s pane is closed and never a different agent’s pane, even if a stale orphan pane has shifted the grid. The re-tile preserves every other agent’s pane and rebalances each row to equal width. The branch→pane mapping for the survivors is re-derived from pane_current_path on the supervisor’s next sweep, so a mid-grid removal is safe.

  4. Removes the worktree (reusing git paw purge’s per-worktree teardown) unless --keep-worktree, then drops the branch from the session.

  5. Branch safety: deletes the branch only when git-paw created it and its tip is an ancestor of the default branch or of the branch it was created from, printing Deleted branch <name> (was <sha>). A branch that existed before the session is never deleted. A created branch carrying commits in neither — or one whose merge state cannot be determined — is kept after its worktree is gone, and is reported with its tip and how to pick the work back up or throw it away:

    Kept branch feat/wip at 9f8e7d6 (3 commit(s) not in main)
      resume it with: git worktree add '/path/to/worktree' feat/wip
      discard it with: git branch -D feat/wip
    

    The discard command is git branch -d when git would accept the safe delete, -D when only the forced form would work.

    --force relaxes the uncommitted-work check only; it never deletes a branch with unmerged commits.

git paw remove supervisor is refused — to end the whole session use git paw stop (or git paw purge). The supervisor notices a removed agent passively: its heartbeat stops, and the supervisor drops it from its coordination scope on the next /status poll.

Scope: add / remove operate on every live session — bare, spec-driven and supervisor-mode alike. Every session git-paw builds uses the agent grid, which is the layout that can be re-tiled incrementally for a new agent count, so dynamic branch attachment no longer depends on the session’s mode. One branch per invocation; bulk add/remove and configurable layouts are tracked for a later release.

One exception — a session started by an earlier git-paw. Bare sessions used to be laid out with tmux’s whole-window tiled layout, which cannot be re-tiled while the session runs (re-tiling it would move every running pane under you). add and remove refuse such a session before touching anything, with an error naming the tiled layout. The fix is one restart:

git paw stop     # or detach and let the session go stale
git paw start    # rebuilds the window in the agent grid layout

Recovery rebuilds the window as a grid and records it, so after that restart add and remove work on the session normally. Sessions started by this version are already grids and are never refused.

Worktree Placement

Every agent git-paw launches works in its own git worktree — an independent checkout of a branch that shares the repository’s object store. worktree_placement controls where git-paw creates that directory.

Child vs sibling

There are two layouts:

PlacementWorktree pathWhen it applies
child<repo>/.git-paw/worktrees/<branch-slug>/New repos (git paw init writes it)
sibling../<project>-<branch-slug>/The default when worktree_placement is absent

Sibling is the original (v0.7.0) layout: worktrees are created in the repository’s parent directory, beside the repo. Three agents on my-project produce ../my-project-feat-a/, ../my-project-feat-b/, and ../my-project-fix-c/ — scattered across the parent folder.

Child keeps worktrees inside the repository, under .git-paw/worktrees/. The same three agents produce .git-paw/worktrees/feat-a/, .git-paw/worktrees/feat-b/, and .git-paw/worktrees/fix-c/. This matches how tools like Claude Code keep worktrees within the project tree.

The child-layout slug comes from the branch name alone — / becomes - and characters outside [A-Za-z0-9._-] are stripped — so feat/auth-flow → .git-paw/worktrees/feat-auth-flow/ and fix/issue#42 → .git-paw/worktrees/fix-issue42/. The project name is not prepended because the directory already lives under that project’s .git-paw/worktrees/.

Why the contained layout

The motivation for child is a project-scoped permission model. An agent CLI (or an MCP client, or a sandbox profile) scoped to the repository can read and write .git-paw/worktrees/ with a single grant. A sibling worktree lives outside the repository the tool is scoped to, so it either falls outside the grant or forces a broader, less precise one. Containing the worktrees lets one grant for .git-paw/worktrees/ cover every agent.

Because child worktrees live inside the repo tree, they must be ignored so git never tries to stage them. git paw init adds .git-paw/worktrees/ to .gitignore for you. If you switch an existing repo to child by editing the config manually, add that entry yourself. Git itself handles the nested .git correctly — a linked worktree carries its own .git file pointing back at the main repository.

Placement decides what an agent CLI can find

The same containment affects what an agent’s CLI can read, not just what a grant can cover. A worktree is populated from a commit, so anything the repository gitignores — an agent CLI’s command and skill definitions at the repository root, .claude/ among them — is simply absent from it. A sibling worktree lives outside the repository, so those uncommitted definitions are invisible: an agent told to run a workflow command finds nothing and quietly improvises one instead. A child worktree sits under the repository root, so the CLI’s upward search through ancestor directories still reaches them.

Committing the definitions fixes it under either layout. git paw doctor reports which applies: its spec-workflow check warns when the command a spec-driven launch instructs cannot be reached from where your worktrees are placed.

Existing sessions are never moved

Placement only governs new worktree creation. git-paw records the concrete, absolute path of each worktree in the session it created, and resume, status, and purge all operate on that recorded path — they never re-derive it from the current worktree_placement. The consequences:

  • A session created under sibling keeps resuming and purging from its sibling path even if you later flip the config to child.
  • A session created under child keeps using its .git-paw/worktrees/... path.
  • Flipping the config mid-project leaves a mixed layout (old worktrees stay put, new ones follow the new setting). Each worktree is still torn down correctly because the recorded path is the single source of truth at teardown time. git-paw does not migrate existing worktrees.

This makes the upgrade to v0.8.0 a no-op for existing repositories: with no worktree_placement field, the effective placement is sibling, byte-for-byte identical to v0.7.0. Re-running git paw init backfills the field — see below.

Setting it

Set it per-repo in .git-paw/config.toml or globally in ~/.config/git-paw/config.toml (repo wins on conflict):

worktree_placement = "child"   # or "sibling"

See the Configuration reference for the full field description and merging rules.

git paw init backfills the field

Re-running git paw init in an already-initialised repo backfills every missing top-level default key, worktree_placement = "child" among them. If the repo already has worktrees under the sibling layout, init prints a warning naming them, states that it did not move them, and tells you how to keep the old layout. Nothing is moved, deleted, or re-registered — the choice is yours:

  • Set worktree_placement = "sibling" to pin the previous layout. Init never touches an explicitly-set value, so the pin holds on every later run.
  • Or leave the backfilled "child": existing worktrees keep resuming from their recorded sibling paths, and only new ones land under .git-paw/worktrees/.

Runtime Isolation

git-paw isolates each agent’s source: every agent gets its own git worktree, so nobody stashes, nobody switches branches, and nobody overwrites a peer’s edits.

It does not, by default, isolate each agent’s runtime. Sibling worktrees read the same .env and bind the same ports, so the moment two agents start a dev server, a preview, or a debugger, the second one fails with EADDRINUSE — or worse, quietly talks to the first one’s database.

Runtime isolation fixes the three collisions that matter: the shared env file, the shared port, and — through a hook you write — the shared database. All are opt-in, configured in .git-paw/config.toml:

[worktree.env]
copy = [".env", ".env.development"]

[worktree.ports]
base   = 3000
stride = 10
vars   = ["PORT", "VITE_PORT"]

[worktree.hooks]
on_create = "scripts/paw-db-branch.sh {worktree_id}"
on_remove = "scripts/paw-db-drop.sh {worktree_id}"

With no table present, nothing changes — no files are copied, no .env.local is generated, no command runs, and worktrees are created exactly as they were before. git-paw assumes no filename, no port, and no stack; you declare what your project actually needs.

Env files are copied, not linked

Each entry in [worktree.env] copy is copied verbatim from the repository root into the new worktree, after the worktree is created and before the agent’s CLI process starts. The agent’s very first read already sees a provisioned checkout.

The copy is independent — never a symlink. That is the whole point: a symlinked .env is shared mutable state across every agent, which is exactly the silent runtime corruption worktrees exist to prevent. One agent flipping FEATURE_X=true to debug its branch would flip it for everyone. A copy diverges cleanly instead, and any drift you actually care about surfaces at merge time.

A declared file that does not exist at the repository root is skipped with a warning and the worktree is still created. Per-branch divergence is normal, and a missing optional env file should not cost you a session.

Each worktree gets its own port block

Each worktree holds a runtime slot. Its port block starts at base + slot * stride, and the Nth name in vars receives base + slot * stride + N. With the configuration above:

WorktreeSlotPORTVITE_PORT
first agent030003001
second agent130103011
third agent230203021

The assignments land in a delimited managed block in the worktree’s .env.local, the override layer most stacks load last:

# >>> git-paw (managed) >>>
PORT=3010
VITE_PORT=3011
# <<< git-paw (managed) <<<

Anything you keep in .env.local outside that block survives, and regenerating the block is idempotent. The copied .env is never touched — offset ports live only in .env.local, so .env stays a faithful copy of the source you can diff against.

Keep stride at least as large as the number of vars, with headroom for vars you add later. A smaller stride would overlap adjacent worktrees’ blocks and hand two live agents the same port; git-paw clamps the stride up and warns, but it is better to get it right in config.

Ports are deterministic, not probed

git-paw does not scan the host for free ports. A slot’s block is computed from config and the slot index, nothing else.

The trade is deliberate. Probing would dodge a busy port once, but the block a worktree gets would change from run to run — so a bookmark, a proxy rule, or a teammate’s note about “the API is on 3010” would go stale every restart. Deterministic assignment means a worktree keeps its ports for as long as it exists.

The cost is that an assigned port can still be taken by something unrelated on your machine. When that happens, change base or stride — you are picking a range for the whole session, not fighting one process.

Slots survive add/remove churn

The slot is recorded in session state rather than derived from the worktree’s position in the list. Position would be fragile: remove the middle worktree of three and everything after it shifts down, so the next git paw add would be handed a still-running agent’s ports.

Instead, a new agent takes the lowest slot no live agent holds:

$ git paw add feat/one     # slot 0 → 3000
$ git paw add feat/two     # slot 1 → 3010
$ git paw add feat/three   # slot 2 → 3020
$ git paw remove feat/two  # slot 1 is freed
$ git paw add feat/four    # slot 1 → 3010, reused

Blocks stay bounded instead of climbing forever, and two concurrently active worktrees are never assigned overlapping blocks. git paw purge releases every slot along with the session.

Session state written before runtime isolation existed loads with no slot and is assigned one the next time the worktree is provisioned, so upgrading changes nothing until you opt in.

Giving each worktree its own database

Copying an env file and offsetting a port are things git-paw can do for any project. Branching a database is not — it means a Neon API call on one stack, createdb on another, a Docker volume on a third. So git-paw does not ship a driver. It runs your command at the two moments that matter, and lets that command hand its result back through .env.local.

Two scripts, one for each direction:

# scripts/paw-db-branch.sh — called with the worktree id
#!/bin/sh
set -e
createdb "paw_$1"
psql -q -d "paw_$1" -f db/schema.sql
echo "DATABASE_URL=postgres://localhost/paw_$1"
# scripts/paw-db-drop.sh — called with the same worktree id
#!/bin/sh
dropdb --if-exists "paw_$1"

Wire them up:

[worktree.hooks]
on_create = "scripts/paw-db-branch.sh {worktree_id}"
on_remove = "scripts/paw-db-drop.sh {worktree_id}"

Now git paw start --branches feat/two creates the worktree, copies the env files, allocates the ports, then runs paw-db-branch.sh feat-two inside that worktree. The one line the script printed to stdout joins the same managed block:

# >>> git-paw (managed) >>>
PORT=3010
VITE_PORT=3011
DATABASE_URL=postgres://localhost/paw_feat-two
# <<< git-paw (managed) <<<

The agent’s app reads .env.local and talks to its own database. A migration one agent runs cannot corrupt another’s fixtures.

git paw remove feat/two (or git paw purge) runs paw-db-drop.sh feat-two in the worktree before deleting it, so the database goes away with the branch.

Anything the hook prints that is not a KEY=value line is ignored, so your script can log progress freely. Only the assignments become environment.

Make teardown idempotent

Note the --if-exists in the drop script. on_remove is best-effort by design: if it fails, git-paw warns and removes the worktree anyway, because a failed teardown must never leave you with a stranded checkout. A crash or a forced purge can skip the hook entirely.

That means your teardown script will sometimes run against a resource that is already gone, and sometimes not run at all. Write it so a second run is harmless, and reconcile leftovers out of band (a periodic sweep of paw_* databases) rather than relying on the hook alone.

By contrast, a failing on_create does stop that worktree — git-paw reports the hook’s exit status and stderr rather than handing an agent a half-built runtime.

Print secrets on stdout and diagnostics on stderr. git-paw logs only the names of the variables it merged, so a password inside DATABASE_URL never reaches a log line — but a failing hook’s stderr is shown. And since the value lands in a plain .env.local, make sure that file is gitignored.

A hook is an arbitrary command from your config file, run with your privileges — the same trust model as a custom CLI’s command. git-paw never builds one from anything an agent or the network supplied.

Checking it worked

After starting a session, look inside any agent’s worktree (under .git-paw/worktrees/ by default — see Worktree Placement):

$ cat .git-paw/worktrees/feat-two/.env.local
# >>> git-paw (managed) >>>
PORT=3010
VITE_PORT=3011
# <<< git-paw (managed) <<<

If .env.local is missing, [worktree.ports] is absent or its vars list is empty and no hook printed an assignment. If a declared env file is missing from the worktree, check the warning git paw start printed — the file probably does not exist at the repository root. If a hook key is missing, check that the script prints it on stdout in exact KEY=value form.

See also

  • Configuration — the full [worktree.env] / [worktree.ports] / [worktree.hooks] key reference
  • Worktree Placement — where the worktrees themselves are created

Security Posture

This is the single, authoritative statement of git-paw’s trust model. Read it before trusting git-paw with autonomous execution. It is deliberately blunt about what the tool does and does not protect.

What git-paw runs

git-paw launches AI coding agents — arbitrary programs — in tmux panes on your machine, each in its own git worktree. Under supervisor/unattended mode (and whenever you point an agent CLI at a skip-permissions or full-auto flag), those agents execute arbitrary code with your user’s privileges: they run your build, your tests, shell commands, and anything else the model decides to run, often without pausing for per-command approval.

The blast radius is your user account. An agent — whether malicious, prompt- injected, or simply mistaken — can by default read and write anything your user can: your whole home directory, your SSH keys, your cloud credentials, your other repositories, and your login keychain. git-paw does not change your OS’s permission model; it automates running code within it.

git-paw decides nothing about your agent’s commands

git-paw makes no safety judgement about a command an agent asks to run, and it approves no permission prompt on its own. It has no safe-command list, no danger list, and no path-based safety rule. There is no command classifier.

Whether a command runs without a prompt is decided entirely by the agent CLI’s own permission system — the permission mode git-paw launches it with, plus whatever allow and deny rules you have configured in that CLI. Anything the CLI decides to ask about is a prompt, and every prompt is answered by a human.

What git-paw does is narrower, and it is the part it can do honestly: it detects that a pane is sitting on a live permission prompt and gets that prompt in front of you — on the broker, in the exit summary and in the manual-decision log — without stalling the rest of the wave. Under git paw start --unattended every live prompt is escalated this way. Approval keystrokes reach a pane only when a human explicitly runs sweep.sh approve <pane>, which answers once with option 1 and refuses an option 1 that would grant a durable “don’t ask again” permission.

No LLM process runs at the repository root inside a session. A supervised session holds only the dashboard and the coding agents, each confined to its own worktree; the orchestrator runs in your own agent session, under the permission mode you chose for it, and git-paw launches it with no flags. Every git-paw keystroke sender — sweep.sh approve, and the drive loop’s nudges, stale-input recovery and buffered-input submits — follows one role-based rule: keystrokes reach only a pane resolved as a coding agent (by the session’s discovery roster for the helper, by the recorded layout and the pane’s owning worktree for the loop). The dashboard and any pane no agent worktree owns are refused, whatever their index.

Earlier versions did decide. Up to and including v0.16.0, git-paw ran a command classifier that answered prompts it judged safe, often choosing the “Yes, and don’t ask again” option. Your agent CLI persisted those choices as durable permission grants in its own settings file — for Claude Code that is the repository-root .claude/settings.local.json, which every worktree and every one of your own sessions shares. Retiring the classifier stops new grants being created; it removes none of the existing ones. Review that file (and any user-level equivalent) and delete grants you did not intend. git-paw never edits or deletes it.

What the controls do — and do not — guarantee

git-paw’s controls are the ones below. None is a complete sandbox on its own; each narrows one specific risk. Do not read any of them as “the agent is now safe.”

ControlWhat it doesWhat it does not do
The agent CLI’s permission systemThe [supervisor] agent_approval level maps to each coding agent’s own launch flags (manual, auto, full-auto), and the CLI then applies its own allow and deny rules to decide what runs without asking. This is the only thing deciding whether a command needs approval.It is the CLI’s guarantee, not git-paw’s, and it is only as narrow as the rules you wrote: an allow rule for a command prefix permits everything that prefix permits. A full-auto level skips the prompts entirely, so no rule is consulted and nothing is escalated. Path-scope your rules; never grant curl * / cd *.
FS-scoped sandbox (optional, you configure it)When you wrap an agent CLI in sandbox-exec/bwrap, the kernel confines the agent’s writes to its worktree — rm -rf ~, dotfile corruption, and planted git hooks are blocked at the syscall level. This is the only control here that is kernel-enforced.It protects the machine around the work area, not the work area (the worktree, including uncommitted work, stays destroyable by design). The default profile does not restrict reads, so it protects write-integrity and not the confidentiality of host secrets unless you add read-hardening. It does not cover network, resource-exhaustion, or exposed-socket vectors unless you add rules. See Sandbox.
Broker Host/Origin guardRefuses browser-shaped requests (a non-loopback or null Origin) and, on a loopback bind, requests whose Host is not loopback — closing the DNS-rebinding path into the unauthenticated coordination bus. See Browser exposure.It is not authentication: any local process can still reach the broker, read the whole log, and publish any message. A non-loopback [broker] bind disables the Host half entirely.
Remote control (documented workflow)Lets you drive a session from elsewhere by remote-controlling your own driving agent session through its CLI’s native remote control.Exposing a session to remote control extends the blast radius to whoever reaches that channel; secure the channel yourself.

Two things that used to appear in this table are gone, and should not be relied on: git-paw’s auto-approve classifier and its command allowlist ([supervisor.auto_approve], [supervisor.common_dev_allowlist]) are retired — both tables still parse so old configs load, but they have no effect and warn. The protected-paths and .git/-write rules existed only to stop that classifier approving such a write; with nothing approving automatically, those prompts reach you like any other. To enforce those boundaries mechanically, use deny rules in the agent CLI’s own settings, or the sandbox.

Writing into an agent CLI’s settings

git-paw writes a permission rule into an agent CLI’s own settings in exactly one circumstance: you ran git paw setup-cli. Running it is the consent. No other command, process or interface does it — not start, add, init, session recovery, the drive loop, the dashboard, or any tool exposed over MCP.

What it may write. For claude, one file: <main checkout>/.claude/settings.local.json, plus one anchored line in the repository’s shared info/exclude when the target is not already ignored. Never a file inside a worktree, never a user-level file, never the configured [clis.<name>].settings_path.

The grant. Twelve allow entries naming git-paw’s own helper scripts by relative path and fixed subcommand, from a compiled table. Nothing is derived from a command argument, your configuration or your repository content. No entry names curl, an interpreter, an absolute path or a directory wildcard. The keystroke-sending and verdict-publishing subcommands are deliberately excluded, and sweep.sh approve is pinned to an ask rule so it always reaches a human. The twelfth entry, sweep.sh events, is a pure read of the broker’s log and status — no keystroke, no verdict. See the full table in Per-CLI Setup.

An applied grant is never rewritten for you. When a release adds an entry to the table, a file applied under an earlier version keeps the entries that version wrote; git-paw changes nothing behind your back. git paw doctor reports the applied setup as out of date, and the remedy is explicit and yours: git paw setup-cli claude --undo, then git paw setup-cli claude --allowlist. Until then the new subcommand prompts like any ungranted command.

The tamper threat, and the mitigation. A granted helper runs without a prompt, and it lives inside the agent’s own worktree. In an edit-accepting mode the CLI would apply an agent’s edit to that script without asking, after which the grant runs whatever the agent wrote — arbitrary code, unprompted. A quieter variant: the helpers read .git-paw/config.toml, so an edited broker address turns a granted publish into exfiltration.

So the allowlist item never writes the grants alone. In the same file and the same replacement it writes Edit(**/.git-paw/scripts/**) to deny and Edit(**/.git-paw/config.toml) to ask. Both were confirmed against the installed CLI before the item shipped — including that the deny rule also blocks the filesystem commands the edit-accepting mode auto-approves (sed -i, cp, mv, rm, touch, and cd-prefixed forms), and that it covers both the Edit and the Write tool, at any depth, from a worker’s directory and from a root session reaching into a worker’s copy.

The gate. An item is written only when its facts have been verified against an installed copy of that CLI. An unverified item can be planned with --dry-run but never written. The claude allowlist item ships verified against Claude Code 2.1.220 (2026-10-15); git paw doctor reports the current status.

Residual risks. These remain even with the item applied:

  • An agent CLI other than the one with a fact row has no row, so nothing is written for it — and nothing is pre-approved.
  • A separately installed MCP server or other tool that writes files outside the CLI’s permission system is not governed by these rules.
  • In bypassPermissions / full-auto, the grant adds no privilege because nothing prompts — but the protection rules buy nothing either. Do not pair the allowlist with those modes outside the sandbox.
  • In the CLI’s own auto mode, its classifier decides edits.
  • broker.sh --agent <other> can publish under another agent’s id. That is pre-existing; broker-side identity is a later release. Verdict publishing is deliberately not granted.
  • A write by the CLI itself (a “don’t ask again” approval) landing between git-paw’s re-read and its rename would be lost. git-paw detects the change and stops rather than clobbering it, but prefer to run setup when no session is waiting on an approval.

A grant is not a safety judgement. git-paw decides nothing about your prompts. Whether a call runs without one is decided entirely by the agent CLI’s own permission system and the rules in its settings. git paw doctor additionally lists the broad allow rules already in that file, so you can see what earlier “don’t ask again” answers left behind — it reports them and never edits them.

Self-approval residue

One shape of leftover grant deserves naming on its own, because it undoes the single most important distinction the grant table draws.

The allowlist item grants sweep.sh per subcommand, and withholds approve, verified, feedback-gate and conflict-ack precisely because the target settings file reaches every session in the repository — a worker’s as much as the orchestrator’s. A durable allow rule naming one of those subcommands, or a whole-script Bash(.git-paw/scripts/sweep.sh *) wildcard that covers them all, hands every agent session exactly what was withheld: it can press the approval keystroke in another pane, or publish a supervisor verdict, without a prompt. An agent can then approve its own permission prompts and sign off its own work.

Such a rule is not something git-paw writes. It arrives from a “don’t ask again” answer — including ones the retired classifier accepted on your behalf up to v0.16.0 — or from a broad rule added by hand. git paw doctor now reports it, in the project CLI grant residue check, alongside a blanket curl grant and the inert allowed_bash_prefixes seed earlier versions wrote. See Doctor.

Removing it is manual by design. git-paw never rewrites or deletes a grant it did not write: it cannot prove the rule is not one you meant to add, and a tool that edits your permission rules on its own is the thing this whole section exists to rule out. Doctor names the file and the entry; the deletion is yours.

Hook bypass and tmux driving

Two things an agent session can do with ordinary commands step around git-paw’s own safeguards, and both have been seen in practice:

  • Bypassing git-paw’s git hooks. git-paw’s hooks guard each agent’s branch and report its commits. A per-invocation override such as git -c core.hooksPath=…, or git commit --no-verify (-n), silently skips them — the branch guard and the commit report go with it.
  • Driving tmux. tmux send-keys, tmux run-shell and tmux pipe-pane reach into the session’s panes from outside the agent’s CLI. Keystrokes sent that way can answer a permission prompt — the agent’s own included — and so bypass the human and the orchestrator entirely.

The bundled coordination skill tells every agent plainly not to do either. An instruction is not a control, though, and git-paw adds no permission rule for these commands: it pre-approves only its own helper workflow (setup-cli writes rules naming git-paw’s helper scripts and nothing else), and every other permission decision belongs to your agent CLI and to you. That is the same line the v0.17.0 classifier retirement drew.

If you want these forms to prompt — or to be refused outright — add your own ask or deny rules to your agent CLI’s settings, for example ask rules for git -c …, … --no-verify and the tmux verbs above. git-paw never writes them, never edits them, and never removes them. Like any rule matched on a command’s text, they are a speed bump against the natural forms rather than a boundary; the sandbox is the boundary.

Browser exposure — the broker’s Host and Origin guard

The coordination broker is an HTTP server on your loopback interface (127.0.0.1:9119 by default) and it authenticates nothing: any client that reaches the port can read GET /log — the whole session’s coordination traffic — and POST /publish any message, including an agent.answer or agent.feedback that coding agents read as authoritative guidance. Whatever those agents are allowed to run without asking — under a permissive CLI permission mode, or full-auto — a request that reaches the broker is a prompt-injection channel into a shell.

A web page you open in a browser can reach a loopback port through DNS rebinding: the attacker’s hostname re-resolves to 127.0.0.1, so the page becomes “same-origin” with http://<attacker-host>:9119 and its requests go to your broker. To block that, the broker checks two headers on every request, before routing — so the check covers every endpoint, including the 404 and 405 fallbacks and any endpoint added in a later release:

CheckWhen it appliesWhat it accepts
HostOnly when [broker] bind is a loopback address (the default).localhost (case-insensitive), any IPv4 literal in 127.0.0.0/8, or [::1]. The port is not checked, so ssh -L-style tunnels and local port-forwards keep working. A missing Host is refused.
OriginWhenever the header is present, on any bind.An origin whose host passes the Host rule. The serialized opaque origin null (sandboxed iframes, file:// pages) is refused. A request with no Origin, which is what every non-browser client sends, is unaffected.

A refused request gets 403 with {"error":"forbidden host"} or {"error":"forbidden origin"}, is not routed, and changes no broker state.

No git-paw client is affected. The bundled broker.sh and sweep.sh helpers, the post-commit git hook, the Rust HTTP clients used by the CLI, the MCP server and the dashboard all address the broker by its configured http://<bind>:<port> URL and send no Origin.

The non-loopback exception

If you set [broker] bind to 0.0.0.0 or a LAN address, git-paw cannot enumerate the names your network uses for the machine, so the Host check is disabled. Two things follow, and the second is easy to get wrong.

First, the broker is reachable from your network by any host, with no authentication at all — a far larger exposure than the one this guard addresses.

Second, a non-loopback bind does not keep browsers out. The Origin check still blocks browser writes and cross-origin reads: browsers attach Origin to every POST, and to any cross-origin GET made in CORS mode (fetch defaults to it, as does XMLHttpRequest). A cross-origin GET made in no-cors mode sends no Origin, but the browser hands the page an opaque response it cannot read, so that is not a way out either.

A DNS-rebinding page, though, is same-origin with the broker once the attacker’s hostname re-resolves to your machine. A same-origin GET carries no Origin header at all, and its response is fully readable — so with the Host check off, fetch('/log') from that page is served and the page reads it. The same goes for /status and /messages/*. Only a loopback bind stops a rebinding page from reading the coordination log.

Every command that brings the broker up prints a warning on stderr naming the bind address: git paw start (including --from-specs), the supervisor launch, a recovery of a stopped session, and a resume from pause. The broker process prints it too, but into the dashboard pane, which switches to the alternate screen moments later. Do not expose the broker this way on a network you do not control.

This is not authentication

The guard distinguishes browsers and foreign host names from local processes. It does not authenticate anybody. Any process running on your machine can still reach the broker, publish any message, and read the whole log — including an agent that has gone off the rails, and any other software running as your user.

That includes pages: any page served from a loopback origin is accepted — http://localhost:3000, or any other port — so a local development server you are running, or a cross-site-scripting flaw in a page it serves, can reach the broker from the browser on both a loopback and a non-loopback bind. The guard excludes foreign origins, not all browser code.

If you need the broker to be unreachable by local processes, that is a containment problem; see the FS-scoped sandbox, and do not rely on this guard for it.

Migration: reverse proxies

A reverse proxy that forwards the original external Host to the broker gets 403. Configure it to rewrite Host to a loopback name (for nginx, proxy_set_header Host 127.0.0.1:9119;), or have clients address the broker directly. Tunnels and port-forwards need no change — they already present a loopback Host.

Broker verdict authority

Two broker messages carry the root session’s authority: agent.verified, which authorises a branch to merge, and agent.feedback whose from is the root identity supervisor, which the drive loop’s correction loop treats as a gate verdict. The broker accepts these only with the per-session authority credential that only the root session holds, sent in the X-Paw-Authority request header. Without it — or with a wrong value — POST /publish answers 403 {"error":"authority required"} and changes no broker state. An agent.status or agent.artifact that claims the verified status for a worker is treated as a verdict too, since the roster would otherwise mark the branch verified from it. The comparison is constant-time, and the message body (the wire envelope) is unchanged: the credential travels only in the header.

Where the credential lives. A broker-enabled git paw start mints 32 bytes of OS randomness (64 hex characters) per session and stores them in the user-data session file — under the platform data directory, outside the repository and so outside every worktree — which git-paw writes owner-only (0600). The launcher hands the same value to the broker host through a one-shot 0600 file next to it, which the host removes once read. It is never on a command line, in the environment, in a pane, or in a log, and it never appears in the per-repo discovery file, GET /status, GET /log, git paw status --json, MCP get_session_status, or the broker log. A session saved by an earlier git-paw gains a credential the first time recovery or a restart from pause starts its broker host.

Who carries it. Only root-side publishers:

  • the unattended drive loop, for its own branch-refresh notification;
  • the sweep.sh verdict verbs verified, feedback-gate and conflict-ack, which fetch it through the hidden git paw __authority subcommand — which refuses to run from a linked worktree.

A root session’s MCP server (one started in the main working tree) offers publish_verified and publish_feedback, which carry the root credential read from the same session file. A worktree session’s MCP server has no verdict tool.

A headless orchestrator one-shot is not handed the credential: it publishes a verdict only through those root-side paths — the sweep.sh verbs or a root session’s MCP verdict tools — like any root-side helper.

The worker-side broker.sh has no credential-bearing verb.

Threat model. The rule stops a mistaken or skill-misled worker — one following a stale example, or an LLM that decides its own work is verified — and a rogue worker sandboxed with home-directory reads denied: the sandbox’s Tier 2 (deny $HOME), or a Tier 1 blocklist that covers git-paw’s data directory (~/Library/Application Support/git-paw on macOS, $XDG_DATA_HOME/git-paw — by default ~/.local/share/git-paw — on Linux). The default sandbox profile confines writes only, so it does not hide the credential. A worker with home-directory read access is out of this rule’s threat model, whether it runs unsandboxed or under the default profile: it can read the credential, and keeping it out is the job of the sandbox’s read-confinement tiers.

What stays ungated. Every non-verdict message is accepted without the credential, as before — including agent.status, even one that claims the supervisor identity, which can wind down the drive loop’s wave early. Feedback from any other sender (the post-commit branch-guard hook, a person) is not a verdict and stays ungated, and so does one agent publishing a non-verdict message as another. Messages the broker’s own subsystems publish in-process (the conflict detector, the role guard) never cross HTTP and are not gated.

The headless orchestrator one-shot

Under --unattended, the drive loop can hand a judgment call — an ambiguous question, a merge decision, a correction-exhausted branch — to the orchestrator CLI, run once and non-interactively at the repository root. Its permission level is [supervisor] approval (falling back to agent_approval), and nobody is present to answer its own prompts.

  • full-auto here is an unattended agent with root-level write access for up to [supervisor.escalation] timeout_seconds (default 30 minutes). It is the power the old orchestrator pane had, without its interactive surface: bounded by the timeout (which kills the whole process group), serialized to one invocation at a time, and logged to .git-paw/logs/<session>/orchestrator-escalations.log, with each outcome published on the broker. Run it inside the sandbox.
  • Under full-auto the ask rule on sweep.sh approve does not apply. git paw setup-cli puts sweep.sh approve on ask so a human sees every approval; a skip-permissions turn consults no rule. The one-shot’s framing, which forbids running sweep.sh approve or answering any permission prompt, is then the only guard — an instruction, not a control. Permission prompts never enter its context: the loop never hands one off, so the one-shot has no prompt in front of it to be talked into approving.
  • Under auto it cannot sign off work — with the built-in mapping. For claude, auto resolves to --permission-mode acceptEdits; with the setup-cli grants the verdict verbs (sweep.sh verified, feedback-gate, conflict-ack) are never granted, so a non-interactive turn is denied them and a merge decision stays on the broker for you. This does not hold for a custom CLI or wrapper that pins its own permission mode: its one-shot can do whatever that mode allows.
  • Prompt injection from worker-written text is the residual risk. A question, artifact status or branch detail is text a coding agent wrote, and the context quotes it. It is fenced between markers as data, and the framing forbids acting on instructions inside it or verifying, merging or publishing a verdict because it asks — but that is an instruction to a model, not a control. Under full-auto, or a CLI wrapper with a permissive mode, a one-shot runs at the repository root where git paw __authority hands it the credential, so injected text could steer it into a self-verify and a merge. Run full-auto inside the sandbox.
  • One pending question per agent. A worker publishing many distinct questions queues one one-shot at a time; the rest stay on the broker and are counted in the exit summary, so it cannot buy an LLM run per question.
  • No secret reaches it. The escalation context goes on stdin, never on argv (nothing in ps), and the environment adds only GIT_PAW_BROKER_URL; the authority credential is never on its command line or in its environment.
  • Interrupting git-paw does not stop it. The one-shot runs in its own process group, so Ctrl-C or a closed terminal reaches git-paw but not the one-shot, and its timeout is enforced by the drive loop, which is then gone. The escalation log records each one-shot’s pid, which is also its process group id; kill that group by hand. A session torn down under a running loop kills it.
  • Its background processes outlive a normal exit. The whole group is killed on timeout or teardown, but when the one-shot exits on its own, any process it left running in the background keeps running.

Set [supervisor.escalation] enabled = false to keep every judgment call on the broker for a human.

MCP repository writes

The MCP server’s spec-authoring tools (create_spec, write_spec_artifact) are its only tools that write file content into the repository. MCP input is untrusted, because the orchestrator calling them may have been prompt-injected by content it read. These are the guarantees:

  • Off by default, and only the repository can consent. The tools exist only when the repository’s own .git-paw/config.toml sets [mcp] allow_repo_writes = true. The user-level config, environment variables and tool arguments cannot enable them, and no tool has a parameter that grants consent. The server reads the switch once at startup and logs whether writes are enabled.
  • Root session only. A server in an agent’s worktree never lists or runs them, whatever that worktree’s config says.
  • Confined to the change directory. Lexical checks refuse an absolute path, a .. or .git component, and a change that is not one kebab-case segment. Canonical checks refuse a spec directory outside the repository, inside .git, at the repository root or inside .git-paw, a symlinked directory leading out of the change directory, and a symlinked target file. The spec directory comes from the repository’s own [specs] section only, never the user-level config. So writes land only inside change folders under the repository-configured spec directory, and never in .git or .git-paw. Where that directory points is the repository’s choice: set to .github or src, its “change folders” are CI or source directories. Content is written to a temporary file and renamed into place.
  • No process, no commit. No tool spawns a process (validate_spec checks in-process), and nothing is staged or committed. git status / git diff and one server-log line per write record what was authored.

The limit. The switch is a file in the repository, so anything that can edit the root’s .git-paw/config.toml can turn it on. That includes an unsandboxed worker writing outside its worktree, or a merged branch that carries the key. The change shows up in the diff a human reviews and in the server’s startup log line, and the sandbox confines a worker’s writes to its worktree. Against an unsandboxed rogue worker, though, the switch is no defence, as with broker verdict authority. Likewise, a local process that can already write inside the change directory could swap a directory that was just checked for a symbolic link before the write lands. That is out of scope for the same reason: it already has write access.

The bottom line

  • Treat every agent as capable of running arbitrary code as you.
  • git-paw decides nothing about an agent’s commands and approves no prompt. What runs without asking is your agent CLI’s decision, from the rules you gave it.
  • A CLI allow rule reduces friction; it is not containment. A full-auto level removes the prompts altogether.
  • For containment, use the optional FS-scoped sandbox — and understand precisely what it does (writes) and does not (reads, by default) protect.
  • If you ran v0.16.0 or earlier, audit your agent CLI’s settings file for durable “don’t ask again” grants the retired classifier accepted on your behalf. git paw doctor names the known-dangerous ones — above all a sweep.sh rule that lets an agent approve its own prompts — but removing them is yours to do.
  • Run untrusted or aggressive automation on hardware, accounts, and repositories whose compromise you could tolerate.

git paw doctor prints a pointer to this page and reports whether your OS’s sandbox backend is available.

Remote Control

You can drive a git-paw session from a phone or another device. The orchestrator is your own agent session, so if that session can be remote-controlled, the whole run can be steered from anywhere. This chapter covers how.

The topology

phone / browser ──(the agent CLI's own remote control)──▶ your driving agent session
                                                            (local machine, repo root,
                                                             git-paw MCP server attached)
                                                                    │
                                                                    ▼
                                                     git-paw session (tmux: dashboard
                                                     at pane 0, coding agents from pane 1)
  • The driving session runs on your local machine, at the repository root and outside tmux. git-paw’s MCP server is attached to it, and it has loaded the orchestrator skill (.git-paw/skills/orchestrator/SKILL.md) and the session context (.git-paw/AGENTS.local.md).
  • You remote-control that session through the agent CLI’s native remote-control feature. Your phone is only a view into the local session. The session’s files, MCP servers and tools stay on the machine.
  • The driving session drives git-paw the way it does when you sit at the keyboard. It uses the bundled helpers (.git-paw/scripts/sweep.sh, broker.sh) and the MCP tools: wait_for_events and get_session_status to follow the run, and, in the root session, start_session, pause_session, stop_session, purge_session, publish_verified and publish_feedback.

git-paw ships no bridge, tunnel or network transport of its own. The remote channel belongs to the agent CLI vendor, and git-paw opens no port for it. Pair the driving session with git paw start --unattended so the coding agents keep moving while you are away. Each permission prompt the drive loop escalates reaches you through the driving session, and you answer it from the phone.

Which agent CLIs can be the remote-controlled driving session

Last checked: 2026-10-29. These are third-party facts that change quickly; check the vendor’s current documentation before relying on a row.

Agent CLIRemote-controlled driving sessionNotes
Claude Code✅ claude remote-control, or /remote-control in a running sessionContinues the local session from the Claude app or the web. Local MCP servers stay available. Requires a paid plan and /login.
GitHub Copilot CLI✅ copilot --remote, or /remote on in a running sessionSteered from GitHub Mobile or github.com. Whether local MCP tools are usable remotely is to verify; the shell-helper path works regardless.
OpenAI Codex✅ through the Codex desktop app, steered from the ChatGPT mobile app (preview)The codex terminal CLI has no remote control of its own; drive git-paw from the desktop app. MCP over the remote channel is to verify.
Cursor✅ /remote-control in a desktop agent session, steered from the Cursor iOS app (public beta)Requires a paid plan and a workspace with a Git remote. The agent loop runs in Cursor’s cloud and tool calls run on your machine. MCP over the remote channel is to verify.
Qwen Code⚠️ to verifyEarlier research (2026-07) found text-only messaging integrations, not a native remote control.
Antigravity agy❌ to verifyNo native remote control was found (2026-07).
Amp / Aider❌ to verifyNo native remote control was found (2026-07).

A CLI without native remote control can still drive git-paw locally. git-paw does not build a bridge to make it remote-controllable.

Caveats

  • The local process must stay running. The remote control is a view into a process on your machine. If the machine sleeps, loses its network or the process exits, the remote session stops with it. An outage of roughly ten minutes ends the remote session (Claude Code’s documented timeout; other vendors behave similarly), and you have to restart it locally. Keep the machine awake for long runs.
  • Secure the channel yourself. Remote control extends the blast radius to whoever can reach that channel. Treat your vendor account and its sessions as access to your repository. See Security posture.
  • Remote approval is still your approval. The orchestrator relays every escalated permission prompt to you, and runs sweep.sh approve only when you say so, whether you are at the keyboard or on your phone.

See also

Per-CLI Setup

git paw setup-cli is the one command that writes into an agent CLI’s own settings file. Running it is the consent.

Agents call git-paw’s bundled helper scripts constantly — to publish status, poll for messages, capture panes. Under most agent CLIs every one of those calls raises a permission prompt. setup-cli lets you pre-approve exactly those calls, in the CLI’s own allow rules, once.

Nothing else in git-paw writes an agent CLI’s settings. Not start, not add, not init, not session recovery, not the drive loop, not the dashboard, and no tool exposed over MCP. Earlier versions of git-paw tried to seed permissions automatically; that was removed, and it is not coming back under a different name.

Everything the command does is therefore gated:

  • it prints its plan before anything is written;
  • it writes nothing without --yes or your confirmation on a terminal;
  • it records every entry, so it can take back exactly what it added;
  • it refuses to run from a linked worktree, so a coding agent in a worktree cannot run it by accident.

What it writes, and where

For claude, the allowlist item targets exactly one file:

<main checkout>/.claude/settings.local.json

That one file is enough. Claude Code reads the settings file at the repository root even when the session starts in a subdirectory or in a linked worktree — which is what the change’s fact gate confirmed against the installed CLI. So a single write covers your root session, every agent worktree nested under .git-paw/worktrees/, and every session you start later, including worktrees that do not exist yet.

git-paw never writes a settings file inside a worktree (that would put the file inside the agent’s own reach), never writes a user-level settings file, and never writes the path you configured as [clis.<name>].settings_path.

If the file is not already ignored by git, the plan also adds one anchored line to the repository’s shared info/exclude, so a file git-paw created does not show up in your git status.

The grant table

The allow entries are a fixed table. Nothing is derived from a command argument, from your configuration, or from anything in your repository — two differently configured repositories plan byte-identical entries.

HelperGrantedDeliberately not granted
broker.shevery subcommand—
docs-fetch.shfind onlyget
sweep.shsnapshot, capture, status, worktrees-status, inbox, detect-stuck, stuck-eval, status-publish, learn, eventsapprove, verified, feedback-gate, conflict-ack, answer, advanced-main, classify
_paw_common.sh— (it is sourced, never invoked)—

That is twelve allow entries in total.

Each entry names the helper by its relative invocation path, like Bash(.git-paw/scripts/broker.sh *). No entry names an interpreter, an absolute path, a directory wildcard or curl.

sweep.sh events is granted — as Bash(.git-paw/scripts/sweep.sh events *) — because it only reads the broker’s log and status to serve the event long poll. It sends no keystroke and publishes no verdict.

What is withheld, and why:

  • docs-fetch.sh get takes an absolute http(s):// or file:// URL. It is a read-anywhere and exfiltration primitive; find only fetches the one configured index.
  • sweep.sh approve presses keys in another pane. It also gets an explicit ask rule, so it prompts even if some broader rule of your own would otherwise allow it.
  • sweep.sh verified and feedback-gate publish supervisor verdicts, and conflict-ack writes feedback into other agents’ inboxes. The target file reaches worker sessions as well as the supervisor’s, so granting these would let a worker forge its own verification or tell a peer a conflict was cleared.
  • sweep.sh answer and advanced-main publish with supervisor authority: an answer is authoritative guidance to another agent, and a main-advance event makes the drive loop rebase worker branches. Granting either would let a worker forge one without a prompt.
  • sweep.sh classify is on its way out with the command classifier. It is listed as withheld so the guard that requires every subcommand to be explicitly granted or explicitly withheld keeps passing while it still exists.

The protection rules

A granted helper runs without a prompt, and it lives inside the agent’s own worktree. Without protection, an agent could edit the script and have the grant run whatever it wrote. So the allowlist item never writes the grants alone — it writes these in the same file and the same replacement:

RuleWhereWhy
Edit(**/.git-paw/scripts/**)denyrefuses the CLI’s file-editing tools on any helper directory, at any depth
Edit(**/.git-paw/config.toml)askthe helpers read this file (the broker address), so a change to it always prompts

The fact gate confirmed both against the installed CLI, including that the deny rule also blocks the filesystem commands acceptEdits auto-approves (sed -i, cp, mv, rm, touch, and cd-prefixed forms). git paw doctor additionally warns when a granted helper no longer matches the copy the binary bundles.

Using it

# See exactly what would be written. Writes nothing.
git paw setup-cli claude --allowlist --dry-run

# Apply it, with a confirmation on a terminal.
git paw setup-cli claude --allowlist

# Apply it without asking (scripts, CI).
git paw setup-cli claude --allowlist --yes

# Take back exactly what git-paw added.
git paw setup-cli claude --undo --yes

Run it from the repository’s main checkout. At least one of --allowlist, --trust, --hooks or --undo is required.

--trust and --hooks exist so the flag surface will not change when later releases add those facts. Today no CLI declares them, so they report “not supported” and write nothing.

Re-running after an upgrade

git-paw never rewrites recorded grants itself. When a release adds an entry to the grant table — sweep.sh events is the v0.18 example — a file you applied under an earlier version keeps exactly the entries that version wrote. Nothing updates it in the background.

git paw doctor notices and reports the applied setup as out of date, naming how many entries this version would also write. It judges that against your CLI’s settings file rather than against git-paw’s record, so an entry you already held yourself counts as applied — see the per-CLI setup checks. The remedy is to take back what git-paw wrote and apply the current table:

git paw setup-cli claude --undo
git paw setup-cli claude --allowlist

Until you do, the new subcommand simply prompts the way any ungranted command does — nothing breaks, you just answer a prompt.

Undo

--undo removes an entry only when it is still present, still byte-equal to what was recorded, and still of a shape the item produces. Anything else — an entry you edited, an entry someone added to the record by hand — is reported and dropped from the record, never removed from your file. The protection rules stay as long as any allow entry still names a path under .git-paw/scripts/, including one you added yourself.

Undo removes entries, not containers: an allow/ask/deny list that setup created and undo emptied is left in place, empty. An empty list grants nothing, and removing it would mean touching something git-paw cannot prove it created.

A file git-paw created is deleted only when nothing but its own skeleton is left; if you added a key of your own, the file stays.

Upgrading from 0.17.0 or earlier

If you applied the allowlist on an earlier release and git paw doctor told you the applied allowlist was out of date — straight after applying it, with an --undo and re-apply remedy that never cleared the warning — that is fixed. Setup skips a planned entry your settings file already holds and records only what it wrote, and doctor used to compare its plan against the record alone, so any entry you already had read as permanently missing. Doctor now judges the plan against the settings file, so an entry you already had counts as applied and the undo/re-apply round trip converges. Nothing about what setup writes, what it records, or what undo removes has changed.

Doctor also reports grant residue now: durable rules left in the project’s CLI settings by earlier versions or by earlier “don’t ask again” answers — a blanket curl grant, a sweep.sh rule covering a subcommand the table above withholds, or the inert allowed_bash_prefixes seed written before v0.16.0. git-paw does not rewrite a grant it did not write, so those must be removed by hand. See Doctor.

Verified and plan-only

Each item carries a verification status: whether its facts — the target file, the key paths, the entry forms, and the protection the entries rely on — were confirmed against an installed copy of that CLI.

  • Verified: the plan can be applied.
  • Plan-only: --dry-run still prints the plan, but writing is disabled and the command exits 2. --undo keeps working, so entries written by an earlier version can still be removed.

The claude allowlist item ships verified, against Claude Code 2.1.220 (2026-10-15). git paw doctor reports the current status.

Claude Code variants (a wrapper binary registered under another name) are covered by git paw setup-cli claude: the target settings file belongs to the repository, not to the binary.

The record

Every entry git-paw writes is recorded at:

<git-common-dir>/git-paw/cli-setup.json

It lives in the git directory, so it can never be committed, never shows up in any worktree’s git status, and is shared by every worktree of the repository. It is written owner-only, and an entry is recorded before it is written — a crash in between leaves a recorded entry that is not on disk, which undo simply drops.

Residual risks

Read these before applying the item:

  • Prefer the FS-scoped sandbox. The allowlist reduces prompts; the sandbox bounds what a process can reach at all. They are complementary, and the sandbox is the stronger control.
  • Do not pair the allowlist with full-auto outside a sandbox. In a mode where nothing prompts, the protection rules buy you nothing, because everything else is unprompted too.
  • Undo it when you no longer want it. The grant reaches every session in the repository, including ones you start much later.
  • Undo before downgrading. Entries already written stay in your file after a downgrade, and an older git-paw does not know how to remove them.
  • Run setup when no session is waiting on an approval. The CLI writes to the same file when you answer “don’t ask again”; a write that lands between git-paw’s re-read and its rename would be lost. git-paw detects the change and stops rather than clobbering it.
  • A grant is not a judgement about safety. git-paw decides nothing about your prompts. What runs without one is decided entirely by the agent CLI’s own permission system and the rules in its settings.

See also: Security Posture and Doctor.

FS-Scoped Sandbox

An optional, opt-in way to confine an agent to its worktree using your OS’s kernel sandbox — sandbox-exec (Seatbelt) on macOS, bwrap (bubblewrap) on Linux/WSL. It bounds the blast radius of a full-auto agent: writes are allowed inside the worktree and denied outside, enforced by the kernel.

The sandbox is your own local configuration. git-paw does not invoke or generate it, and does not ship a maintained profile. It launches whatever [clis.<name>].command names; you point that at a small wrapper that execs the real CLI under the sandbox. Everything below is a worked example to copy and adapt to your CLI, OS, and stack — not a drop-in git-paw ships.

The sandbox confines writes. By default it does not restrict reads — see Confidentiality hardening — and it does not cover network or resource-exhaustion. Read the Security Posture first.

How it hooks in

git-paw already gives a wrapper everything it needs at launch: the pane’s working directory is the worktree, and GIT_PAW_BROKER_URL is in the environment. So a wrapper derives its scope from $PWD with zero git-paw changes.

The wrapper must be a PATH-resolvable executable. git-paw launches a custom CLI by its name on PATH — not by an arbitrary script path. Put your wrapper on PATH (e.g. symlink it next to the real CLI) and set command to that name:

[clis.claude-sandboxed]
command = "claude-sandboxed"   # a wrapper on PATH, NOT a path to a script

If you instead point command at a script path that isn’t resolvable by name, the pane drops to a raw shell and the boot prompt is typed into it.

git paw doctor reports whether sandbox-exec/bwrap is installed.

macOS (sandbox-exec)

A wrapper (claude-sandboxed on your PATH):

#!/bin/sh
set -eu
WT="$PWD"
GITDIR="$(git -C "$WT" rev-parse --git-common-dir)"; GITDIR="$(cd "$GITDIR" && pwd)"
exec sandbox-exec \
  -D WORKTREE="$WT" \
  -D GITDIR="$GITDIR" \
  -D GITHOOKS="$GITDIR/hooks" \
  -D GITCONFIG="$GITDIR/config" \
  -D TMPDIR="${TMPDIR%/}" \
  -D CLIHOME="$HOME/.claude" \
  -D CARGOHOME="${CARGO_HOME:-$HOME/.cargo}" \
  -f "$HOME/.config/git-paw/sandbox.sb" \
  claude "$@"

The profile (sandbox.sb), a (allow default) base that denies writes except a scoped set:

(version 1)
(allow default)                          ;; reads + network stay open
(deny file-write*)
(allow file-write*
  (subpath (param "WORKTREE"))           ;; the worktree
  (subpath (param "GITDIR"))             ;; shared .git — needed to commit
  (subpath (param "TMPDIR"))
  (subpath (param "CLIHOME"))            ;; the CLI's own config/auth/cache
  (subpath (param "CARGOHOME"))          ;; toolchain cache (stack-specific)
  (regex #"^/private/tmp/claude-")       ;; the CLI's per-session temp/cwd
  (subpath "/private/tmp")               ;; shell heredocs (zsh writes /private/tmp/zsh*)
  (literal "/dev/null") (literal "/dev/tty") (regex #"^/dev/ttys[0-9]+$"))
;; Close the git-persistence gap (see below):
(deny file-write*
  (subpath (param "GITHOOKS"))
  (literal (param "GITCONFIG")))

/private/tmp must be writable, not just $TMPDIR and the CLI’s per-session prefix: zsh writes heredoc bodies to /private/tmp/zsh* while building the command it’s about to run, so the standard multi-line commit idiom — git commit -m "$(cat <<'EOF' … EOF)" — hits Operation not permitted without this grant. /private/tmp is already world-writable and holds no secrets, so this is a write-only convenience grant; it does not change the read-confidentiality tiers below (see Confidentiality hardening).

What breaks under the sandbox

Some failures aren’t a missing write grant — they’re the kernel refusing to exec at all, and no profile rule fixes them:

Setuid/setgid binaries cannot exec under sandbox-exec. /bin/ps and /usr/bin/top are setuid root on macOS. Seatbelt refuses to exec a setuid binary inside any profile, even the most permissive one:

$ sandbox-exec -p '(version 1)(allow default)' /bin/ps -o pid= -p $$
sandbox-exec: execvp() of '/bin/ps' failed: Operation not permitted

An agent running ps inside a wrapped CLI typically only sees a bare operation not permitted: ps — the same wording the sandbox uses for a denied write. Don’t chase it with a write grant: it isn’t one, and no (allow …) rule fixes it. Use pgrep instead — it works under the same profile because it isn’t setuid.

The bundled worker-orientation skill already tells agents this, so both surfaces agree; an agent that hits it should adapt (use pgrep) rather than probe or widen the profile.

Linux / WSL (bwrap)

#!/bin/sh
set -eu
command -v bwrap >/dev/null || exec claude "$@"   # detect-and-degrade
WT="$PWD"
GITDIR="$(git -C "$WT" rev-parse --git-common-dir)"; GITDIR="$(cd "$GITDIR" && pwd)"
exec bwrap \
  --ro-bind / / \
  --bind "$WT" "$WT" \
  --bind "$GITDIR" "$GITDIR" \
  --ro-bind "$GITDIR/hooks" "$GITDIR/hooks" \
  --ro-bind "$GITDIR/config" "$GITDIR/config" \
  --bind "${TMPDIR:-/tmp}" "${TMPDIR:-/tmp}" \
  --bind "$HOME/.claude" "$HOME/.claude" \
  --bind "${CARGO_HOME:-$HOME/.cargo}" "${CARGO_HOME:-$HOME/.cargo}" \
  --dev-bind /dev /dev \
  --unshare-user \
  --chdir "$WT" \
  claude "$@"

bwrap needs unprivileged user namespaces; some hardened kernels disable them — hence the command -v bwrap degrade line. sandbox-exec is deprecated but present on every macOS; both are pragmatic, not stable contracts.

The --bind "${TMPDIR:-/tmp}" "${TMPDIR:-/tmp}" line already covers shell heredocs on Linux — zsh writes heredoc bodies under $TMPDIR (or /tmp when unset), and both are bound writable, so the git commit -m "$(cat <<'EOF' … EOF)" idiom works here without an extra grant. ps is not setuid on Linux and works fine here — see What breaks under the sandbox for why it fails on macOS instead. bwrap itself sets no_new_privs, so even a setuid binary that did run inside it would run without its elevated privileges.

The writable set to get right

The profile is only as good as this list. Beyond the worktree, an agent CLI legitimately needs to write:

  • The shared .git — a child worktree commits into the main repo’s .git, so it must be writable except .git/hooks and .git/config (below).
  • The CLI’s own config/auth/cache (~/.claude, ~/.config/…) and its per-session temp/cwd. Claude Code, for example, uses /private/tmp/claude-<uid>/ and /private/tmp/claude-<hash>-cwd symlinks (random names → a regex, not a subpath) — omit these and the agent’s shell tool dies with operation not permitted.
  • TMPDIR and device nodes (/dev/null, /dev/tty, the pane pty).
  • Toolchain caches the workers write to (~/.cargo, ~/.rustup, …) — these are stack-specific; source them from your stack, don’t assume.
  • Network stays open by default: agents need the loopback broker and outbound model-API egress.

The git-persistence gap

Keep .git writable for commits, but bind .git/hooks and .git/config read-only. Otherwise a confined agent can plant a .git/hooks/post-commit script or set core.pager/editor/fsmonitor to an arbitrary command — which fires later, unsandboxed, with full privileges the next time you run plain git outside the box. The agent can still commit (index/objects/refs); it just can’t plant persistence.

Credential refresh (per-OS)

A long-lived agent refreshes its OAuth token mid-session. Where that token lives differs by OS, and it affects the profile:

  • macOS: the token is in the login Keychain, and refresh writes it back — an in-process write the (deny file-write*) base blocks, so the agent logs in fine at first but hits “Not logged in” when the token expires. To allow refresh, add ~/Library/Keychains to the writable set. Tradeoff: that grants write to your whole login keychain (and reads were already open) — so the sandbox protects filesystem write-integrity, not the confidentiality of keychain secrets. If your CLI can be configured to use file-based credentials (a file under the already-writable CLIHOME), prefer that and deny the Keychain entirely.
  • Linux: credentials are file-based (headless: a file under ~/.claude*/) or written by a separate Secret Service daemon (desktop). The file lives in the writable CLIHOME, and the daemon writes on the agent’s behalf outside the sandbox — so neither has the macOS refresh problem.

Confidentiality hardening

The base profile confines writes only; the agent can still read anything your user can (SSH keys, cloud creds, other repos). If that matters, add read-denials. Two tiers:

Tier 1 — blocklist (cheap). Keep (allow default), deny reads of known secret stores:

(deny file-read*
  (subpath (param "SSH"))       ;; ~/.ssh
  (subpath (param "AWS"))       ;; ~/.aws
  (subpath (param "GNUPG")))    ;; ~/.gnupg

Tier 2 — allowlist (strong). Deny reading your home, then re-allow only what the agent needs (everything outside $HOME — system libraries — stays readable, so the app still runs):

(deny file-read* (subpath (param "HOME")))
(allow file-read*
  (subpath (param "REPO"))
  (subpath (param "CLIHOME"))
  (subpath (param "CARGOHOME"))
  (subpath (param "KEYCHAINS")))   ;; still needed for macOS auth (see above)

~/.ssh is safe to deny for a git-paw worker: it commits locally (no key needed unless you sign commits with an SSH key), and pushes/fetches are done by the unsandboxed supervisor or you — not the worker. Deny it unless your workers sign with an SSH key or push over SSH themselves, in which case grant the specific key plus SSH_AUTH_SOCK rather than the whole folder.

The one residual even under Tier 2 is the login Keychain (allow-listed for auth); per-item ACLs do not gate a confined agent in your unlocked session. Close it only via file-based credentials as noted above.

Spec-Driven Launch

The --from-all-specs and --specs flags let you define branches, CLI assignments, and prompts in spec files instead of using interactive selection. git-paw reads spec files from a configured directory, creates worktrees for each pending spec, and launches AI CLIs with the spec content injected into each worktree’s gitignored .git-paw/AGENTS.local.md sidecar (the tracked AGENTS.md is left untouched).

Quick Example

# Initialize repo config (creates .git-paw/config.toml)
git paw init

# Add spec files to your specs/ directory, then launch every discovered spec
git paw start --from-all-specs

# Narrow to specific specs without editing config
git paw start --specs add-auth,fix-session

# Open a multi-select picker (requires an interactive terminal)
git paw start --specs

Picking specs at launch time

You have three ways to control which specs are launched:

  • --from-all-specs — launches every discovered spec across the configured backend.
  • --specs NAME[,NAME...] — comma-separated list of spec names. Mirrors the existing --branches feat/a,feat/b syntax. Unknown names exit with the discovered-set listed as candidates so you can correct quickly.
  • --specs (bare, no values) — opens a multi-select picker showing every discovered spec. Each row shows the unit identifier; for Spec Kit features that decompose into multiple worktrees, the row also shows a worktree-count hint (e.g. 003-user-list — 3 worktrees: 2 [P] + 1 phase/). The picker is a type-to-filter multi-select (identical to the branch picker): type to narrow the rows by case-insensitive substring match against the displayed label, Ctrl-U clears the filter, Space toggles, and Enter confirms. Selecting a filtered row still expands to every underlying entry for that unit, and selections persist across filter changes.

--from-all-specs and --specs are mutually exclusive — they express opposing intents — and clap rejects any invocation that combines them.

Picker requires a TTY

The bare --specs form requires an interactive terminal. When stdin is not a TTY (CI, scripted invocation, redirected input), git-paw exits with an actionable error pointing at the explicit forms:

error: --specs without values requires an interactive terminal
  Use `--specs NAME[,NAME...]` to narrow explicitly, or
  `--from-all-specs` to launch every discovered spec.

Name resolution rules

--specs NAME matches against the discovered set using these strategies in order:

  1. Exact match on the spec id (case-sensitive). Matches OpenSpec change names, Markdown filename stems, or Spec Kit decomposed entry ids like 003-user-list-T009.
  2. Spec Kit feature-name match — the value matches a Spec Kit feature directory prefix (e.g. 003-user-list). All decomposed entries belonging to that feature are launched together.
  3. Spec Kit numeric prefix — a digits-only value (e.g. 003) matches a unique feature directory whose name starts with 003 followed by a non-digit boundary. Ambiguous prefixes (two features both starting with 003) are rejected with the candidate list.

Unknown names error out before any worktrees are created. The error message includes the unresolved name AND the discovered candidate list. There is no partial start.

Spec Formats

git-paw supports four spec formats: OpenSpec (directory-based), Markdown (file-based), Spec Kit (.specify/-based, GitHub Spec Kit), and Superpowers (docs/superpowers/plans/-based, obra/superpowers).

OpenSpec Format (default)

Each pending change lives in its own subdirectory under the specs directory. The subdirectory name becomes the branch identifier.

specs/
  add-auth/
    tasks.md          # Required — main prompt
    specs/
      jwt/spec.md     # Optional — supplementary spec
  fix-pagination/
    tasks.md

Discovery is by archive status, not task completion. --from-all-specs includes every change subdirectory under specs/ except anything under specs/archive/. A change with all tasks marked - [x] in its tasks.md is still picked up — task-completion is a progress tracker, not a discovery filter. After completing and verifying a change, run:

openspec archive <change-name>

before the next git paw start --from-all-specs invocation. openspec archive moves the change directory under specs/archive/<date>-<change>/ and syncs its delta specs into the main specs at specs/<capability>/spec.md. The next session won’t spawn a worktree for it.

tasks.md contains the prompt content sent to the AI CLI. It supports optional YAML frontmatter:

---
paw_cli: claude
---

## Implement JWT Authentication

Add JWT token support to the auth module.

The paw_cli field overrides the CLI for this specific spec. If omitted, the default resolution chain applies (see CLI Resolution below).

Supplementary spec files in specs/<name>/spec.md are appended to the prompt with section headers. File ownership can be declared with “Files owned:” or “Owned files:” followed by a markdown list.

Markdown Format

Flat .md files in the specs directory. Each file uses YAML frontmatter to control status and branch mapping.

specs/
  add-auth.md
  fix-pagination.md
  design-notes.md      # ignored — no paw_status: pending

Example file (specs/add-auth.md):

---
paw_status: pending
paw_branch: add-auth
paw_cli: claude
---

## Implement JWT Authentication

Add JWT token support to the auth module.

Frontmatter Fields

FieldRequiredDescription
paw_statusYesMust be "pending" to be included. Other values ("done", "in-progress") are ignored.
paw_branchNoBranch name suffix. Falls back to filename stem if absent.
paw_cliNoCLI override for this spec.

Only files with paw_status: pending are picked up.

Spec Kit Format

Spec Kit projects place each feature in its own directory under .specify/specs/<feature>/, alongside an optional .specify/memory/constitution.md for project-level rules.

.specify/
  memory/
    constitution.md            # Optional — project rules / governance
  specs/
    001-room-setup/
      spec.md                  # Feature requirements
      plan.md                  # Implementation plan
      tasks.md                 # Required — task list with phases
      checklists/              # Optional — advisory validation criteria
        security.md
    002-poker-voting/
      spec.md
      plan.md
      tasks.md

Selecting Spec Kit. git-paw does not auto-detect the spec system from the filesystem — you set it explicitly. Either configure [specs] type = "speckit" with dir = ".specify/specs" in .git-paw/config.toml (git paw init prompts you to choose the spec system and writes this section), or pass --specs-format speckit for a single launch.

Selecting a spec system for one launch with --specs-format:

# Force Spec Kit even when [specs] config says otherwise
git paw start --from-all-specs --specs-format speckit

# Force OpenSpec on a project that has a `.specify/` folder
git paw start --from-all-specs --specs-format openspec

# Force Superpowers on a project with docs/superpowers/plans/
git paw start --from-all-specs --specs-format superpowers

tasks.md decomposition. Spec Kit’s tasks.md files use ## Phase N: <Name> headings and - [ ] T<NNN> task lines. git-paw decomposes the current phase (the first phase with any incomplete task) into one worktree per kind:

  • Each incomplete [P]-marked task → its own worktree on task/<task-id>-<slug> (one agent per task, parallel).
  • All remaining incomplete non-[P] tasks → one consolidated worktree on phase/<feature>-<phase-slug> (one agent works through them sequentially, since the absence of [P] indicates shared files or context).
## Phase 2: Foundational
- [ ] T009 [P] Contract test POST /auth/otp/request   → task/t009-contract-test-...
- [ ] T010 [P] Contract test POST /auth/otp/verify    → task/t010-contract-test-...
- [ ] T011 Setup database schema                       ┐
- [ ] T012 Create auth tables                          ├ phase/<feature>-foundational
- [ ] T013 Seed test data                              ┘

In this example, one feature directory produces three worktrees and three branches. This “one Spec Kit feature → multiple branches” model is intentional — [P] tasks parallelise across worktrees; non-[P] tasks sequence within a single worktree.

Phase advancement. Phases earlier than the current one are assumed complete (all - [x]). Phases later than the current one are deferred — they only produce worktrees on a future scan, after every task in the current phase has been ticked off. Fully completed features (every task in every phase is - [x]) are skipped silently.

Boot prompts. Each Spec Kit worktree’s .git-paw/AGENTS.local.md sidecar contains:

  1. Feature Context — the full spec.md content.
  2. Implementation Plan — the full plan.md content (omitted when absent).
  3. Validation Criteria (advisory) — each checklists/<file>.md content under its own heading. Checklists are advisory.
  4. Your Task — the single task description (task/... worktrees) or the ordered task list plus sequential-execution + - [x] writeback + agent.artifact (with status: "done") instructions (phase/... worktrees).

Task writeback. Agents working in a phase/... worktree flip - [ ] to - [x] in the worktree’s tasks.md as they complete each task. The writeback may be committed alongside the task’s code change or as a separate commit. Per-line edits across worktrees are merged by git in the normal way. The conflict detector exempts writebacks to the shared tasks.md from ownership-violation detection; concurrent edits to it by two worktrees can still raise in-flight warnings.

Constitution wiring. With [specs] type = "speckit" and no explicit [governance] constitution setting, git-paw probes <specs dir>/../memory/constitution.md (normally .specify/memory/constitution.md) and, when it exists, wires it in as the governance constitution automatically — so a project that already has a Spec Kit constitution gets it configured for free. An explicit [governance] constitution value always wins; see Configuration → Spec Kit constitution auto-wiring.

Superpowers Format

obra/superpowers is a multi-CLI methodology plugin whose writing-plans skill saves implementation plans as flat Markdown files under docs/superpowers/plans/. git-paw scans those plans — one worktree per incomplete plan.

docs/superpowers/plans/
  2026-07-20-add-auth.md       # one plan = one worktree
  2026-07-21-export-csv.md

Unlike Spec Kit, a superpowers plan is not fanned out into per-task worktrees — a plan is a sequential TDD chain meant for a single subagent-driven-development worktree, so each plan file maps to exactly one branch: plan/<plan-file-stem>.

Plan structure. Each plan carries a header (**Goal:**, **Architecture:**, **Tech Stack:**) and one or more ### Task N: <name> sections, each with a **Files:** block (exact paths) and bite-sized - [ ] checkbox steps plus Run: verification commands.

Discovery. A plan is picked up when it has at least one incomplete - [ ] step. A plan whose every step is - [x] is skipped (with a warning); a .md file with no ### Task/step lines — e.g. a design doc under docs/superpowers/specs/ — is skipped silently.

Selecting Superpowers. As with every format, git-paw does not auto-detect it — configure [specs] type = "superpowers" with dir = "docs/superpowers/plans" (git paw init prompts you to choose the spec system), or pass --specs-format superpowers for a single launch.

Boot prompt. Each worktree’s .git-paw/AGENTS.local.md sidecar contains: Plan Context (Goal / Architecture / Tech Stack), Your Tasks (the plan’s task sections with their Files: and Run: metadata), and an Execution instruction to work the steps in order, flip - [ ] → - [x] in the plan file as each lands, and commit your work when every step shows - [x]; the post-commit hook will auto-publish your completion.

Configuration

Configure spec scanning in .git-paw/config.toml (or the global config):

# Default CLI for spec-mode launches (bypasses picker when set).
default_spec_cli = "my-cli"

# Prefix for spec-derived branch names (default: "spec/").
branch_prefix = "spec/"

# Spec scanning configuration.
[specs]
dir = "specs"         # Directory containing spec files (relative to repo root)
type = "openspec"     # "openspec" (default), "markdown", "speckit", or "superpowers"

The interpretation of dir depends on the format:

  • openspec — directory of change subdirectories (<dir>/<change>/tasks.md).
  • markdown — directory of .md files (<dir>/<spec>.md).
  • speckit — directory of feature subdirectories (<dir>/<feature>/tasks.md); typically .specify/specs.
  • superpowers — directory of flat plan files (<dir>/<plan>.md); typically docs/superpowers/plans.

Branch Naming

Branch names depend on the format:

  • OpenSpec: <branch_prefix><id> where ID is the subdirectory name. specs/add-auth/ with prefix spec/ becomes branch spec/add-auth.
  • Markdown: <branch_prefix><id> where ID is paw_branch (if set) or the filename stem. specs/add-auth.md with prefix spec/ becomes branch spec/add-auth.
  • Spec Kit: branch prefix is task/ or phase/ depending on the entry kind; branch_prefix is not applied. Example: .specify/specs/003-user-list/tasks.md with task - [ ] T009 [P] Add login form produces branch task/t009-add-login-form.
  • Superpowers: plan/<slugified plan-file stem>; branch_prefix is not applied. Example: docs/superpowers/plans/2026-07-20-add-auth.md produces branch plan/2026-07-20-add-auth.

Spec Kit and Superpowers derive their branch name from spec text — a task description, a plan-file stem — by lowercasing it and replacing every character outside [a-z0-9_] with -. Underscores survive into the branch name. A task reading - [ ] T001 [P] Create ROOM_SETUP.md produces branch task/t001-create-room_setup-md, and a plan file add_auth.md produces branch plan/add_auth: _ is legal in a git ref, and the branch is durable state that worktree recovery and --resume use to look up work, so it is left intact.

The agent’s broker agent_id is a separate derivation from that branch name, and it does not keep _ — the broker’s id grammar forbids it, so the underscore maps to - there. The branch task/t001-create-room_setup-md publishes as agent task-t001-create-room-setup-md. See Identifier Forms for the full conversion.

CLI Resolution

When a spec-mode launch runs, CLIs are resolved in priority order:

  1. --cli flag (highest) — applies to all specs, no prompt
  2. paw_cli in spec — per-spec override from frontmatter
  3. default_spec_cli in config — fills remaining specs without prompt
  4. default_cli in config — pre-selects in picker for remaining
  5. Interactive picker (lowest) — prompts for any unresolved specs
# Override all specs to use claude
git paw start --from-all-specs --cli claude

# Use per-spec paw_cli and default_spec_cli from config
git paw start --from-all-specs

# Narrow to a subset
git paw start --specs add-auth,fix-session --cli claude

# Preview without executing
git paw start --from-all-specs --dry-run
git paw start --specs add-auth --dry-run

Combining --from-all-specs with supervisor mode

--from-all-specs --supervisor (or --from-all-specs with [supervisor] enabled = true in your config) engages the supervisor flow against the discovered specs. git-paw scans the configured specs directory, creates one worktree per spec, and builds one tmux session with the dashboard pane (pane 0) and the per-spec agent panes from pane 1 — the orchestrator runs outside the session, reading the context rendered into .git-paw/AGENTS.local.md — then returns with an attach hint (or, with --unattended, runs the in-process drive loop instead) — same supervisor architecture as --branches-driven sessions, just with branches discovered from specs.

The CLI Resolution chain above applies identically here — --supervisor never bypasses or alters it. In particular, default_spec_cli is honoured for a spec-driven worker pane exactly as it is without --supervisor, so a [clis.<name>] sandbox pinned via default_spec_cli cannot be silently swapped for a different CLI just because the launch went through the supervisor flow. git paw doctor also reports the effective spec-worker CLI and warns if a configured default_spec_cli is never actually reachable by any discovered spec — see Doctor.

# Spec-driven session with supervisor watching
git paw start --from-all-specs --supervisor

# Same outcome via [supervisor] enabled = true in .git-paw/config.toml
git paw start --from-all-specs

# Skip supervisor for this session even if enabled in config
git paw start --from-all-specs --no-supervisor

When [broker] enabled = true — the broker defaults to disabled and must be opted into explicitly in config (supervisor mode does not turn it on for you) — each spec agent pane receives a boot block via tmux send-keys carrying its BRANCH_ID, broker URL, and the patterns for publishing through the bundled broker.sh helper. This applies to both spec-mode-only (--from-all-specs without supervisor) and supervisor mode; a spec-mode-only launch additionally submits the boot block (rather than leaving it pre-filled) and appends the task prompt naming the agent’s spec assignment, so the agent starts work without a human completing the prompt by hand — see Boot-block submission and task-prompt injection below.

Boot-block submission and task-prompt injection

Not every launch path treats its agent pane’s initial prompt the same way:

  • Bare git paw start (no spec selection) pre-fills the boot block — followed by the worktree rule naming that pane’s own worktree root — into the pane’s input line and stops. No Enter is sent. This is deliberate: the launch is human-driven, so you type your actual task after the boot instructions and submit it yourself.
  • A spec-driven launch (--specs / --from-all-specs, with or without --supervisor) has no human typing the task — the task comes from the spec — so leaving the prompt pre-filled would strand the agent with nothing to act on. Both the supervisor path and the non-supervisor spec-mode path submit the agent’s prompt automatically: the text is injected, then — after a brief per-CLI settle delay — a separate Enter submits it. That prompt is the boot block (when the broker is enabled), the task prompt’s prose naming the agent’s spec assignment, and the worktree rule, joined by blank lines.
  • With [broker] enabled = false, the non-supervisor spec-mode path (bare --specs / --from-all-specs, no --supervisor) injects no boot block — it is broker-specific content — but still injects and submits the task prompt, since the agent’s assignment does not depend on the broker. The supervisor path builds its agent panes’ prompt the same way regardless of whether the broker is enabled, so a supervisor-mode agent still receives a boot block even with the broker off; only its broker-coordination instructions are then inert.

Every launch path also composes its pane’s command from the resolved approval flags (agent_approval and any per-CLI approval_args override) — a bare or spec-driven launch is not exempt just because no supervisor is watching.

The workflow entry point

A spec format may declare a workflow entry point: one command line that brings the agent into the format’s native workflow, so it walks its task list and ticks tasks off rather than improvising a private process. Today only OpenSpec declares one — /opsx:apply <id>. Markdown, Spec Kit, Superpowers and the no-spec --branches path declare none.

When a format declares an entry point, a launch that submits the agent’s prompt sends two messages, not one:

  1. the composed prompt above (boot block + task prose + worktree rule), and
  2. the entry point on its own, as a separate composer line with its own Enter.

The command used to sit inside the task prompt’s prose, at the end of a sentence — and a weaker model read that sentence and narrated the command instead of running it, going idle with the work untouched. Submitted alone, the line is what a paste-aware CLI parses as a slash-command invocation, which is the behaviour the agent needs. The text comes only from the spec format’s own bundled template; git-paw’s code names no workflow command, so a format whose apply invocation changes changes only its template.

A pane whose format declares no entry point receives exactly one message, and the launch does not wait on it at all.

The idle wait

git-paw does not type the second message the instant the first one is sent. Typing into a CLI that is still working is unpredictable — a slash command dropped into a busy composer may be queued as text, run later, or discarded, and a keystroke that lands on a live permission prompt answers it. So after the first message’s Enter, the launch waits until the pane is idle:

  • no live permission prompt,
  • not mid-response, and
  • an input box that is empty, or whose shape the CLI’s prompt-shape profile does not recognise,

observed on two consecutive captures. A single idle-looking capture is not enough: the instant between the Enter and the CLI starting its turn reads as idle too. Immediately before typing, git-paw captures the pane once more and types nothing unless it is still idle — so a pane that raised a prompt or started responding in the meantime receives no stray keystroke. The entry point is then submitted with the same split-send the first message used: literal text, settle delay, separate Enter.

Every waiting pane is polled in one loop, concurrently, under a single ~120-second budget — so a session’s launch grows by its slowest first turn, not by the sum of them. The OpenSpec prose tells the agent its workflow command is coming, which keeps that first turn short.

This wait happens before the launch attaches you to the session (and before git paw add prints its confirmation), so git-paw announces it first:

Waiting for 3 agent(s) to go idle before submitting their workflow command (up to 120s)...

If you see that line and nothing after it for a while, a pane is still working or is sitting at a permission prompt — attach in another terminal with tmux attach -t paw-<project> to see which.

A CLI git-paw has no profile for matches none of the markers above and therefore reads as idle. At worst its entry point follows the first message almost immediately — exactly what that CLI’s own input queue would see from a human typing two messages in a row.

If a pane never goes idle

When the budget elapses with a pane still busy or still sitting at a permission prompt, git-paw types nothing into it and prints a warning to stderr naming the pane, the agent, and the exact line to submit:

warning: pane 2 (spec-add-auth) did not go idle within 120s, so its workflow
command was not submitted; once the agent is idle, submit: /opsx:apply add-auth

The launch continues normally — the agent has its assignment and its boot block, it simply has not been dropped into its spec workflow. Attach to the session, answer whatever the pane is waiting on, and type the quoted command into that pane yourself. An unattended run leaves the same line in its output for an orchestrator to act on.

git paw add and paused sessions

git paw add to a running session follows the same rule: the new agent’s prompt is submitted, then its entry point once its pane goes idle.

git paw add to a paused session submits nothing — the pane is created but the prompt is held in the session record for the restart. The entry point is held alongside it. git paw start then submits the held prompt, waits for the pane to go idle, submits the entry point, and clears both. A session receipt written by an earlier git-paw carries no held entry point and restarts exactly as it did before; see Pause and Resume.

Non-interactive launches

If git paw start is invoked from a non-interactive terminal — CI, scripted invocation, or a harness tool that pipes stdin — git-paw skips the auto-attach step and prints an attach hint instead:

Session 'paw-myproject' started in detached mode.
Attach with:  tmux attach -t paw-myproject

Supervisor mode never auto-attaches, interactive terminal or not: the orchestrator is your own agent session outside tmux, and (without --unattended) the launch prints Supervisor session '<name>' launched with <N> coding agent(s)., the orchestrator-context path (.git-paw/AGENTS.local.md), and the same Attach with: hint. Attach later from a real terminal:

tmux attach -t paw-myproject

Session Logging

git-paw can capture the full terminal output of each AI CLI pane to log files. This lets you review what happened in a session after it ends.

Enabling Logging

Add the [logging] section to your .git-paw/config.toml (or global config):

[logging]
enabled = true

When enabled, git-paw uses tmux pipe-pane to capture each coding-agent pane’s output to a log file when it launches a fresh session — a plain git paw start, a spec-driven launch, or a supervisor session. The dashboard pane is not logged, and neither are panes attached later with git paw add or the panes of a session recovered by git paw start after git paw stop.

Log Storage

Logs are stored under .git-paw/logs/ organized by session:

.git-paw/logs/
  paw-my-project/
    feat--add-auth.log
    fix--pagination.log

Branch names are sanitized for filenames — / becomes -- (e.g., feat/add-auth becomes feat--add-auth.log).

The git paw init command automatically adds .git-paw/logs/ to .gitignore so logs are never committed.

Replaying Logs

Use git paw replay to view captured logs.

List available sessions and branches

git paw replay --list

Shows all sessions and their logged branches.

Replay a branch

git paw replay feat/add-auth

The argument must match a log exactly — as the original branch name (feat/add-auth), the sanitized stem (feat--add-auth), or the full filename (feat--add-auth.log). Partial names are not matched; when nothing matches, the error lists the available branches.

By default, ANSI escape codes are stripped for clean text output.

Replay with colors

git paw replay feat/add-auth --color

Preserves ANSI colors and pipes through less -R for scrollable colored output.

Replay from a specific session

git paw replay feat/add-auth --session paw-my-project

By default, the most recent session is used. Use --session to specify an older one.

Flags

FlagDescription
--listList available log sessions and branches
--colorDisplay with ANSI colors via less -R
--session <name>Session to replay from (defaults to most recent)

AGENTS.md Injection

An agent’s working context is the combined view: the project’s tracked AGENTS.md plus a managed git-paw section that carries the entire spec body for the agent’s assigned change (proposal, design when present, task list, and any spec deltas). The agent reads the spec from this combined view plus openspec/changes/<id>/, not from the boot prompt.

git-paw writes that combined view to a gitignored sidecar, .git-paw/AGENTS.local.md, in each worktree — not to the worktree’s tracked AGENTS.md. The boot prompt points the agent at the sidecar. The tracked AGENTS.md is left exactly as the project committed it, so a legitimate hand edit to it (yours or the agent’s) shows up in git status and commits normally, mid-session.

Why a sidecar. Through v0.7.0 git-paw injected its section into the tracked worktree AGENTS.md and then ran git update-index --assume-unchanged AGENTS.md to keep git add -A from committing the ephemeral injection. That bit is file-level: git stopped reporting every change to AGENTS.md, not just the injected block. An agent that needed to commit a real AGENTS.md edit found git status reporting the file unmodified and the commit silently empty for that path. Moving the injection to a gitignored sidecar removes the reason assume-unchanged existed: the tracked file is never touched, so it is never hidden.

Boot-Prompt-Full-Body Model

Earlier versions of git-paw embedded a condensed spec excerpt inside the supervisor-mode boot prompt itself (“Branch + CLI + Spec content + Owned files”). The supervisor-mode boot prompt is intentionally short: it points the agent at the sidecar .git-paw/AGENTS.local.md and at the change directory openspec/changes/<id>/, and tells the agent to read those files before acting. (Because the supported CLIs auto-load only the worktree-root AGENTS.md and not the sidecar, the prompt is what directs the agent to the sidecar’s combined content — every backend, including OpenSpec, points there first.)

The benefits of this split:

  • The spec body lives in a markdown file the agent can re-read at any time, not in volatile boot-prompt context.
  • The boot prompt becomes small enough to remain in the agent’s working context for the entire session, even after long conversations.
  • Manual operators can pre-stage the boot block at the pane input line via paste, the same way the supervisor stages it programmatically.
  • The same generator (src/agents.rs::setup_worktree_agents_md) writes the same content in both supervisor mode and broker-only mode, so the agent’s experience does not depend on which mode launched it.

How It Works

  1. Sidecar. When a session starts, each worktree gets a .git-paw/AGENTS.local.md sidecar containing the combined view (the root AGENTS.md content plus the managed git-paw section). The sidecar is overwritten on every git paw start so the spec body never goes stale.
  2. The tracked AGENTS.md is untouched. git-paw does not write to it, hide it, or exclude it. A hand edit to it appears in git status and commits normally.
  3. Exclusion. The sidecar path (.git-paw/AGENTS.local.md) is added to the worktree’s ignore set so the ephemeral injection is never committed. For a linked worktree, git honors info/exclude only from the common git directory, so the entry is written there.
  4. Self-healing upgrade. If a worktree carries a stale assume-unchanged bit on AGENTS.md from a pre-sidecar git-paw, the next git paw start clears it (git update-index --no-assume-unchanged AGENTS.md) so the tracked file becomes committable again — no manual git update-index needed.

Markers

git-paw manages its section in the sidecar using HTML comment markers so the root AGENTS.md content carried alongside it is preserved across launches:

<!-- git-paw:start — managed by git-paw, do not edit manually -->

(git-paw content here — full spec body)

<!-- git-paw:end -->

Content between these markers is replaced on each launch. Content outside the markers is preserved. If no markers exist, the section is appended to the end of the file.

Orchestrator pointer section

Separately from the per-session sidecar, git paw init injects one short, permanent section into your root AGENTS.md (creating the file when it is absent), between its own markers:

<!-- git-paw-orchestrator:start -->

(when a git-paw session is active, you are its orchestrator: read
 .git-paw/skills/orchestrator/SKILL.md and .git-paw/AGENTS.local.md)

<!-- git-paw-orchestrator:end -->

It is how the agent you drive the session with discovers the orchestrator skill: AGENTS.md is the cross-CLI file that agent already reads. The prose comes from a bundled, project-agnostic asset and carries no broker URL, secret, toolchain or vendor name. Re-running git paw init replaces the section in place, leaving everything outside the markers byte-identical. Unlike the per-session <!-- git-paw:start --> block, it is configuration, not session state: git paw stop and git paw purge leave it alone. Delete the section by hand if you do not want it; init reports what it wrote.

Sidecar Content

Each worktree’s .git-paw/AGENTS.local.md sidecar includes:

  • The root repo’s AGENTS.md content (if any), preserved verbatim.
  • A git-paw section between the markers carrying:
    • Branch name and assigned CLI for this worktree.
    • Full spec body — proposal, design, tasks, and any spec deltas from openspec/changes/<id>/. The exact set depends on what the change directory contains; missing files (design.md is often optional) are silently skipped.
    • Owned files list (from OpenSpec file-ownership declarations) when declared.
    • Boot block that calls the bundled .git-paw/scripts/broker.sh helper for the agent to self-report status, artifacts, blockers, and questions. The helper (installed by git paw init, alongside the supervisor’s sweep.sh) discovers the broker URL and shapes the JSON, so the boot block carries no raw curl or broker URL. Nothing is seeded into the agent CLI’s settings file, and git-paw answers no prompt on the agent’s behalf. Whether the helper runs without prompting is the CLI’s own decision; because it always lives at the stable path .git-paw/scripts/broker.sh, one permission rule you write there covers every call instead of a broad curl rule — see Coordination.

The supervisor-mode boot prompt does not duplicate any of this content — it points at the sidecar. If a change is updated on disk during a session (e.g. the supervisor edits tasks.md to mark a task complete), the worktree’s sidecar is regenerated on the next git paw start invocation rather than live-patched mid-session.

Agent Coordination

When multiple AI agents work in parallel, they benefit from knowing what the others are doing. The coordination broker is a lightweight HTTP server that lets agents share status updates, publish artifacts, and flag blockers – all without touching git.

Enabling the Broker

Add a [broker] section to your .git-paw/config.toml:

[broker]
enabled = true

When you run git paw start, pane 0 becomes a dashboard instead of an agent pane and the agents start at pane 1. Supervisor mode keeps the same layout: the orchestrator runs outside the session (see Supervisor § Session layout). The broker runs in its own process in a detached broker window; the dashboard is a client of it and displays a live status table.

How Agents Discover the Broker

git-paw sets the GIT_PAW_BROKER_URL environment variable in every agent pane. Agents use this URL to send and receive messages. A typical value is http://127.0.0.1:9119.

With the broker enabled, each agent’s boot block calls the bundled broker helper (or, for an MCP-speaking CLI, the MCP publish tools), and the agent’s AGENTS.md view carries the bundled coordination skill with the broker URL pre-expanded, so agents know how to use the broker without any manual setup.

Requests the broker refuses

The broker is unauthenticated, so before routing any request it checks two headers to keep browsers and DNS-rebinding pages out. A refused request is not routed and changes no broker state:

StatusBodyCause
403{"error":"forbidden host"}The broker is bound to a loopback address and the request’s Host is missing, or its host part is not localhost, an IPv4 literal in 127.0.0.0/8, or [::1]. The port is not checked, so tunnels and port-forwards are fine.
403{"error":"forbidden origin"}The request carries an Origin header whose host is not loopback, or the serialized value null, or the header was sent more than once or with unreadable bytes. This check applies on every bind. A request with no Origin — which is what every non-browser client sends — is unaffected.

A Host must also be followed by nothing or a well-formed :<port>, so localhost:evil.com is refused; a duplicated or unreadable Host is refused; and an absolute-form request target (GET http://host/path) must name a loopback authority too.

Because the check runs before routing, it also answers for unknown paths and wrong methods: a request with a foreign Host gets 403, not 404/405.

Every client git-paw ships — the broker.sh and sweep.sh helpers, the post-commit hook, the CLI, the MCP server and the dashboard — addresses the broker by its configured http://<bind>:<port> URL and sends no Origin, so none of them is affected. See Security Posture for the threat model, the non-loopback-bind exception, and the reverse-proxy migration.

A verdict message is refused on its own terms, after those checks:

StatusBodyCause
403{"error":"authority required"}The message is an agent.verified, or an agent.feedback whose from is supervisor, and the request’s X-Paw-Authority header is missing or does not match the session’s authority credential. Nothing is delivered or logged.

Feedback from any other sender — the post-commit branch-guard hook, the in-process conflict-detector, a person — is not a verdict and needs no header. Root-side helpers (sweep.sh verified, feedback-gate, conflict-ack) attach the credential for you; see Security Posture.

Broker helper

git paw init installs a bundled agent-side helper at .git-paw/scripts/broker.sh — the agent-facing analogue of the supervisor’s sweep.sh. .git-paw/scripts/ is gitignored, so a fresh agent-worktree checkout does not contain it. git paw start and git paw add therefore auto-provision the helper into each agent worktree at setup: they write broker.sh (whenever the broker is enabled) and docs-fetch.sh (whenever docs_base_url is configured) — with the shared _paw_common.sh preamble both scripts source — into the worktree’s .git-paw/scripts/, executable, from the same bundled assets init installs. The scripts are (re)written on every attach, so a worktree’s helper always matches the running binary’s version and agents never copy it by hand from assets/. The agent invokes the helper by its stable relative path.

The helper wraps every agent→broker curl an agent is allowed to make. It discovers the broker URL from .git-paw/config.toml [broker] (defaulting to http://127.0.0.1:9119) and shapes the JSON internally, so callers pass only simple arguments. The agent id comes from --agent <id> (the boot block passes the pre-expanded branch id) or, absent one, from slugifying the current worktree branch. Run .git-paw/scripts/broker.sh --help for the full surface.

In the repository’s main working tree the helper needs an explicit --agent <id>. Agents publish from linked worktrees; the main working tree is the orchestrator’s, and deriving an id from its branch would mint a phantom agent named after the default branch (main → agent-main) rather than identifying anybody. Run there without --agent, the helper exits non-zero with a diagnostic naming the remedy and publishes nothing. Pass the identity you mean — broker.sh --agent supervisor question "which order?" — and it publishes as before. Nothing changes for an agent in its own worktree, where derivation works exactly as it always has.

SubcommandPublishes / reads
status <message>agent.status (status:"working", the message, modified_files:[])
artifact [--exports a,b] [--files a,b]agent.artifact (status:"done") — the code-less DONE fallback
blocked <needs> <from>agent.blocked with dependency info
question <text>agent.question
intent <summary> <files> [valid_for_seconds]agent.intent (forward-coordination file claim); the TTL defaults to 900 seconds when omitted
poll [since]reads GET <broker>/messages/<agent-id>?since=<n> — this agent’s inbox

valid_for_seconds is a required field of agent.intent, so the helper always sends one: give a TTL explicitly when you want a different window, and the default of 900 seconds (15 minutes) applies otherwise.

A publish the broker rejects is loud. Every publish subcommand checks the broker’s HTTP response. When the broker does not accept the publish — any non-2xx status, or no response at all because the broker is not running — the helper prints the broker’s error to stderr and exits non-zero; it never reports the publish as done. Treat a non-zero exit as “this event did not reach the broker” and fix the cause (a malformed agent id, a stopped broker) rather than moving on. The same holds for the supervisor-side sweep.sh publish verbs.

The helpers are refreshed on every attach. git paw start and git paw add (re)write <worktree>/.git-paw/scripts/ from the bundled assets of the binary that launched the session, on every launch path, so a worktree’s helper always matches that binary’s version. Re-attaching a worktree that already has the scripts rewrites them rather than failing — which also means local edits to a worktree’s helper copy are overwritten. Edit assets/scripts/ (when developing git-paw itself) rather than a deployed copy.

Why a script and not a git paw publish subcommand? The helper is an agent-internal mechanism. A user-facing subcommand would surface in --help and produce confusing errors when run by a human (no broker, no session). A script under .git-paw/scripts/ is unambiguously agent-internal, mirrors the supervisor’s sweep.sh, and gives you a single stable path that one permission rule in your agent CLI can name — see Boot calls and permission prompts.

Boot-Prompt Injection

To ensure reliable agent self-reporting, git-paw automatically injects a standardized boot instruction block into every agent’s initial prompt. The block calls .git-paw/scripts/broker.sh (with the pre-expanded --agent <id>) for four essential operations — no raw curl and no broker URL appear in the boot block:

1. REGISTER - Immediate Status Publication

Agents automatically publish their working status with a “booting” message as their very first action:

.git-paw/scripts/broker.sh --agent feat-auth status booting

2. DONE - Task Completion Reporting

The primary completion path is git commit. The git-paw post-commit hook auto-publishes agent.artifact { status: "committed" } with modified_files derived from git diff HEAD~1 --name-only, so agents working on code changes do not publish anything manually — they commit and the hook reports on their behalf.

The boot block retains a manual agent.artifact { status: "done" } fallback for code-less tasks (docs-only updates handled outside the worktree, planning notes, exploration tasks where the artifact is information reported to the broker). The block warns agents NOT to publish manual done while their worktree has uncommitted changes — they should commit instead.

.git-paw/scripts/broker.sh --agent feat-auth artifact --exports "" --files ""

3. BLOCKED - Dependency Waiting Notification

Agents can properly declare when they’re waiting on dependencies:

.git-paw/scripts/broker.sh --agent feat-api blocked "auth token format" feat-auth

4. QUESTION - Uncertainty Escalation (Critical)

Agents are instructed to publish questions and wait for answers rather than guessing:

.git-paw/scripts/broker.sh --agent feat-auth question "Should the JWT use RS256 or HS256 signing?"

IMPORTANT: The boot block explicitly instructs agents: “DO NOT CONTINUE UNTIL YOU RECEIVE AN ANSWER!”

MCP tool form

The four events above are the broker.sh helper form. For a CLI known to speak MCP (Model Context Protocol) — the built-in set is claude, codex, cursor-agent, and agy — the boot block instead expresses each event as an invocation of the matching MCP publish tool: publish_status, publish_artifact, publish_blocked, publish_question. The events, their meanings, and their ordering are identical between the two forms; only the invocation mechanism differs, and each MCP publish tool resolves the publishing agent id from the session’s own worktree branch automatically — the boot block never needs to pass one.

An unrecognized or unknown-capability CLI always gets the broker.sh form. This is the safe default: a boot block naming tools a CLI cannot call would strand the agent at the very first step, so git-paw only switches to the MCP form when it is confident the target CLI can act on it.

A [clis.<name>] mcp = true|false entry overrides the built-in set, and is consulted first. The lookup is keyed on the CLI’s identity — the [clis.<name>] key, or the built-in / pass-through name — never on the command that key launches, so a custom variant such as [clis.claude-oss] command = "claude" keeps the helper form until you set mcp = true on it.

The MCP publish surface has no inbox or poll tool, so even the MCP form of the boot block reads the agent’s inbox with .git-paw/scripts/broker.sh --agent <id> poll.

Boot calls and permission prompts

Whether an agent’s first boot action stalls on a permission prompt is decided by the agent CLI’s own permission system — nothing in git-paw decides it, and git-paw grants nothing on your behalf. Two things are in play:

  • the agent CLI’s resolved permission mode, set via agent_approval (see Configuration → Supervisor). Under a mode that still prompts for shell commands, the boot publish will prompt;
  • your own allow rules in that CLI. The boot block always invokes the helper at the single stable path .git-paw/scripts/broker.sh (or the bash .git-paw/scripts/broker.sh form), precisely so one permission rule scoped to that exact path covers every boot call — never a broad curl * rule, and never per-endpoint curl prefixes that drift with URL normalisation or flag order.

git-paw writes to no CLI settings file — not the repo-root .claude/settings.json, not a per-worktree copy, and not a configured [clis.<name>].settings_path. It also never answers the prompt for you: if the boot publish prompts, an --unattended run escalates it for a human decision like any other prompt, and the rest of the wave keeps moving. The same is true of routine dev commands (git status, grep, find, your stack’s build/test verbs) — they run without asking only when the CLI’s own mode or rules permit it.

If you would rather not answer those prompts at all, you can pre-approve git-paw’s helper calls in the agent CLI’s own allow rules:

git paw setup-cli <CLI> --allowlist --dry-run   # see exactly what it writes
git paw setup-cli <CLI> --allowlist             # apply it

This is optional and consent-only: you run it, it prints its plan before writing anything, and git paw setup-cli <CLI> --undo takes back exactly what it added. No launch path, no drive loop and no MCP tool ever runs it. A call that runs without a prompt still does so only through the agent CLI’s own permission system and the rules in its settings — git-paw makes no safety decision about any command. See Per-CLI Setup for the grant table, the protection rules that come with it, and the residual risks.

Boot Block Injection Modes

  • Supervisor Mode: Boot block is prepended to each agent’s task prompt, then submitted (injected, then a separate Enter after a settle delay)
  • Spec-driven, non-supervisor mode (--specs / --from-all-specs without --supervisor): the boot block (when the broker is enabled) is prepended to the task prompt the same way, and submitted the same way — the task comes from the spec, not a human, so the combined prompt cannot be left for someone to complete and submit by hand
  • Bare, human-driven mode (git paw start with no spec selection): Boot block is pre-filled into each agent pane’s input line and left unsubmitted (user pastes their task after the boot instructions, then submits both together)

Both submitting modes end the injected prompt with the worktree rule. When the agent’s spec format declares a workflow entry point, a second line follows as its own message once the pane goes idle — see The workflow entry point.

The worktree rule

Every prompt git-paw injects into a coding agent pane at launch ends with a bundled worktree rule naming that agent’s absolute worktree root. It is appended by every injecting path — the bare pre-fill (which is still left unsubmitted, with no Enter), spec-driven and supervised start, git paw add, and a prompt held for an agent added while the session was paused — so no path can forget it. The rule is placed last, for recency: it is the constraint the agent should still be holding when it starts editing.

The rendered text, with the pane’s own recorded worktree path substituted:

Your worktree root is <worktree root>. Every file you create or edit SHALL lie under that directory. Never write outside it — not by an absolute path, and not by a relative path that climbs out of it (such as ../).

The ../ clause is the point. Under child placement the repository’s main checkout is an ancestor of every worktree, so a relative path that climbs far enough reaches it — and an agent told only to “use relative paths” can edit the main checkout without ever typing an absolute path. Naming the root makes the boundary something the agent can check a path against.

Like the rest of the boot prompt, the rule is instruction, not enforcement. What actually stops an out-of-worktree write is the agent CLI’s own outside-directory permission prompt and, if you have configured one, the FS-scoped sandbox — see Memory Isolation below. The template is project-agnostic: it names no tool, CLI or project, only the path.

Paste Handling

The boot block includes instructions for proper paste handling, particularly the requirement to send an additional Enter key after paste operations to ensure full content processing.

Benefits

  • Reliable Monitoring: Agents self-report immediately on boot
  • Consistent Behavior: All agents follow the same coordination pattern
  • One rule covers every boot call: the boot block invokes the bundled broker.sh helper at one stable path, so a single permission rule in your agent CLI can allow it — rather than a broad curl grant. Whether it prompts at all is that CLI’s decision; if it does, the prompt is escalated to you, never auto-answered
  • Supervisor Visibility: Questions and blockers surface to the dashboard promptly
  • Audit Trail: All boot operations are logged in the broker log

Worktree Environment

A fresh worktree surfaces a few conditions that look broken but are actually expected policy. The bundled coordination skill briefs agents to recognise them and adapt rather than spend budget diagnosing a non-bug:

  • Gitignored install artifacts. Per-stack install artifacts (dependency directories, build/restore caches) are absent from a fresh checkout by design and may already be provisioned by an operator-configured worktree on_create hook before the agent’s CLI starts. Agents are told not to reach for the repo-root copy or symlink it in, and to run their stack’s install step themselves only when the artifacts are genuinely missing.
  • FS-confinement is policy, not a fault. When a worktree is FS-confined by an operator-configured sandbox, an Operation not permitted on a path outside the worktree is the sandbox working as intended. Agents are told to adapt rather than probe it (xattr, id, ls -lO@, write-probes) and to keep working inside their worktree.
  • Setuid binaries under a sandbox. Some setuid-root system binaries — ps is the canonical example — cannot exec inside a sandbox. This is expected and not fixable, and does not indicate anything else is wrong.

This orientation is stack-agnostic: it never names a specific package-manager invocation, referring instead to “the stack’s install step.”

Memory Isolation

Coding agents are briefed (via the bundled coordination skill) that their worktree is their entire writable territory:

  • Persistent artifacts live in the worktree. Memory files, notes, scratch state, and any configuration an agent generates for itself SHALL be created inside the agent’s own worktree — never elsewhere on the machine.
  • Operator configuration directories are off-limits for writes. Home-level CLI configuration directories (and the per-project memory subtrees beneath them) and the host repository’s .claude/ and .git-paw/ control directories belong to the operator. Writing there mutates state shared across sessions and agents.
  • Question instead of write. When a task appears to require writing outside the worktree, the agent publishes agent.question describing the path and waits for an answer rather than writing.

The agent is told the same boundary twice, on purpose: once here, by the bundled coordination skill it reads from its sidecar, and once at the end of its very first prompt, by the worktree rule — which names the absolute path rather than leaving the agent to infer it.

This boundary is carried by doctrine, not by a mechanical check in git-paw: a write targeting operator territory raises the CLI’s own permission prompt, which is escalated to a human and never answered automatically (see Agent permissions). For mechanical enforcement, add deny rules in the agent CLI’s own settings or use the sandbox. The supervisor treats an observed out-of-worktree write attempt as a boundary violation with scoped feedback and repeat-escalation (see Out-of-worktree write violations). The guidance closes the remaining gap: persistence paths that never raise a prompt are governed by the briefing itself.

Message Types

Most broker messages use the same JSON envelope:

{
  "type": "agent.<variant>",
  "agent_id": "<slug>",
  "payload": { ... }
}

<slug> is the agent’s slugified branch name (lowercase alphanumeric + - only; slashes and underscores from a branch name like feat/auth or feat/my_auth become hyphens — feat-auth, feat-my-auth). Eight variants use this envelope: agent.status, agent.artifact, agent.blocked, agent.intent, agent.question, agent.feedback, agent.answer, and agent.verified. Three more do not: agent.advanced-main carries its fields flat alongside type (see Advanced Main); agent.learning, published by the learnings aggregator, carries its agent_id inside payload; and supervisor.verify-now is emitted by the broker itself — carrying only the committing branch_id — to the supervisor inbox whenever an agent.artifact { status: "committed" } arrives and [supervisor] verify_on_commit_nudge is on (the default). src/broker/messages.rs is the source of truth for the payload schemas.

agent.status and agent.artifact are normally automatic. The filesystem watcher publishes agent.status (with modified_files) whenever a worktree’s git status --porcelain output changes, and the post-commit git hook publishes agent.artifact with status: "committed" and the committed file list every time an agent commits. The manual curl examples below are escape hatches for cases where the automatic publishers do not apply (e.g. code-less tasks or heartbeat injection during read-only investigation).

Status

An agent reports what it is currently doing along with any files it has already modified in this work step.

curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.status","agent_id":"feat-auth","payload":{"status":"working","modified_files":["src/auth.rs"],"message":"implementing login endpoint"}}'

Artifact

An agent shares the result of a commit (or the analogous output of a code-less task) so peers can see exports and modified files. The modified_files array is what the conflict detector watches for in-flight overlap.

curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.artifact","agent_id":"feat-auth","payload":{"status":"committed","exports":["AuthClient"],"modified_files":["src/auth.rs","src/auth/client.rs"]}}'

Blocked

An agent declares that it is waiting on something specific from another agent (or external resource). from names the agent that can unblock it.

curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.blocked","agent_id":"feat-api","payload":{"needs":"auth token format","from":"feat-auth"}}'

Intent

An agent declares which files it plans to modify before any edit lands. The broker conflict detector reads agent.intent to flag forward conflicts when two agents target overlapping paths. valid_for_seconds is the TTL after which consumers MAY treat the intent as stale.

curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.intent","agent_id":"feat-auth","payload":{"files":["src/auth.rs","src/auth/client.rs"],"summary":"wire AuthClient","valid_for_seconds":900}}'

Each files entry may also be an object declaring the regions within a file the agent intends to touch — see Declaring regions for the region-level granularity added in v0.6.0.

Question

An agent escalates an uncertainty to the supervisor inbox. The asking agent blocks at its prompt until a typed reply arrives.

curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.question","agent_id":"feat-auth","payload":{"question":"Should the JWT use RS256 or HS256 signing?"}}'

Feedback

A supervisor (or the broker’s auto-emitted [conflict-detector] voice) sends a list of error messages to a target agent. The target agent_id field on the envelope is the receiver; from inside the payload is the sender.

Feedback from supervisor is a verdict: the broker refuses it without the session’s authority credential (403 {"error":"authority required"}), so the supervisor publishes it through the helper, which attaches the credential:

.git-paw/scripts/sweep.sh feedback-gate feat-auth testing "test for HS256 path is failing"

The wire body it sends:

{"type":"agent.feedback","agent_id":"feat-auth","payload":{"from":"supervisor","errors":["[testing] test for HS256 path is failing"]}}

Answer

A supervisor replies to an asking agent’s agent.question with a non-error answer. Like feedback, the envelope agent_id is the receiver (the agent being answered) and from inside the payload is the sender; the message routes to the target agent’s inbox only. answer carries the reply text and the optional re field is a short reference to the question being answered (omitted when absent). An answer is authoritative guidance to act on — it is not an error report, so the receiving agent has nothing to fix and nothing to re-publish. Reserve agent.feedback for corrective errors.

curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.answer","agent_id":"feat-auth","payload":{"from":"supervisor","answer":"Use RS256; the key rotation tooling assumes it","re":"RS256 or HS256?"}}'

Verified

A supervisor confirms that an agent’s work has passed every verification gate. The agent_id is the agent whose work was verified; verified_by names the verifier. Every agent.verified is a verdict, refused without the session’s authority credential, so it is published through the root-side helper:

.git-paw/scripts/sweep.sh verified feat-auth "all five gates pass"

The wire body it sends:

{"type":"agent.verified","agent_id":"feat-auth","payload":{"verified_by":"supervisor","message":"all five gates pass"}}

Advanced Main

The supervisor publishes this after every successful merge to the default branch so dependent agents learn the base moved. The payload fields are flat (alongside type, not nested under payload): from, merged_branch, new_main_sha, base (the resolved default-branch name), merged_at, and an optional summary.

curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.advanced-main","from":"supervisor","merged_branch":"feat/auth","new_main_sha":"a1b2c3d4e5f6","base":"main","merged_at":"2026-06-04T13:30:00Z","summary":"landed AuthClient"}'

The broker rejects a publish missing any required field (merged_branch, new_main_sha, base, merged_at) with a 400-class error naming the field. Each event carries a deterministic id derived from merged_branch + new_main_sha + base + UTC-hour-bucket, so re-publishing the same merge within the hour dedups to one logical event.

When You Depend on a Peer’s Work

When a peer’s agent.artifact lists work an agent depends on, the bundled coordination skill tells the agent to treat it as a signal, not an instruction to graft the peer’s commit: publish agent.blocked naming the peer and what it needs, then continue on unblocked work (or wait if none remains). Agents must not cherry-pick a peer’s commit into their own branch — the supervisor later merges that commit from the peer’s own branch, so the grafted copy leaves the dependent branch divergent and unmergeable. Peer work arrives through the base branch instead, via the agent.advanced-main discipline below.

When Main Advances

An agent.advanced-main event arrives on your normal /messages/<agent_id> poll — no special subscription is needed. When the base it names is one your branch depends on, react deliberately, never automatically:

  1. git fetch origin <base> to bring the new SHA local.
  2. git log HEAD..origin/<base> --oneline to see exactly what landed.
  3. Decide between rebase, merge, or wait based on what changed and the state of your working set.

Agents do not auto-rebase on receipt: rebasing rewrites history and can conflict with in-flight work, so it always requires judgment. If you do rebase, commit or deliberately stash your work first so a rebase conflict can never wipe uncommitted edits. (The bundled coordination skill teaches this discipline to coding agents directly.)

Separately, when the operator sets [supervisor] branch_refresh = true, the supervisor’s unattended drive loop may itself rebase an idle, clean worker branch onto the default branch after a merge. The worker is then told its HEAD moved by an agent.feedback tagged [branch-refresh] — never by agent.advanced-main. See Supervisor → Branch refresh.

Polling for Messages

Agents poll for messages from other agents using cursor-based pagination. The since parameter is a sequence number – the broker returns only messages with a sequence greater than the given value.

# First poll -- get all messages
curl -s "$GIT_PAW_BROKER_URL/messages/feat-auth?since=0"

The response includes a last_seq field. Pass this value as since on the next poll to get only new messages:

# Subsequent poll -- only new messages since last check
curl -s "$GIT_PAW_BROKER_URL/messages/feat-auth?since=42"

This cursor-based approach is lossless – no messages are missed between polls, regardless of timing.

Agents can use the bundled helper instead of a raw curl for the same read — .git-paw/scripts/broker.sh poll [since] issues GET /messages/<agent-id>?since=<n> and prints the returned messages (see Broker helper).

Waiting for Events

/messages/<agent_id> is one agent’s inbox. GET /log is the whole session’s message log, and it is the read surface for anything that watches the session as a whole — a supervisor, an external orchestrator, a monitoring script.

curl -s "$GIT_PAW_BROKER_URL/log?since=0"

The response is {"entries": [...], "last_seq": N}. since is the same cursor the inbox poll uses: the broker returns only entries whose sequence is greater than the value you pass, and last_seq is the cursor to send next. An empty response reports last_seq equal to the since you sent, so a reader’s cursor never rewinds and never has to re-read the log from the start. (Before v0.18 an empty response reported 0, which rewound every reader that trusted it — this is the one behaviour change in the response.)

The broker epoch — reset your cursor when it changes

Sequence numbers are per-broker and restart at zero every time a broker starts, which a git paw pause + git paw start cycle does. Because a cursor never rewinds, a reader that kept its old cursor across a restart would sit above everything the new broker appends: it would see nothing, forever, and time out every long poll while the session worked normally around it.

Every GET /log response therefore carries an epoch identifying the running broker instance (its start time in Unix milliseconds), and so do sweep.sh events and wait_for_events:

{ "entries": [ ... ], "last_seq": 57, "epoch": 1761500000123, "wave": { ... } }

Record the epoch alongside your cursor, and whenever it differs from the one you recorded, send since=0 on your next request — the entries you have already seen belong to a broker that no longer exists, so re-reading from the start is correct and happens exactly once per restart. The first epoch you see needs no reset; there is no earlier cursor to invalidate. git-paw’s own in-tree readers (the drive loop’s observer, the MCP tool) already do this.

The long poll

Add wait=<seconds> and the broker holds the request open until there is something to return:

# Block for up to 30s, or return the moment an entry newer than 42 is appended
curl -s "$GIT_PAW_BROKER_URL/log?since=42&wait=30"
  • wait is whole seconds and is capped at 60. A larger value is clamped; a non-numeric value is rejected with HTTP 400.
  • With wait and nothing newer than since, the request is held until an entry is appended or the wait elapses — whichever comes first. A wait that elapses with nothing new returns {"entries": [], "last_seq": <since>}.
  • Without wait, the request returns immediately, exactly as before.

This is what makes an external reader event-driven rather than capture-diff polling. A reader that samples pane output on a timer silently misses anything that starts and finishes between two samples; a reader that blocks on the cursor sees every appended entry exactly once, whatever the timing.

The bundled helper verb

The supervisor-side helper wraps both reads in one call:

.git-paw/scripts/sweep.sh events <since> [wait]

wait defaults to 30 seconds. The verb calls GET /log?since=&wait= and GET /status, and prints one JSON object to stdout:

{ "entries": [ ... ], "last_seq": 57, "epoch": 1761500000123, "wave": { ... } }

It is strictly a read: it sends no keystroke and publishes nothing. With no reachable broker it prints an object carrying "no_session": true and exits 0, so a caller never has to distinguish a crash from a session that is not running. It is granted read-only by git paw setup-cli <CLI> --allowlist.

An MCP-speaking client reaches the same surface through the wait_for_events tool.

Checking Overall Status

The /status endpoint returns a summary of all agents and their latest state:

curl -s "$GIT_PAW_BROKER_URL/status"

Wave staleness

Per-agent heartbeats answer “is this agent alive?” They do not answer “is this wave getting anywhere?” A session can sit on an unanswered permission prompt for days with every pane alive and every agent reporting working. So /status also carries a wave object describing the session as a whole:

"wave": {
  "last_progress_unix_secs": 1761500000,
  "idle_seconds": 11520,
  "stall_threshold_seconds": 1800,
  "stalled": true
}

What counts as progress is deliberately narrow — exactly two things:

  • A wave phase advances. An agent.status whose status or phase differs from that agent’s previously recorded values, or any agent.artifact, agent.verified, agent.feedback, or agent.advanced-main.
  • A branch tip moves. The broker’s filesystem watcher sees an agent worktree’s HEAD commit change.

What does not count, however much of it arrives:

  • a repeated identical agent.status — whatever its modified_files set;
  • uncommitted file churn;
  • agent.question, including the unattended drive loop’s escalation of a live permission prompt and its re-escalation of the same prompt;
  • agent.learning, agent.blocked, agent.intent, agent.answer, and the broker’s own supervisor.verify-now nudge;
  • an agent.status carrying one of the stuck-detection helper’s synthetic alert phases — stuck-on-prompt, stuck-stream-timeout, context-bloat, no-progress or blocked-on-supervisor. Each is a report about a wave that is not moving, so counting it would let the detection of a stall reset the stall clock; no-progress says as much in its own name. A status moving an agent back out of one of those phases is an ordinary phase advance and does count.

That exclusion list is the point: those are precisely the messages that keep arriving while a wave sits on an unanswered prompt, so counting them would make the signal report healthy exactly when it matters most.

A broker that has accepted no message at all reports its own start time as the last progress. stalled is true when the threshold is non-zero and idle_seconds exceeds it.

The threshold is the [broker] wave_stall_minutes key — default 30 minutes, 0 to disable the flag while still reporting idle_seconds. See the configuration reference. Thirty minutes sits just above the unattended drive loop’s 25-minute heartbeat, so a wave that is merely between heartbeats is not reported as stalled.

The same object is surfaced by git paw status (human and --json), the dashboard status line, the MCP tools get_session_status and wait_for_events, and sweep.sh events — so a waiter whose long poll times out with no entries still learns that the wave has stalled.

Multi-Repo Considerations

Each git-paw session runs its own broker. If you have multiple repos running sessions simultaneously, each needs a unique port:

# In repo-a/.git-paw/config.toml
[broker]
enabled = true
port = 9119

# In repo-b/.git-paw/config.toml
[broker]
enabled = true
port = 9120

git-paw enforces this: a broker reports the repository it serves on GET /status, and a session that finds another repository’s broker on its port is refused with an error naming that repository and telling you to set a distinct [broker] port. See Multi-repo port assignment.

The default port is 9119. The broker binds to 127.0.0.1 (localhost only) by default — the [broker] bind setting — and should never be exposed to the network.

Automatic Conflict Detection (v0.5.0)

When supervisor mode is active, the broker runs an in-process conflict detector that auto-emits agent.feedback (tagged [conflict-detector] in the errors array) and, for unresolved in-flight conflicts, escalates to the supervisor inbox via agent.question. Three failure shapes are detected:

  • Forward conflicts — two agents publish agent.intent with overlapping files. Both receive agent.feedback listing the other agent and the overlapping paths. Toggle with [supervisor.conflict] warn_on_intent_overlap.
  • In-flight conflicts — two agents’ agent.status modified_files sets overlap on the same file. Both receive agent.feedback. The broker waits [supervisor.conflict] window_seconds (default 120) for one side to stop touching the file; if both keep modifying it, the detector escalates a true collision to the supervisor inbox via agent.question (an overlap whose declared regions are disjoint is downgraded to an informational agent.feedback instead).
  • Ownership violations — an agent’s modified_files includes a file outside its own active agent.intent and inside another agent’s active intent. The violator receives agent.feedback; if [supervisor.conflict] escalate_on_violation = true the supervisor inbox also receives a follow-up agent.question. The shared spec tasks.md every agent ticks its own line in is exempt.

See the Conflict Detection chapter for the full walkthrough — failure shapes, the [conflict-detector] tag, supervisor inbox routing, and the configuration knobs.

Audit Trail

The broker appends every message to a <session_name>.broker.log file in git-paw’s session-state directory (~/Library/Application Support/git-paw/sessions/ on macOS, $XDG_DATA_HOME/git-paw/sessions/ — default ~/.local/share/git-paw/sessions/ — on Linux), outside the repository. Each line is plain text — [<seq>] <unix-seconds> [<agent_id>] <message summary> — and the file is flushed every 5 seconds, giving an audit trail of agent communication. For the full JSON of every message, query the broker’s GET /log endpoint (see Waiting for Events).

The log file is automatically cleaned up by git paw purge.

Working Heartbeat

The broker’s filesystem watcher publishes agent.status whenever a worktree’s git status changes, which keeps the dashboard’s last_seen timestamp fresh during active editing. The watcher cannot observe read-only tool uses (file reads, greps, searches), permission-prompt waits, or LLM-only deliberation between tool calls — so a long read-heavy investigation looks stuck on the dashboard even though the agent is making progress.

To bridge that gap, the embedded coordination skill instructs agents to publish a lightweight agent.status heartbeat every 5 tool uses while actively working, through the bundled broker helper, which publishes {"status":"working","message":"<text>","modified_files":[]}:

.git-paw/scripts/broker.sh --agent feat-auth status "reviewing auth tests"

The heartbeat reuses the existing agent.status shape — no new wire format is introduced. The broker merges heartbeats with watcher-driven updates without conflict.

Activity phrases

A status payload may also carry an optional activity field — a short phrase naming what the agent is doing right now:

curl -s -X POST http://127.0.0.1:9119/publish \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.status","agent_id":"feat-auth","payload":{"status":"working","activity":"running the test suite","modified_files":[]}}'

The field is optional and backward compatible: a payload that omits it deserializes and re-serializes exactly as before, so existing agents and stored messages are unaffected. When present, the dashboard’s Broker log panel renders the phrase alongside the status. Because the panel also collapses consecutive identical heartbeats, a phrase that changes as the work moves along keeps each phase on its own row.

Prefer Native Read Tools Over Shell Pipelines

The bundled coordination skill teaches agents to prefer their CLI’s own native file-read and search facilities over a shell text-extraction pipeline (a command that pipes output through line-slicing or field-extracting utilities) when inspecting source. Two costs motivate this:

  • The approval stall — a shell pipeline typically requires a permission approval before it runs, so the agent waits on a prompt every time.
  • The dead grant — a durable approval is keyed on the exact command string, so the next inspection (a different line range, a different search term) never matches it, and the allowlist accumulates entries that exist only to be ignored.

The same mechanic governs scratch files: when an agent captures a command’s output across repeated iterations, the skill tells it to reuse one scratch path rather than writing a fresh numbered file each time, since a durable approval keyed on a path does not carry over to a new filename.

The guidance is deliberately CLI-agnostic — it names no specific CLI, tool, or vendor — so it holds for any CLI git-paw launches.

Commit Cadence

The bundled coordination skill teaches a per-group commit cadence. When a change has an OpenSpec-style tasks.md with numbered groups (## 1., ## 2., …), the agent commits after every - [ ] item in a group is - [x] — one group, one commit (by default) — before starting the next group.

The skill bounds uncommitted work to roughly ten files at a time. If a single group exceeds that mid-implementation, the agent splits into multiple commits using a (part N of M) suffix:

close per-scenario coverage gaps (part 1 of 2)
close per-scenario coverage gaps (part 2 of 2)

Each commit must be a releasable unit — it builds and passes its own gates on its own, not a checkpoint of half-finished work. When the agent needs to fix the commit it just made (a typo, a missed file) and that commit has not yet been verified, the skill tells it to fold the fix in with git commit --amend rather than land a separate fix typo micro-commit. It must not --amend an already-verified commit or an earlier group’s commit — --amend applies only to the most-recent, not-yet-verified commit. This keeps the history as clean, verifiable units and avoids the manual release-time squashes earlier cycles needed (148→10 in v0.6.0, 4→1 in v0.7.0).

The bundled skill does not mandate, default to, or recommend a commit-message format. Message format is a per-project convention, so the skill defers entirely to the host project’s injected AGENTS.md (subject style, any prefix or scope convention, any “no AI-assistant trailer” rule), and any commit example it shows — like the (part N of M) split above — uses a format-neutral subject. git-paw’s own repo uses Conventional Commits, but only via git-paw’s own AGENTS.md, never via the skill the binary exports to other projects.

Per-group cadence protects against agent crashes, conflict mediation, and /clear resets losing unbounded work, and it maps cleanly to the post-commit hook’s agent.artifact { status: "committed" } event sequence the supervisor consumes during verification.

Terminal Action — Commit Then Publish, Never Archive

The bundled coordination skill defines the coding agent’s terminal action as:

  1. A commit. The post-commit git hook auto-publishes agent.artifact { status: "committed" } with the committed file list. For code changes this is the canonical “done” signal.
  2. A manual agent.artifact { status: "done" } (rare). Used only for code-less tasks or to announce named exports peers depend on.

The skill is explicit that the coding agent SHALL NOT invoke /opsx:verify <change-id> or /opsx:archive <change-id> — both are off-limits for the coding agent and are the supervisor’s job:

  • Verification runs the supervisor’s five-gate framework (testing → regression → spec audit → doc audit → security audit) against the committed branch. Self-verification by the coding agent bypasses gates and produces a premature agent.verified the supervisor never reviewed.
  • Archiving happens on the release branch during the supervisor’s cherry-pick
    • merge flow, not on the agent’s feature branch. Archiving from a feature branch leaves the change directory deleted on an unmerged branch and produces confused history.

The skill frames this positively as a stand-by-after-commit protocol: once the final commit lands, the agent publishes the terminal signal and then waits — it does not reach for verify/archive. While standing by it waits for one of three supervisor messages: agent.verified (work passed — pick up the next task), agent.feedback (fix the listed errors and re-publish agent.artifact), or a further agent.intent (new scope to pick up). This is the actionable counterpart to the role-gating forbidden-commands rule — what to do instead of self-verifying. On the supervisor side, that post-commit agent.artifact is the cue for the supervisor (not the agent) to run /opsx:verify and /opsx:archive.

This is a paw-specific rule for the bundled coordination skill. Single-agent workflows that self-verify can override the rule via the standard skill resolution chain (a user override at <config_dir>/git-paw/agent-skills/coordination.md wins over the bundled default).

Identifier Forms — Branch vs agent_id

Two related forms of an agent identifier appear throughout the broker protocol:

  • Branch name — the original git ref (e.g. feat/no-supervisor-flag). Used in git checkout, git worktree, git push, and any other git command.
  • agent_id — the dashed slug form (e.g. feat-no-supervisor-flag). Used in every /publish envelope’s agent_id, every /messages/<id> URL, and payload fields that name another agent, such as from in agent.blocked.

agent_id is the slugified form of the branch name. The conversion (named slugify_branch in the source) lowercases the input, replaces every character outside [a-z0-9] with - — underscores included — collapses runs of -, trims leading and trailing -, falls back to the literal agent-unnamed if the result is empty, and prefixes a single-word result with agent- (hotfix → agent-hotfix). Every output therefore matches the agent_id pattern /publish enforces, and no branch can derive the reserved id supervisor.

A branch name may keep _; its agent_id never does. Branch names git-paw derives from spec text keep underscores — a Spec Kit task naming ROOM_SETUP.md produces the branch task/t001-create-room_setup-md — because _ is legal in a git ref and the branch is durable state that recovery and --resume use to look up work. Only the broker’s id grammar forbids _, so the mapping happens at the id boundary: that branch publishes as task-t001-create-room-setup-md.

Match the form to the context: dashed agent_id in any JSON going to or coming from the broker; slashed branch name in any shell command involving git.

Stash Hygiene in Worktrees

When multiple worktrees run side-by-side, every worktree shares the same underlying git stash list. A git stash pop invoked without inspection can pop an entry created by a different worktree, conflict with your in-progress changes, and wipe work. The embedded coordination skill teaches agents three rules:

  1. List before pop — git stash list first; inspect every entry’s branch label and timestamp.
  2. Inspect before pop — git stash show -p stash@{N} to read the patch contents of the specific entry before popping.
  3. Pop only your own — only pop entries you authored on the current worktree. If authorship is uncertain, leave the stash alone and escalate via agent.question.

Blind git stash pop is a data-loss pattern in a multi-worktree session and is not recommended.

Supervisor Acknowledgement of agent.question

When an agent publishes agent.question, it blocks at its prompt waiting for a typed reply. A blocked agent does not poll its inbox for agent.answer responses, so a supervisor that only publishes agent.answer to the broker will see its answer recorded on the dashboard while the asking agent stays blocked indefinitely.

The supervisor skill therefore instructs supervisors (both human and LLM) to both publish agent.answer (the non-error reply shape — agent.feedback stays reserved for corrective errors) and send the answer text to the asking agent’s tmux pane via tmux send-keys. The MCP publish tools include no inbox read, so the dual write applies to agents booted with the MCP tool form too.

Spec Kit Consolidated Worktrees

When git-paw drives a Spec Kit project (.specify/specs/<feature>/), each feature’s current phase decomposes into multiple worktrees:

  • One worktree per [P]-marked task (branch prefix task/). These are parallelisable.
  • One consolidated worktree per non-[P] task group (branch prefix phase/). Non-[P] tasks share files or context, so a single agent works through them sequentially.

The embedded coordination skill picks up on the branch prefix:

  • task/<task-id>-<slug> branches: the agent runs the standard “before/while editing” coordination pattern for a single task.
  • phase/<feature>-<phase-slug> branches: the agent:
    1. Works through the listed tasks in tasks.md order.
    2. Flips - [ ] to - [x] for each completed task in the worktree’s tasks.md. The writeback can be a separate commit or bundled with the task’s code change.
    3. Publishes agent.intent for the union of files across the next 1–2 tasks (with a generous TTL) rather than one publish per task.
    4. Publishes agent.artifact with status: "done" only when every listed task shows - [x] in tasks.md. Partial completion is not “done”.

When tasks.md is the merge-conflict surface between worktrees, git’s line-level merge handles per-task checkbox flips automatically. A checkbox writeback to the shared tasks.md is never reported as an ownership violation — the conflict detector exempts that file — while forward and in-flight overlap detection still apply to it.

Workflow phases

The bundled coordination skill structures an agent’s editing work into two phases that mirror the skill’s “Before you start editing” and “While you’re editing” sections.

Before you start editing

Before touching any file, the agent:

  1. Reads the spec or task description in full to understand the scope.
  2. Publishes agent.intent listing the specific files it plans to modify, a one-line summary, and a TTL in seconds (default 900 = 15 minutes). This advertises ownership to the broker conflict detector so forward conflicts are caught before any edit lands.
  3. Polls its inbox once for warnings or overlapping peer intents — not a busy loop, a single poll.
  4. Decides on overlap: if a peer’s intent already covers the same files, the agent picks among wait (peer’s TTL is short, work is small), split (narrow the file list to avoid overlap, re-publish agent.intent with the reduced scope), or escalate (publish agent.question describing the overlap so the supervisor or human can decide). If no overlap is reported, the agent proceeds to edit immediately — there is no explicit go-ahead to wait for.

While you’re editing

Once editing is underway, the agent keeps the intent honest and asks rather than racing:

  • Re-publish intent on scope growth. If the in-progress work touches files that were not in the original agent.intent, the agent re-publishes agent.intent with the expanded files list before touching the new files. The re-published intent replaces the previous claim for downstream consumers.
  • Question on peer overlap. If a peer’s agent.intent arrives in the inbox naming a file in the same module the agent is editing, the agent sends agent.question describing the overlap and pauses edits on the contested file. Silently racing the peer to a commit is forbidden.

The agent MUST NOT:

  • Perform pairwise check-ins on every change — the broker is not a chat channel and peers are not waiting for status pings.
  • Wait for an explicit go-ahead from peers when no conflict signal exists — silence from the broker means “no overlap detected”, not “permission pending”.
  • Block on broker silence — if agent.intent polling returns no overlap, the agent proceeds.

Declaring regions (v0.6.0)

By default an intent claims whole files, so the detector warns any two agents who name the same path. When several agents collaborate on different parts of one shared file, that whole-file warning is noise — and noisy warnings get dismissed, taking real overlaps with them. From v0.6.0, each files entry MAY be an object that declares the regions the agent intends to touch, and the detector then warns only when the declared regions actually intersect.

A files array may mix bare-path strings (file-level intent, the v0.5.0 shape) and region objects freely. Four region kinds are recognised:

  • function — { "kind": "function", "name": "<symbol>" }
  • class — { "kind": "class", "name": "<symbol>" }
  • block — { "kind": "block", "anchor": "<heading or landmark>" } for prose or config files
  • range — { "kind": "range", "start_line": N, "end_line": M } when no symbolic name fits

Worked example. Two agents both intend src/auth.rs, but feat-auth is hardening validate_token while feat-session reworks refresh_session:

# feat-auth
curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.intent","agent_id":"feat-auth","payload":{"files":[{"path":"src/auth.rs","regions":[{"kind":"function","name":"validate_token"}]}],"summary":"harden token checks","valid_for_seconds":900}}'

# feat-session
curl -s -X POST "$GIT_PAW_BROKER_URL/publish" \
  -H "Content-Type: application/json" \
  -d '{"type":"agent.intent","agent_id":"feat-session","payload":{"files":[{"path":"src/auth.rs","regions":[{"kind":"function","name":"refresh_session"}]}],"summary":"rework session refresh","valid_for_seconds":900}}'

Because the declared functions differ, no forward-conflict warning fires — the two agents proceed in parallel on the same file. Had both named validate_token, the warning would fire and name the intersecting function.

Four rules govern detection:

  • Both sides must declare regions for region-level matching. If either side omits regions (a bare path string), the detector conservatively falls back to a file-level warning — the v0.5.0 safety net.
  • Name matching is normalized. Region names are compared after case-folding, trimming, collapsing separator runs (space, underscore, hyphen) to a single form, stripping a trailing (), and stripping a leading declaration keyword (fn, def, function, class) — so spelling variants of the same symbol intersect: validate_token matches Validate Token() and fn validate_token. Genuinely distinct symbols (foo_bar vs foobar) stay distinct.
  • Cross-kind comparisons are conservative. A function/class/block region compared against a range always intersects (the detector can’t resolve a symbol to line numbers without parsing source). Named regions of different kinds — say function foo vs block foo — intersect when their normalized names match, because they almost always describe the same construct through different lenses. Both shapes carry a hint noting the comparison was conservative.
  • Don’t manufacture narrow regions to dodge a warning. A region you don’t really own hides a collision that resurfaces later as a merge conflict.

Because matching is string-based, three declaration rules keep intents detector-comparable:

  • Use the canonical source spelling — declare validate_token, not a paraphrase like token validation logic; the detector cannot equate two paraphrases of the same symbol.
  • Declare all the regions the work touches, including shared constant blocks, import sections, and asset files — not only the headline function.
  • Re-publish agent.intent when scope grows mid-task, before touching a region that was never declared.

This guidance is taught to agents by the bundled coordination skill (assets/agent-skills/coordination.md, the “Declaring regions” section), which is the authoritative source.

Context budget

After the “While you’re editing” discipline, the coordination skill teaches agents to manage their own context window so they don’t hit an opaque “context length exceeded” failure mid-task and lose uncommitted work. The guidance has four parts:

  • Residual-budget heuristic. After the boot block, skill prose, and governance docs load, an agent aims to keep at least ~60% of the model’s context window free for task work. This is a heuristic target judged by the agent, not a config field.
  • Three named moments to compact / clear / summarise, in priority order: after each spec scenario completes (compact), when the working set grows past ~40% of the window (compact), and when switching between sub-tasks that don’t share state (clear).
  • Commit before you compact. Every compact / clear / summarise operation is preceded by a commit or an agent.artifact publish, so reducing context is never lossy.
  • Proactive context-bloat flagging. When an agent’s pane shows a /clear to save <N>k tokens hint with N at or above [supervisor] context_bloat_threshold_k (default 250), the supervisor flags it with a synthetic agent.status carrying phase: "context-bloat"; the agent commits (or publishes an agent.artifact) first, then clears or compacts.

The bundled coordination skill (assets/agent-skills/coordination.md, the “Context budget” section) is the authoritative source for the exact heuristics, the named moments, and the per-CLI compact/clear mechanism table (claude, claude-oss, and a generic fallback). This chapter only summarises it.

Supervisor

This chapter mirrors the user-facing prose for the bundled orchestrator skill (assets/agent-skills/orchestrator.md, exported to .git-paw/skills/orchestrator/SKILL.md; supervisor remains an alias of its name) — the doctrine the orchestrator runs against in supervisor mode. Sections here document the same rules the skill teaches, so users reading the mdBook can understand the orchestrator’s behaviour without opening the skill file directly.

For the launcher-level “how do I start supervisor mode” walkthrough, see Quick Start: Orchestrator Mode. For the broker-side message contract the supervisor exchanges with coding agents, see Agent Coordination.

Which CLI the coding agents launch with

A supervised launch given neither --branches nor a spec selection runs the same interactive selection flow as git paw start: the branch picker, the assignment-mode prompt, and the CLI picker (uniform, or one picker per branch). Each coding agent then launches with the CLI that selection mapped to its branch — the uniform choice, or that branch’s own per-branch choice. default_cli only pre-selects an entry in those pickers; it never skips them.

The session-wide fallback — --cli, else default_cli, else [supervisor].cli — applies only to launch paths that show no CLI picker, such as --branches without --cli; when none of the three is set the launch fails, naming --cli and default_cli. Spec-driven branches keep their own resolution chain (see Spec-driven launch).

Session layout: the orchestrator runs outside the session

A supervised session holds the dashboard at pane 0, as a full-width top row, and one coding agent per pane from pane 1, row-major below it (at most five per row). No pane runs an orchestrator CLI. The orchestrator is your own agent session, run outside tmux at the repository root. The launch re-renders the exported orchestrator skill (.git-paw/skills/orchestrator/SKILL.md, with the gate commands and the spec-path doctrine) and writes the session context — the session name, the broker URL, the helper paths, any [governance] documents and, under --unattended, the drive-loop directive — into the gitignored sidecar .git-paw/AGENTS.local.md, and prints both paths with the attach hint. The orchestrator reads them and drives the run through the bundled helpers (.git-paw/scripts/sweep.sh, broker.sh) or the MCP server. See Driving the session from your own agent. Its roster row (supervisor) appears on the dashboard once it publishes its own agent.status; the launcher never publishes on its behalf.

[supervisor].cli (else default_cli) names the orchestrator’s CLI in that context and is the coding agents’ last CLI fallback; no orchestrator CLI is needed to launch. It is also the CLI the --unattended drive loop runs headlessly for a judgment call (see Headless hand-off). [supervisor].approval shapes no pane command — it is the approval level of those headless invocations.

A live session started by an earlier release (orchestrator CLI at pane 0, dashboard at pane 1, agents from pane 2) keeps working until it ends: every command derives the agent panes from the session’s recorded dashboard index, and nothing types into that session’s first two panes. Recovering it rebuilds the layout above.

Driving the session from your own agent

The orchestrator is whatever agent session you already use — any agent CLI, at the repository root, outside tmux. git-paw gives it three things:

FileWritten byWhat it holds
.git-paw/skills/orchestrator/SKILL.mdgit paw init, refreshed by git paw startThe orchestrator skill (the procedure: five gates, spec audit, merge orchestration, stuck detection, routing), rendered with your [supervisor] gate commands and configured spec format. It carries no session value, so one file serves every session.
.git-paw/AGENTS.local.mdgit paw startThe session context: session name, broker URL, helper paths, the discovery file, [governance] documents, the drive-loop directive under --unattended, and a pointer to SKILL.md. The only file carrying this session’s values.
AGENTS.md pointer sectiongit paw initA short section between <!-- git-paw-orchestrator:start --> and <!-- git-paw-orchestrator:end --> telling any agent that reads your root AGENTS.md that, when a git-paw session is active, it is the orchestrator and should read the two files above. git paw stop and git paw purge leave it in place. See AGENTS.md Injection.

Both .git-paw/skills/ exports (the orchestrator skill and the cli-setup skill beside it) are in the standard skill-directory layout, so a skill-aware CLI can load them directly; copy or symlink them into a vendor skill directory if your CLI needs that. Over MCP, get_skill serves the same skill under the name orchestrator (or its alias supervisor).

The surfaces it uses.

  • Follow the session by blocking on the broker’s event cursor — the MCP wait_for_events tool or .git-paw/scripts/sweep.sh events — carrying the returned cursor forward, and inspect when an event or a stalled wave says to. get_session_status, sweep.sh status and sweep.sh inbox read the roster and the orchestrator’s inbox. A sampled pane capture can miss a prompt that was answered and replaced between two samples, so capture (sweep.sh capture <pane>) is for inspection, not detection.
  • Publish verdicts only through the credential-carrying helpers: sweep.sh verified, sweep.sh feedback-gate and sweep.sh conflict-ack (in the root session, the MCP publish_verified and publish_feedback tools do the same). The broker refuses a verdict without the session’s authority credential.
  • Answer and announce with sweep.sh answer <agent> <text> [re] and, after each merge, sweep.sh advanced-main <merged-branch> <summary>. Its own questions and main-side intents go through broker.sh --agent supervisor question|intent. The skill contains no raw HTTP request.
  • Run the session from the root session with git paw commands or the MCP start_session, pause_session, stop_session and purge_session tools.

Pairing with --unattended. git paw start --unattended adds the in-process drive loop: it keeps the panes moving and escalates every live permission prompt to the broker, approving none. The orchestrator reads those escalations first each cycle, relays each prompt to you in its own session, and runs sweep.sh approve <pane> only on your explicit instruction. It merges as soon as every expected agent is verified and escalates only on a conflict, an exhausted correction cycle or a regression (see Merge orchestration). If the loop exits on its heartbeat, re-run git paw start --unattended to re-attach a drive loop to the live session. The driving session can itself be remote-controlled from a phone — see Remote Control.

Unattended mode (--unattended)

git paw start --unattended runs a supervisor wave to completion with no human in the seat. Without it, git paw start --supervisor builds the tmux session and returns immediately with an attach hint, expecting a human (or a throwaway pane-scraper script) to watch the panes, answer permission prompts, and notice when the work is done. --unattended folds the driving half of that operator loop into the tool itself.

--unattended engages supervisor mode (you do not also need --supervisor), then — instead of returning — runs an in-process drive loop in the foreground git paw start process. It is designed for detached operation: it does not attach a terminal and does not replace your shell with an interactive supervisor CLI. It is mutually exclusive with --no-supervisor.

--unattended approves nothing. The loop detects live permission prompts and escalates every one of them for a human decision; it never answers one. For a run that genuinely does not stop for prompts, the decision has to be made before the prompt exists — in your agent CLI’s own allow rules, or by launching the agents at [supervisor] agent_approval = "full-auto" inside the documented sandbox. See Agent permissions.

Each poll (~15 seconds) the loop:

  • Sweeps every coding-agent pane — and only those. It enumerates panes and resolves each to its agent by pane_current_path (never by pane index, which drifts), capturing each coding pane with its own capture-pane call. The dashboard, and any pane no agent worktree owns, is never captured, escalated or typed into.
  • Acts only on a live prompt — the prompt’s structural markers (numbered option glyphs and/or the Do you want to … question, with the Esc to cancel footer) anchored on the modal block itself, searching upward from the end of the capture and tolerating up to 13 non-blank trailing lines of non-prompt content below it (a task list, a status strip, a progress footer), so a CLI that renders chrome under its input area doesn’t hide a live modal — and so a prompt offering a “don’t ask again” option is detected too. Beyond that true end, liveness requires the modal’s actual shape (the footer anchoring a line, with an option list nearby), not a bare mention of the footer text in narration or tool output. Prompt-like text scrolled well past that bound is ignored.
  • Escalates every live prompt, whatever the command is. The loop makes no judgement about the prompted command — ls -la and rm -rf / are handled identically. The prompt is recorded on the broker as an agent.question review item (from supervisor), written to the manual-decision log, captured as a learning, and listed in the exit summary. No keystroke is sent, to that pane or any other: the orchestrator outside the session reads the review item and relays it to the human.
  • Submits a directive left buffered on an idle pane. A pane showing no live prompt and no mid-response marker, whose input box still holds unsubmitted text, has a stranded directive: the CLI swallowed the first Enter into its paste buffer. The loop sends a follow-up Enter first; if the text survives that, it runs the same clear-and-recover sequence nudges use (see below), re-typing the text read from the whole input box. A paste-buffer placeholder is never cleared — it is escalated instead. Neither the live-prompt scan (there is no prompt) nor the ~25-minute stuck detector would otherwise surface that stall. A pane that is actively generating is never touched.
  • Keeps the wave moving while a prompt waits. An escalation is non-blocking: one agent parked on a prompt never freezes the others, and the loop does not wait for anyone to answer. Repeated escalations are deduplicated per (agent, command-shape) within a five-minute window, so a prompt still unanswered after that window is re-escalated as a reminder rather than every fifteen seconds. A prompt nobody ever answers does not, on its own, end the run — the wave ends on completion or the heartbeat.
  • Leaves judgment calls on the broker — see The loop is a pump, the supervisor is the brain — and hands a question, a merge decision or a correction-exhausted branch to a headless one-shot orchestrator when one resolves.
  • Re-engages a gate-failed worker, when [supervisor.correction] auto_loopback is enabled — closing the verify↔fix cycle without a human hop.

The loop keeps polling until one of:

  1. Completion — a terminal PASS/FAIL verdict is signalled, or every agent’s tasks are checked complete.
  2. Stuck / bloat — the stuck-detection signal fires.
  3. Heartbeat — about 25 minutes pass without progress, so you are re-engaged with a status summary rather than the loop running forever. Progress is a coding agent’s branch tip moving (a new commit) or its phase advancing (e.g. working → committed → verified/done); the interval is measured from the last progress the loop saw, not from launch. Nothing the loop does itself counts — its escalations, its reminder re-escalations of an unanswered prompt, the learnings it records, its own rebases of a branch after a merge — and neither does changed pane output or a repeated working status. So a wave that keeps committing is never cut off at 25 minutes, while a wave whose agents all sit on unanswered prompts still exits one interval after its last progress. Before exiting, the loop takes one fresh status look, so a done published during a long tick still ends the run as completed.
  4. Session torn down — the loop’s own session instance is purged, stopped, or replaced (see Session-instance lifecycle below).

On exit it prints a summary: the outcome (completed / escalated-for-review / stuck / heartbeat / session-torn-down), each agent’s resolved final state and its merged/unmerged status, the deduped list of prompts escalated for human review, a line stating that the orchestrator runs outside the session and its CLI is not monitored by the loop, and pointers to the broker log and captured learnings. Because there is no human to hand-write findings, the loop self-captures qualitative learnings during the run and once more at wind-down.

A heartbeat, stuck or interrupted exit ends the loop, not the session — the panes, their conversations and their worktrees are all still there. Re-run the same git paw start --unattended to drive it again; see Re-attaching a drive loop to a live session.

Each agent’s final state is resolved from the most-progressed lifecycle phase ever observed for it during the run — not simply the last /status row — so a generic working heartbeat published after a verified/done artifact event can never make a finished worker look unfinished in the summary.

The merged/unmerged roll-up is ref-derived, never inferred from a worker’s reported status. A worker reaching verified/done says nothing about whether its branch ever reached the default branch — reporting a wave completed on the strength of worker status alone was the failure mode a 0.16.0 external dogfood surfaced: the summary read completed, 2/2 branches merged, zero escalations, while main was unchanged and both branches were still ahead. At exit the loop asks git directly, once per coding branch, whether the branch tip is an ancestor of the default branch (git merge-base --is-ancestor); a comparison git cannot make (a deleted branch, a bad ref) counts as unmerged — the fail-safe direction, since this summary is the input to a destructive git paw purge. The wave-level roll-up line (e.g. 2/2 branches merged) and each agent’s own line (<agent>: <phase> · merged / <agent>: <phase> · unmerged (N ahead of <default>, tip <sha>)) both come from this same ref check, and print for every outcome with coding agents — a heartbeat exit gets the same truth, not only a completed one. completed now implies every branch is merged. A wave that finishes with any branch unmerged reads escalated-for-review instead, with one [unmerged] escalation entry per unmerged branch, so “Escalations awaiting human review” can no longer read 0 while work sits un-integrated — check this list, not just the outcome word, before running purge.

CLI unavailability

A rate-limited or logged-out coding agent is an explicit non-success signal — not a silent idle pane the loop waits out for up to 25 minutes. Each poll, every coding-agent pane’s capture tail is also scanned for a CLI-unavailability marker (a usage/session-limit phrase, an organisation spend-limit phrase, or a login-loss phrase such as an expired/missing credential). A match becomes a deduped cli-unavailable escalation that quotes the matching line, and the loop keeps driving the rest of the wave; a wave carrying one is never reported completed, and that worker’s branch shows as unmerged in the exit summary regardless. Text on the dashboard — whose broker log can quote a worker’s banner — is never scanned.

The scan tolerates the same bounded trailing region of non-prompt content the live-prompt gate does, so a banner is still found underneath chrome a CLI draws below it — current Claude Code’s persistent task list, a status strip, or similar — rather than only a fixed last few lines. A marker separated from the end of the capture by more than that bound still counts as scrolled away and is ignored, same as before.

Markers are resolved per pane, for its own CLI: a [clis.<name>] override of cli_unavailable_markers applies to that CLI’s panes, falling back to the embedded Claude Code default (which includes You've hit your session limit, an organisation org's monthly spend limit, an expired/missing login, and an invalid API key) when the CLI has no override. The override resolves from that pane’s own broker-reported CLI (the /status snapshot), so it only takes effect once that pane has published at least one status — a pane’s first sweep of the run, before any /status row exists for it, always falls back to the embedded default for that one tick. git paw __prompt-profile --field cli_unavailable_markers reads the exact same resolved field the loop does — it is a read path for the bundled sweep.sh helper’s other detectors (stuck-shape, live-prompt) to stay in sync with the Rust side, not a second detector: sweep.sh itself does not consult cli_unavailable_markers or evaluate a pane for CLI unavailability today.

A cli-unavailable escalation is deduplicated on the agent and the matched marker — never on the banner line’s full text, which can carry a reset time or countdown — so the same banner observed across sweeps is one escalation, while two distinct banners on one agent (e.g. a session limit followed later by a login failure) are two.

The orchestrator runs outside the session, so the loop has no pane to watch for the orchestrator’s own CLI dying — every exit summary says so explicitly (The orchestrator runs outside the session; its CLI is not monitored by the loop.) instead of implying coverage it does not have.

Nothing in an unattended session approves a prompt — the loop escalates and the human answers. The dashboard subprocess runs no approver either, attended or unattended; it renders the broker log and nothing more.

Re-attaching a drive loop to a live session

A drive loop ends while the session it was driving is still perfectly alive — on the heartbeat, on a stuck signal, or because you interrupted it. To drive that session again, re-run the same git paw start --unattended command in its repository:

# Drove the wave the first time; drives it again the second time
git paw start --unattended --from-all-specs

The live session is neither relaunched nor forked. Launch-selection flags (--branches, --specs, --from-all-specs, --cli) are never applied to a session that already exists — attaching a new agent to a live session is git paw add — and that is exactly what lets the original launch command double as the re-attach command. No paw-<project>-2 is created.

What it does next depends on the session’s effective status:

StatusWhat start --unattended does
ActiveBinds a loop and drives the session as it stands. No pane is created, rebuilt or re-titled, no worktree is touched, and no boot block, prompt or keystroke is injected anywhere.
PausedRevives the session exactly as git paw start revives it, then drives it.
StoppedRecovers the session exactly as git paw start recovers it, then drives it.

A revive or recovery that fails fails the command, without running a loop.

The terminal is never attached, even from an interactive shell — the drive loop occupies the foreground process, exactly as it does on a fresh --unattended launch. The command prints the session name and the git paw attach line for watching it from another terminal.

In every other respect the re-bound loop is the same loop: the same cadence, the same escalation, correction and heartbeat behaviour, the same exit summary, and it approves nothing. It drives the session’s own agent roster, and reaches the broker at the endpoint recorded for that session rather than the one in your current configuration — so a [broker] port changed after launch cannot point the loop at another repository’s broker, or at none.

Bare sessions are refused. A session started without supervisor mode deliberately leaves its boot prompts unsubmitted for a human to send, and the loop’s buffered-input recovery would submit them. start --unattended against one exits non-zero naming the session, stating that only a session launched in supervised mode can be driven unattended, and pointing at git paw attach for driving it by hand. Nothing about the session is changed. A --dry-run likewise binds nothing and is unaffected.

One drive loop per session

Two loops on one session would each nudge, submit buffered input and escalate into the same panes, so at most one may drive a session instance at a time. Every loop — whether from a fresh launch or a re-attach — holds a drive-loop binding: a lease file at .git-paw/tmp/drive-loop.binding (gitignored, beside the approval claims) naming the session instance and the loop itself. It is refreshed every poll and released when the loop exits.

A start --unattended that finds a live loop already holding the binding refuses before changing anything — no session, pane or worktree state is touched, and no second loop runs. It exits non-zero with an error naming the session, the process id holding the binding, and the binding file. Wait for that loop to exit (or stop that process), then re-run the command.

A loop killed without releasing its binding does not wedge the next one. A binding that stops being refreshed lapses after the staleness window — the larger of four poll intervals and two minutes, and of the window the holder itself recorded, so a loop polling more slowly than the caller is never displaced while it is alive — and the next start --unattended simply takes it. There is never a lock file to clear by hand. A loop that exits cleanly releases its binding immediately, so a re-attach right after a heartbeat exit binds without waiting at all.

Two start --unattended runs that reach a lapsed binding at the same instant still produce exactly one loop: the binding is read, judged and rewritten under an exclusive lock (.git-paw/tmp/drive-loop.lock), so the second runs its check against the first’s result and refuses. That lock file is left in place between runs; it is empty, and nothing needs to be done about it.

git paw stop revokes the session’s binding, and recovering a stopped session supersedes any binding left behind for it. A loop that finds its binding revoked or taken over exits session-torn-down on its next poll without acting — see Session-instance lifecycle below.

Deleting .git-paw/tmp/ ends a running loop. The binding goes with it and the loop exits on its next poll. That is fail-safe rather than destructive: a loop that is not running approves nothing, so the panes go on waiting for a human exactly as they would have. Re-run git paw start --unattended to bind a new loop.

Upgrading mid-run. A drive loop started by a git-paw older than this one holds no binding, so a re-attach is not refused while it is still running — and you would end up with two loops driving the same panes. Stop that older --unattended process before re-attaching to its session.

Session-instance lifecycle

A loop binds to the specific session instance it began driving — not just the session name — and re-confirms on every tick, before it sweeps or sends anything, that it is still that instance’s driver. Two things must hold:

  • The session receipt still describes the instance it bound to. The receipt’s creation timestamp is the durable instance token. If the receipt is gone, its created_at no longer matches (a new session of the same name was started after a purge), or its status is now stopped, the loop is done.
  • The drive-loop binding still names this loop. git paw stop revokes it, and a re-attach that recovers a stopped session supersedes it.

Either one failing exits the loop immediately — without sweeping panes or sending any keystrokes — rather than continuing to drive whatever now occupies that session name. This is what makes purge/stop safe to run against a session still being driven unattended: the old loop notices on its very next poll and gets out of the way instead of escalating prompts from a session it no longer owns.

The binding covers the gap the receipt alone cannot see. Recovering a session keeps its created_at, so a session stopped and recovered inside one poll interval looks unchanged to the receipt check — and the loop bound to the stopped instance would go on driving the recovered one. Revoking the binding on stop is what tells it otherwise.

# Drive every discovered spec to completion, unattended
git paw start --unattended --from-all-specs

# Or a fixed branch set
git paw start --unattended --branches feat/auth,feat/api

Multiple feedback → fix → re-verify cycles per agent are normal and are not treated as a stuck signal — the loop lets agents iterate.

The loop is a pump, the supervisor is the brain

The drive loop is deliberately mechanical: it cannot decide anything. The orchestrator is the opposite — it can reason about anything — and it runs outside the session, so the loop never types into it. Every judgment call the loop meets is a broker review item, and nothing is injected into any pane:

Judgment callThe broker record
A live permission prompt — to relay to the human, not to decidethe loop’s agent.question from supervisor
An ambiguous questionthe worker’s own agent.question
A merge decisionthe worker’s own committed/done agent.artifact
A non-converging branchthe loop’s correction slow / exhausted escalation

The loop never waits on any of them: one call being deliberated cannot freeze the wave, and every record stays on the broker until it is answered. The orchestrator reads them there — through sweep.sh, the broker helper or the MCP server — and so does a human reading the dashboard’s broker log.

A permission prompt is relayed, not decided. Deciding it from the pane text would make the orchestrator a classifier — one reading untrusted output, and so reachable by prompt injection. The orchestrator puts the prompt in front of the human, naming the agent and the command, and runs sweep.sh approve <pane> only when the human explicitly says to.

A nudge is sent reliably, not fired and forgotten. Typing text into a pane and immediately following it with Enter is not enough: on a paste-aware CLI the text can land in a paste buffer, and a bare follow-up Enter against a stale input line is a proven no-op — it does not submit anything. So every nudge — a worker re-engagement, and a stranded directive’s recovery — sends the text, waits the CLI’s configured settle delay (the same [clis.<name>].submit_delay_ms the boot prompt uses), then sends Enter as a separate keystroke. Verification waits that same settle delay again, so the pane has redrawn, before taking the check capture — and judges by the input box’s content, never by comparing it against the sent text verbatim: a long nudge wraps onto more than one visual row, and only its first row still carries the prompt sigil, so an exact-equality check would misread a wrapped line as already delivered. The text counts as still present when the input box is non-empty and either starts with that first row or shows a paste-buffer placeholder; it counts as submitted once the box is empty or the pane has left its idle state. A line still present is recovered by clearing it (Ctrl-U), re-typing the text read from the whole box, and sending Enter again, up to a bounded number of attempts. A pane still wedged after every attempt is escalated to the broker for human review and listed in the exit summary’s escalations — deduplicated per pane, so a persistently stuck line is reported once rather than on every ~15-second sweep. The whole send — the first attempt through every recovery attempt — runs under the target pane’s exclusive claim (see “One approver per pane, enforced” below), so a recovery only ever clears a line the loop itself just put there, never text a worker agent typed.

No nudge is sent once the loop is winding down. The wind-down/exit state is checked before dispatching any nudge on a tick, so a worker’s idle pane gains no further unsubmitted text on the loop’s way out.

Worker nudges skip finished workers. Any nudge aimed at a worker pane is suppressed once that worker has finished — its broker status is done or verified. The signal is deliberately the broker status and never a pane-content diff: a finished worker’s pane is just as unchanging as a stuck one’s, so a diff cannot tell “idle because done” from “idle because stuck”. A worker that later leaves the finished state becomes nudge-eligible again — the verdict is deferred, not dropped.

Note this is a narrower set than the one stall detection treats as terminal, which also counts blocked and committed. That is deliberate: those two are exactly the states a worker occupies while it awaits correction — committed and standing by for re-verification, or blocked and not polling its inbox — and they are precisely who the correction loop exists to re-engage. Suppressing them would silently disable it for its most common path. Finished and quiet are different conditions; only the first ends a worker’s eligibility for a nudge.

What the orchestrator does with a judgment call is documented in the bundled supervisor skill under Judgment calls handed to you: derive spawn order from the agents’ agent.intent declarations and the conflict detector’s edges; answer a question from the specs plus cross-agent state; sequence merges after verifying; and declare a worker unrecoverable only per max_cycles / on_exhausted.

The human is the exception handler. A human is re-engaged for what the orchestrator genuinely cannot decide — plus the heartbeat exit described above, and any run no orchestrator is driving, where the broker review items and the exit summary are the whole escalation.

Headless hand-off of judgment calls

Waiting for someone to pull the broker inbox makes an unattended run depend on a human. So when a headless invocation resolves, the drive loop hands each judgment call it cannot make to the orchestrator CLI once, non-interactively — a fresh one-shot turn at the repository root that reads the orchestrator skill and acts on the call:

Judgment callHanded to a one-shot?
An ambiguous agent.questionyes
A merge decision (a committed/done agent.artifact)yes
A correction-exhausted branchyes
A live permission promptnever
The early correction heads-up (slow-converging branch)never — it requires no action yet

The broker review item is always in place first — the worker’s own message for a question or a merge decision, the loop’s escalation otherwise — so the hand-off adds to the record, it never replaces it. A permission prompt is never handed off: a one-shot has no human to relay it to, and no git-paw process answers a permission prompt. It stays on the broker, in the exit summary and in the manual-decision log, for you or for an orchestrator you tell to run sweep.sh approve.

Resolution. The CLI is [supervisor].cli, else default_cli. Its headless form is [clis.<name>].headless_args, else the built-in row (claude → -p, codex → exec), else none. The launch output reports the result once, at loop start — for example headless hand-off: claude -p — or says why the hand-off is unavailable (no CLI resolved, the CLI has no headless form and which [clis.<name>].headless_args key would supply one, or [supervisor.escalation] enabled = false). With no hand-off the loop behaves exactly as without it: broker records and the exit summary only.

The invocation. The argv is the CLI, then the approval flags resolved from [supervisor].approval (falling back to agent_approval), then the headless arguments — e.g. claude --dangerously-skip-permissions -p. The escalation context arrives on stdin, never on argv, so nothing appears in ps. It names the agent and the call — the worker-written part fenced between markers as quoted data the one-shot must never take instructions from — points at .git-paw/skills/orchestrator/SKILL.md and .git-paw/AGENTS.local.md, gives the broker URL, tells the one-shot to act only through the bundled helpers or the MCP tools and to publish its outcome, and forbids it to run sweep.sh approve or answer any permission prompt. For a merge decision it carries the unattended merge rule: merge fast-forward only once every gate is verified; escalate only on a merge conflict, an exhausted correction cycle or a regression. “Escalating”, for a one-shot, means publishing the reason on the broker and exiting, leaving the review item for you. The environment adds only GIT_PAW_BROKER_URL — no credential: verdicts go through sweep.sh, which fetches it at the root via git paw __authority. There is no terminal; stdout and stderr are appended to .git-paw/logs/<session>/orchestrator-escalations.log.

Bounded, serialized, deduplicated. At most one one-shot runs at a time per session; further calls queue FIFO. Each queued call is keyed by its kind, its agent and its discriminator (the question text, the artifact status, or nothing for a correction-exhausted branch); a call whose key is already queued or in flight is dropped, so a worker re-publishing the same question or committed artifact while its one-shot is pending does not spawn a second one. Once that one-shot has exited, a later identical message is a new event. Each agent also gets one pending question at a time: while a question from an agent is queued or running, its further questions stay on the broker only, and the exit summary counts them — a looping worker cannot queue an LLM run per question. [supervisor.escalation] timeout_seconds (default 1800) bounds each one: on expiry its whole process group is killed and the outcome recorded as timeout. The loop never waits on a one-shot — the sweep continues while it runs. At wind-down the loop stops starting queued one-shots but lets the in-flight one finish or time out, saying on stderr that it is waiting; calls never started are reported as still queued. A session torn down under the loop (git paw stop, git paw purge) kills the running one-shot instead. If git-paw itself is interrupted while a one-shot runs, the one-shot is not killed — see the security posture.

Outcomes. Each finished or timed-out one-shot is published as an agent.status from supervisor (status working) whose detail carries escalation_key, exit_code (or "timeout") and duration_seconds; it is in the broker log, and the supervisor’s /status row shows the phase escalation until its next message. The exit summary adds the number of invocations with a per-outcome count (completed, non-zero exit, timed out), the number still queued and the escalation-log path — or, when no invocation ran, why the hand-off was unavailable or disabled. A handed-off escalation is still listed among the escalations awaiting review.

[supervisor]
cli = "claude"
approval = "full-auto"      # what the one-shot may do unattended — see below

[supervisor.escalation]
enabled = true              # default
timeout_seconds = 1800      # default; 0 is rejected

What a one-shot can do depends on [supervisor].approval. Nobody answers the one-shot’s own permission prompts, so it can do exactly what its approval level and the CLI’s own allow rules permit. Under auto with the git paw setup-cli grants it can read state and answer a question through the broker helper, but it cannot publish a verdict or merge: sweep.sh verified, sweep.sh feedback-gate and sweep.sh conflict-ack are deliberately never granted, and git merges fall under your own rules. A merge-decision hand-off therefore needs approval = "full-auto" — recommended only inside the sandbox — or allow rules you write yourself. See the security posture for what full-auto means here.

Set enabled = false when you drive the session yourself. Your own interactive orchestrator and a one-shot would otherwise act on the same judgment call at once. Disabling the hand-off restores broker-only behaviour without any other change.

Unattended correction loop

When a supervisor gate fails, git-paw publishes agent.feedback to the branch’s inbox — but a blocked worker is not polling its inbox, so the correction stalls until a human re-engages the pane by hand. That single human hop was the dominant cost measured across the v0.9.0–v0.11.0 dogfoods (4–8 manual verify↔fix cycles per branch).

The [supervisor.correction] table promotes that cycle into the drive loop. Enable it with:

[supervisor.correction]
auto_loopback = true       # default is false, for back-compat
max_cycles = 5
on_exhausted = "escalate"  # or "abandon"
escalate_after_cycles = 3

With auto_loopback = true, each poll the loop:

  1. Watches for failing gate verdicts. A gate failure arrives as an agent.feedback from the supervisor whose errors carry a [<gate>] tag naming one of gate_tags (default: [testing], [regression], [spec audit], [doc audit], [security audit], [scope]) — the shape .git-paw/scripts/sweep.sh feedback-gate publishes. A passing gate publishes agent.verified instead, so only failures start a cycle. Other traffic on the same channel — conflict-detector warnings, peer feedback — is never mistaken for a gate verdict. The tag vocabulary is configurable (see gate_tags) so a review process with different gate names can still drive the loop; the five-gate verification framework itself is unchanged.
  2. Re-engages the worker’s pane. The branch’s pane is resolved by pane_current_path, re-captured immediately before the send, and typed with the gate-tagged feedback plus a submitting Enter as a separate keystroke. If the fresh capture shows the pane is no longer idle (a permission prompt went up between the sweep and the send), nothing is typed and the correction is retried on the next poll — deferred, never dropped.
  3. Counts the cycle. Each delivered re-engagement spends one of the branch’s max_cycles. A branch that reaches a terminal PASS leaves the cycle and its counter resets, so an unrelated later failure starts from a fresh budget.
  4. Flags a slow converger once. When the count reaches escalate_after_cycles — and that lands strictly before max_cycles — an early heads-up is surfaced while re-engagement continues.
  5. Applies on_exhausted when the budget is spent. escalate flags the branch to the orchestrator/human as unrecoverable; abandon marks it failed and sends no escalation. Neither re-engages that branch again, and the exit summary lists it under Correction budget exhausted.

Cycle counts are held in the drive loop’s memory and are not persisted — restarting the supervisor restarts every branch’s budget.

The feature is opt-in: with auto_loopback = false, or with no [supervisor.correction] table at all, the loop sends no re-engagement keystrokes and the gate feedback is published exactly as it was before this existed.

When [supervisor] learnings = true, exhausting a branch’s budget also appends a correction_exhausted learning carrying the branch, the worker CLI, and the cycle count — the tiered-model tuning signal that tells you a worker’s model is under-powered for a class of task. With learnings off, nothing is recorded.

The orchestrator’s own permission level

No pane runs an orchestrator, so no git-paw launch flag reaches your interactive orchestrator: it runs in your own agent session, under whatever permission mode you started that session with. [supervisor].approval changes no pane command; it is the approval level of the drive loop’s headless one-shot invocations, falling back to agent_approval when unset. When it resolves to full-auto for a CLI with no built-in flag row and no [clis.<name>].approval_args override, the loop prints a warning naming the CLI and the override key and runs the one-shot without flags. Coding agents resolve their flags from agent_approval alone — setting approval never changes a worker pane.

auto resolves to a deterministic permission mode

agent_approval accepts three levels: manual, auto, and full-auto. full-auto maps to the CLI’s native skip-permissions flag (for example claude --dangerously-skip-permissions); manual appends no flags at all. Previously auto also appended no flags, so an auto pane silently inherited whatever default its CLI’s config directory happened to carry — sometimes manual approval, sometimes something else, depending on machine state outside git-paw’s control.

auto now resolves to an explicit, deterministic flag per CLI wherever one exists: for Claude this is --permission-mode acceptEdits — file edits are accepted by the CLI without asking, while shell commands still raise a prompt, which git-paw escalates for you. Codex’s auto already resolved to --sandbox workspace-write. A CLI with no such mode keeps the empty string and runs at whatever its own default is, unless you supply a [clis.<name>] approval_args override.

These resolved flags are git-paw’s only influence on what an agent may run without asking. Everything the chosen mode leaves prompting stays prompting: git-paw escalates it and waits for you. See Agent permissions.

Launch reliability: acceptance dialogs and dead-pane detection

Two failure modes could previously leave an unattended wave silently dead at boot:

  • First-run acceptance dialogs. The first time a CLI launches with a bypass-permissions flag against a config directory that has never accepted it, some CLIs show a one-time confirmation dialog before their normal UI appears — for example Claude Code’s bypass-permissions dialog, which defaults its highlighted option to “No, exit”. A pane launched unattended into that dialog exited within seconds without git-paw noticing. The launch-readiness gate now recognises this dialog (and a “trust this folder?” prompt) as a distinct state and answers it by selecting the affirmative option by its text, never by a hardcoded digit or a blind Enter — so the default-to-exit trap is never sprung. A dialog the gate cannot resolve fails the launch loudly instead of silently continuing; a CLI whose dialog the gate does not recognise falls back to the prior readiness/relaunch behaviour unchanged.
  • Panes that reverted to a bare shell. git-paw launches every pane as a shell and send-keyss the CLI command into it. When a CLI process exits — whether from an unanswered dialog, a crash, or any other reason — the pane does not close; it reverts to its underlying interactive shell. tmux has-session alone cannot tell a live wave from one where every agent’s CLI has quietly died, because the session (kept up by the dashboard pane) still exists. git paw status and git paw doctor now additionally require at least one agent pane whose foreground process is not a bare shell before reporting a session healthy, so a fully-dead wave is reported stale/stopped rather than active. A session within its first ~30 seconds is exempted from this check — the CLI needs a moment to start.

Pane Layout and Labelling

When you attach to a supervisor session, git-paw styles the tmux panes so the boundary between the top row (the full-width dashboard) and the agent grid below is easy to see. By default each session gets:

  • Double-line pane borders (═║) instead of tmux’s default light ─│ lines, so the rows visibly separate even with four or more agents.

  • A per-pane label strip above every pane — a reverse-video label bar showing its index and role:

    ╔═ 0: dashboard ══════════════════════════════════╗
    ║ (live agent table)                              ║
    ╚═════════════════════════════════════════════════╝
    ╔═ 1: feat/cold-start ════╗╔═ 2: feat/conflict-det ═╗
    ║ (coding agent)          ║║ (coding agent)         ║
    ╚═════════════════════════╝╚════════════════════════╝
    

    Pane 0 is always dashboard and the agent panes are labelled with their branch id (feat/<branch>). The index shown is the same one you pass to tmux send-keys -t <session>:0.<N> (window 0; the session’s second window, broker, hosts the broker), so you can cross-reference the label strip with the broker’s /status agent listing.

  • An active-pane highlight — the focused pane’s border is cyan-bold while inactive panes are dimmed, so you always know which pane has keyboard focus.

These options are applied only to git-paw-managed sessions (paw-*); your other tmux sessions keep their own styling. They apply to plain git paw start sessions too, not just supervisor mode.

Set [layout].border_affordances = false in your config to opt out and inherit your own tmux styling — see the Layout configuration reference. Opting out changes only the styling: git-paw still tags each pane with its role, which is what status and doctor read to tell whether a session’s agents are running. On tmux older than 3.2 the double border lines aren’t recognised; git-paw prints a warning and continues with the remaining affordances rather than failing.

Understanding what the supervisor is doing

You rarely need to read the orchestrator’s own session to know what it is up to. The supervisor tags each status heartbeat it publishes with a phase — a short label for its current lifecycle activity — and a structured detail body. The dashboard’s supervisor row shows the current phase in its status column, and the MCP get_session_status tool returns the same phase and detail so external tooling can read it programmatically. (Both surfaces degrade gracefully: a supervisor that hasn’t published a phase renders with its plain status, exactly as in v0.5.0.)

The supervisor emits these phase-tagged statuses through the bundled .git-paw/scripts/sweep.sh status-publish helper — --phase <phase> sets the label and --detail '<json-object>' carries the structured body, while the plain status-publish <message> form (no flags) publishes the v0.5.0 shape unchanged. The helper shapes the agent.status payload internally, so the supervisor never hand-rolls the JSON. Because the helper always lives at the stable path .git-paw/scripts/sweep.sh, a permission rule in your agent CLI scoped to that exact path covers every phase without a broad HTTP-client rule.

Every sweep.sh publish verb — status-publish, verified, feedback-gate, conflict-ack, learn, answer, advanced-main — checks the broker’s HTTP response. When the broker does not accept the publish (any non-2xx status, or no response at all), the helper prints the broker’s error to stderr and exits non-zero instead of printing its “published” line. A non-zero exit means the event did NOT reach the broker: fix the cause (a malformed target agent id, a stopped broker) and re-publish rather than treating the verb as done. The agent-side broker.sh behaves identically.

The phases, with what each one means:

PhaseThe supervisor is…
baselinerecording the regression baseline on the default branch at boot
sweepscanning agent panes and the message stream for new events
auditverifying a branch through the gates (the detail.audit_step field names which of tests, regression, spec, docs, security is running)
mergecherry-picking or merging a verified branch into the base
feedbacksending feedback or a proactive route to a peer agent
intent_watchtracking declared file intents for overlap
learningsrecording a structured learning record
checkpointenumerating a multi-publish batch before a risky action, so it can recover from a stream timeout
idlewaiting for the next event — nothing in flight

The phase taxonomy is an open set: the supervisor may publish a value not in this table, and the dashboard renders it as-is. The supervisor emits a status on every phase transition and at most once per ~30 seconds while it stays in the same phase, so the row stays current without flooding the broker log. Phases are surfaced for the supervisor row only — coding-agent rows render as they always have. (The one exception is the supervisor-published stuck-on-prompt alert, which appears on a stalled coding agent’s own row.)

Resolve Pane to Agent via pane_current_path

Before the supervisor tmux capture-panes or tmux send-keyss a specific agent, it needs the pane index for that agent. The bundled supervisor skill is explicit that pane indices are NOT alphabetical by agent_id, NOT in the CLI-argument order from git paw start --specs A B C, and SHALL NOT be inferred from git paw status output or the dashboard’s row order (both of those are sorted alphabetically by the broker, which has no relationship to the launcher’s internal scan order).

The canonical resolution command queries tmux directly:

tmux display-message -t <session>:0.<pane> -p '#{pane_current_path}'

<session> is the session name recorded in .git-paw/AGENTS.local.md; window 0 holds the panes (the session’s second window, broker, hosts the broker). The output is the pane’s working directory. For coding-agent panes that is the agent’s worktree path — <repo>/.git-paw/worktrees/feat-<branch> under the child placement git paw init configures, or <project>-feat-<branch> beside the repo under the sibling placement. Matching it against the worktree_path entries in the per-repo session file (.git-paw/sessions/<session>.json) gives the owning agent’s branch_id — the authoritative agent_id (with the slash form feat/<branch> for git operations). A pane whose pane_current_path is <repo>/.git-paw/worktrees/feat-auth belongs to agent feat-auth. Because the directory name depends on the configured placement, the match is against the recorded worktree_path, never against the directory’s basename.

The session file’s roster also records a pane_index, but that is a launch-time value and is not used for routing — the live pane_current_path match is.

The orchestrator reads the roster when it needs an agent’s pane rather than building a pane map once and reusing it for the session, and in any case re-reads it after git paw add or git paw remove — the roster changes and tmux renumbers panes on layout changes. The roster can also be read through the helpers or the MCP get_session_status tool.

The bundled .git-paw/scripts/sweep.sh invokes this command on every sweep iteration. If the helper is missing for any reason, the supervisor falls back to invoking tmux display-message directly — this is the documented escape hatch.

The supervisor MUST NOT use git paw status output (or the dashboard’s row order) as a mapping source — both are sorted alphabetically by the broker and have no relationship to the launcher’s pane assignment. Always resolve via pane_current_path first.

Pane-driving and cross-worktree git disciplines

The bundled supervisor skill hardens three behaviours so a multi-agent session runs without the human approving the supervisor’s every command:

  • Use .git-paw/scripts/sweep.sh for pane work; never inline loops. Ad-hoc for p in <panes>; do tmux send-keys -t ...:0.$p ...; done loops trip the simple_expansion permission gate on each iteration, so one sweep becomes one approval prompt per pane. The helper does the explicit per-pane sends with no variable expansion. A single explicit tmux send-keys -t ...:0.<index> is reserved for pushing one typed reply to one pane.
  • Never send-keys to the dashboard (pane 0). It is a TUI, not an agent; keys sent there land in the dashboard, not in any agent. sweep.sh targets coding-agent panes by design.
  • Cross-worktree git uses git -C <path>, never cd <path> && git. A bare cd trips the untrusted-hooks warning on some CLIs and leaks the working directory, so a later mutating git command can land on the wrong branch. git -C scopes the directory to the single command.

The skill also nudges any agent whose uncommitted working set exceeds ~10 files to commit its completed section, preserving per-section verification granularity.

Spec audit governance sub-step

When a project’s .git-paw/config.toml lists governance documents under [governance] (adr, test_strategy, security, dod, constitution), the supervisor reads each doc as part of the Spec Audit gate and flags drift between the diff and the doc’s checklist. The five canonical doc-checklist examples are:

  • DoD (Definition of Done) — walk each - [ ] item against branch state.
  • ADRs (Architectural Decision Records) — verify new architectural decisions (new deps, new patterns) have a matching ADR.
  • security.md — walk each security checklist item against the diff.
  • test-strategy.md — check that test composition matches the documented strategy.
  • constitution.md — check the diff against documented principles (e.g. “no panics in library code”).

Findings surface as standard agent.feedback errors tagged [doc audit] mixed in with other doc-audit gaps. See Governance for the config schema and how the supervisor reads each doc.

Agent permissions

git-paw does not approve permission prompts, and makes no judgement about whether an agent’s command is safe. It has no safe-command list, no danger list and no path-based safety rule. Whether a command runs without asking is decided entirely by the agent CLI’s own permission system:

  1. The CLI’s resolved permission mode. [supervisor] agent_approval maps to each coding agent’s native launch flags — see auto resolves to a deterministic permission mode. This is git-paw’s only influence on what runs unprompted.
  2. The CLI’s own allow and deny rules. Whatever that CLI reads from its own settings. Pre-approve your routine dev commands there; that is the only place a standing grant can live. A grant is only as narrow as you write it.
  3. A sandbox, if you want a kernel-enforced boundary rather than a policy one — see Sandbox and Security Posture.

Anything the resolved mode leaves prompting stays prompting. git-paw’s job starts there: it detects the live prompt and escalates it for a human decision (the broker agent.question the orchestrator reads, the manual-decision log, and the exit summary) without blocking the rest of the wave. Then a human decides, and the approval is typed only by an explicit sweep.sh approve <pane>.

A fully prompt-free unattended run therefore needs the decision made before the prompt exists: either CLI allow rules covering everything the agents do, or agent_approval = "full-auto" inside a sandbox. Without that, expect escalations.

Deprecated: [supervisor.auto_approve] and [supervisor.common_dev_allowlist]. Up to v0.16.0 these configured a git-paw command classifier that answered prompts it judged safe. The classifier is retired: three successive security reviews each found new command shapes it wrongly called safe, and each fix opened the next hole. Both tables still parse and round-trip, so existing configurations load unchanged — but they have no effect, and each configuration file that declares them prints one deprecation warning per process. Delete them at your leisure, and move any safe_commands, stacks or extra entries into your agent CLI’s own allow rules. See the configuration reference.

The classifier used to choose “Yes, and don’t ask again” for prompts it judged safe, and your CLI persisted those as durable grants in its own settings file. Retirement stops new ones; it removes none. Review that file and delete what you did not intend — git-paw never edits or deletes it.

Run dev commands bare — no exit-code-probe wrappers

A CLI allow rule matches a command prefix, so it generalises across argument variants. Wrapping a command in an exit-code probe — <cmd> && echo "EXIT $?", <cmd>; echo $?, RC=$?; echo "$RC" — defeats that: the probe text varies per run, so the CLI’s command-string matching never matches the next invocation and the command re-prompts every time. The bundled supervisor and coordination skills instruct agents to run dev commands bare and read the exit status directly; keep your own commands to the same shape so a grant actually suppresses the prompt.

(git-paw used to normalise the two most common wrappers away before classifying. With no classifier there is nothing to normalise for, and the CLI’s own matching was never forgiving about them anyway. Run them bare.)

The explicit approve action

sweep.sh approve <pane> is the only path by which an approval keystroke reaches a pane, and nothing in git-paw runs it for you. A human runs it, or tells the orchestrator to run it. It answers the prompt once, with option 1, and consults nothing about the command.

Four guards sit between that instruction and the keystroke.

Option 1, never a durable grant. The helper dispatches the digit 1 followed by Enter as two separate keystrokes, reporting approved pane <N> (option 1). It never dispatches a blind cursor-movement sequence — Down + Enter lands on No on a 2-option prompt and takes the permanent “don’t ask again” grant on a 3-option one. Before sending it checks that a numbered option 1 really is on screen (and sends nothing if not), and that option 1 does not itself carry the prompt profile’s broad-grant wording: on some prompt shapes the first option is “Yes, and don’t ask again …”, and pressing it would write a standing permission rule you did not ask for. In that case the helper sends nothing and says why. git-paw never creates a durable grant.

Re-confirm before send. Immediately before dispatching, the helper takes a fresh capture of the target pane and confirms the prompt’s live structural markers still anchor the modal block (the same anchored search and bounded-trailing-region tolerance detection uses, not a fixed ~4-line scan). If the prompt cleared between the decision and the send — the agent moved on, or you answered it in the pane first — no keystrokes are sent, and the helper reports cleared before send, no keys sent. This closes the stray-input race where the option digit would otherwise land in the CLI’s chat box as literal text, polluting context and leaving dangling unsubmitted commands.

Coding-agent panes only. The helper sends keys only to a pane the session’s discovery roster (.git-paw/sessions/<session>.json) lists as a coding agent’s pane_index. sweep.sh approve 0 against a broker-enabled session — pane 0 is the dashboard — sends nothing and reports that the pane is excluded because it is not a coding agent, whatever the capture shows; so does a pane no roster entry claims. In a broker-less session the first coding agent sits at pane 0, and the roster makes it approvable. With no readable roster the helper falls back to refusing pane 0. The drive loop applies the same role-based rule: it never types into the dashboard or an unattributed pane. The refusal cannot be changed by a prompt profile.

One sender per pane, enforced. Keystrokes can come from sweep.sh approve and from the drive loop’s own nudge and buffered-input recovery. If two land on one pane, the second falls through onto whatever appears next. So before sending, every sender takes that pane’s exclusive claim: an atomic create-if-absent of <repo>/.git-paw/tmp/approve-pane-<N>.claim, held across the send and released immediately after. Because the file system decides who wins, exactly one sender can hold a pane at a time, and the exclusion works across processes and languages — the shell helper and the binary compute the same path and honour each other’s claims.

Taking the claim never blocks. A sender that finds a pane already claimed skips it and moves on, retrying later; sweep.sh approve reports pane <N> claimed by another approver, no keys sent and names the claim file. Release is automatic on every exit path, including an error or a crash mid-send. If a sender is hard-killed (SIGKILL) with a claim held, the next one steals it after it has gone untouched for 10 seconds — so a crash costs one pane one sweep, never a permanent wedge. The claim directory is ephemeral and already gitignored; you never need to manage it by hand, though removing a stale file is harmless.

No new broker message is involved in any of this: the trigger that a pane is awaiting approval is the existing agent.status with phase: "stuck-on-prompt", and an escalated prompt uses the existing agent.question.

What detection still does

Prompt detection is the part git-paw keeps, and it is unchanged. The live-prompt gate decides whether a modal is actually on screen: at the capture’s true end, a textual marker (the Do you want to … question or the Esc to cancel footer) within the last ~4 non-blank lines, or a numbered option line anchoring that tail with a textual marker within the last ~15 non-blank lines. The search runs upward over a bounded trailing region, so a CLI that renders a task list or status strip under its input area cannot hide a live modal beneath that chrome; beyond the true end, liveness requires the modal’s actual shape (a footer-shaped line plus an option list), never a bare mention of the footer text in narration or tool output, and a match is rejected outright if the CLI’s idle input box appears below it — a real modal replaces the input box, so seeing one means the footer is a quoted copy.

Detection reports only that a prompt is present, and what command it is asking about (the text between the Bash command / Bash(…) header and the confirmation question, or the target path of a file-operation prompt). That command text is used for the escalation message, the dedup key and the manual-decision log — never to decide anything. A supervisor that merely mentions rm -rf / in prose is not a prompt to run it, and a prompt asking about ls -la gets exactly the same treatment as one asking about rm -rf /.

The window sizes, the trailing bound and the pane-0 refusal are compiled logic. A CLI prompt profile supplies the literals (markers, patterns) and can never widen the gate, change which option approve presses, or lift the pane-0 refusal.

Out-of-worktree write violations

An observed out-of-worktree write attempt by a coding agent — a permission prompt asking to write into operator territory, pane narration describing one, a diff touching operator paths, a stray artifact in the repo-root control dirs — is a boundary violation, and the supervisor skill instructs a two-step response:

git-paw no longer has a mechanical check for this. The protected-path rule existed only to stop the retired classifier auto-approving such a write; with nothing approving automatically, a write prompt into operator territory reaches a human like every other prompt, and the supervisor relays it rather than approving it. The doctrine below is what carries the boundary; for mechanical enforcement use deny rules in your agent CLI’s own settings, or the sandbox.

  1. First attempt — scoped feedback. The supervisor sends the agent agent.feedback (via sweep.sh feedback-gate <agent> scope …) naming the worktree boundary and the attempted path, so the agent can rescope: persist inside its worktree, or publish agent.question before any outside write.
  2. Repeat by the same agent — operator escalation. A second attempt after the scoped feedback escalates to you via agent.question, naming the agent, both attempted paths, and the feedback already sent — the decision (pause, re-brief, remove) is yours, not the supervisor’s.

The agent-side counterpart is the coordination skill’s memory-isolation briefing (see Agent Coordination): persistent artifacts live inside the agent’s worktree, operator configuration directories are off-limits, and a task that seems to require an outside write becomes a question instead.

Manual approvals

Every prompt the --unattended drive loop escalates is a command that needed you. git paw approvals aggregates them, so you can see which prompts recur and decide which deserve a standing rule in your agent CLI’s own allowlist — closing the loop on repeat friction.

This is written by the drive loop, so it covers --unattended runs only. An attended session (git paw start --supervisor without --unattended) runs no loop and records nothing; git paw approvals still reads any log an earlier unattended run left behind.

What gets recorded

Each time the drive loop escalates a live permission prompt, it appends one JSON line to .git-paw/sessions/<session>.manual-approvals.jsonl:

{ "timestamp": "2026-05-29T12:34:56Z", "agent_id": "feat-auth",
  "pattern": "make integration-test", "first_seen": true }

The loop runs outside the agent’s CLI pane, so it cannot see whether you ultimately pressed Yes or No on the prompt. What it records is the honest, observable signal: a command that required a human decision. A command escalated seven times is a strong allow-rule candidate regardless of each individual yes/no — the friction it causes is what you want to remove.

Exactly one line is written per escalation: a prompt re-seen inside the five-minute dedup window is recorded once, and escalations that are not permission prompts — a cli-unavailable banner, a wedged nudge, a correction-loop signal — are not recorded here at all.

The pattern comes from the escalation’s own prompt slice — the command between the Bash command / Bash(…) header and the confirmation question, or a file-operation prompt’s target path. Surrounding pane text is never scraped, so an accept-edits diff line or a previous command’s output sitting next to the prompt cannot be recorded as if it were the command awaiting a decision. When neither source yields something that parses as a command, no line is written and no permission_pattern learning is emitted — an unusable pattern is worse than a missing one, because it pollutes the promotion report you act on.

Reporting

git paw approvals                       # active session, text table
git paw approvals --json                # machine-readable
git paw approvals --session paw-other   # a specific session
git paw approvals --limit 5             # top 5 patterns by count

The text table sorts patterns by how often each was escalated, with a SUGGEST hint for which of your agent CLI’s own allowlists could absorb it:

PATTERN                       COUNT  SUGGEST
make integration-test             7  user allowlist
./scripts/deploy-staging.sh       3  project allowlist
podman build -t paw-ci .          2  user allowlist

A pattern is suggested for the CLI’s project allowlist when it looks project-specific (a ./-rooted script path, or it contains the project or branch name); otherwise it belongs in your user allowlist, where it benefits every project. Both are rules in the agent CLI’s own settings — the SUGGEST column is a hint, not a rule, and git paw approvals never edits any settings file for you.

When [supervisor] learnings = true, the first sighting of each pattern also emits a permission_pattern learning so it surfaces in Learnings Mode alongside the other patterns.

Opting out

Recording is on by default for --unattended runs. To disable both the log writes and the derived learnings emission for a session:

[supervisor]
manual_approvals_log = false

The opt-out affects writes only — git paw approvals still reads any pre-existing log. See Configuration for the field reference.

Repo-configurable gate commands

The supervisor’s five verification gates each invoke a configurable command substituted from [supervisor] keys at session boot. The eight keys are:

  • test_command
  • lint_command
  • build_command
  • fmt_check_command
  • doc_build_command
  • doc_tool_command — API-doc generator, separate from doc_build_command
  • spec_validate_command
  • security_audit_command

When a key is missing or empty, the placeholder renders as (not configured) in the supervisor skill and the supervisor gracefully skips the tooling invocation for that gate — the gate’s manual review still applies. doc_tool_command is the one exception: when unset it renders as the empty string (not (not configured)) so the surrounding prose reads naturally for projects that don’t ship an API-doc generator. See Configuration for defaults and examples.

Polyglot bundled skills

The bundled supervisor and coordination skills ship as a single template each, but they render per session against the project’s stack and the resolved spec backend. Two placeholders make this work:

  • {{DOC_TOOL_COMMAND}} substitutes the doc_tool_command config value above so doc-audit prose names the actual API-doc tool for your stack.
  • {{SPEC_PATH_DOCTRINE}} renders a per-backend path doctrine: OpenSpec sessions see openspec/changes/<name>/... references, Spec Kit sessions see .specify/specs/<feature>/..., Markdown sessions see the flat paw_status: pending shape, Superpowers sessions see docs/superpowers/plans/*.md, and multi-backend sessions list each present backend’s conventions in one paragraph.

A CI no-leak audit asserts that the rendered supervisor skill contains no stack-specific tokens (cargo, rustdoc, .rs:, Cargo.toml, rustc) and no vendor product names outside the explicitly-allowed <!-- allowlist-prose --> span, which carries a deliberate CLI-specific enumeration (the paste-buffer marker wording).

Verification runs the whole suite (never fail-fast)

The supervisor’s testing gate runs the configured test command in a whole-suite / no-fail-fast mode. Most test runners stop at the first failing test group, so a single early failure — typically an environment-specific guard test rather than a code defect — can hide every later suite and produce a false PASS. A run that aborted early is incomplete, not a pass; a testing PASS requires the full suite to have executed.

For git-paw itself, the no-tmux-server guard test trips whenever any paw-* tmux session exists (which it always does mid-session) and sits in an early-alphabetical test binary, so a plain cargo test / just check stops there. The just verify recipe runs the trustworthy gate — GIT_PAW_ALLOW_LIVE_SESSION=1 cargo test --no-fail-fast (the suite is socket-isolated, so the opt-out is safe) plus lint, deny, and audit — and the dogfood config sets [supervisor].test_command = "just verify" so the supervisor uses it automatically.

Per-commit verification

The supervisor verifies each agent’s commit as it lands, not in batches. When an agent publishes agent.artifact { status: "committed" }, the supervisor runs that branch’s five-gate sweep promptly — independent of whether other agents have finished. Waiting for a slower agent so several commits can be verified “together” delays feedback to the agent that already committed and serialises work that is meant to run in parallel; at five-to- eight agents that idles the whole session until the slowest one finishes.

To make the trigger an explicit event rather than something the supervisor has to notice during a pane sweep, the broker posts a supervisor.verify-now message naming the committing branch to the supervisor inbox on every committed artifact. This is on by default; set [supervisor] verify_on_commit_nudge = false to suppress the nudge and rely on the supervisor’s sweep cadence instead.

Per-branch verifications may run concurrently — each gate sweep runs against its own branch in its own worktree, so verifying one agent never blocks starting another’s verification. The supervisor may bound how many sweeps run at once at its discretion. The one legitimate reason to defer a verification is a genuine dependency (one agent’s work needs another’s merge in place first), which the supervisor states explicitly when it defers.

Verdicts go through the helper

The verdict messages — agent.verified, and agent.feedback from supervisor — need the session’s authority credential, so the supervisor publishes them only through the root-side sweep.sh verbs, which fetch it with git paw __authority and send it as X-Paw-Authority:

.git-paw/scripts/sweep.sh verified <agent-id> "<summary>"
.git-paw/scripts/sweep.sh feedback-gate <agent-id> <gate> "<message>"
.git-paw/scripts/sweep.sh conflict-ack <agent-a> <agent-b> <file> ["<note>"]

A raw curl …/publish of either message gets 403 {"error":"authority required"}, and so does the same verb run from inside a worktree (__authority refuses there). See Security Posture.

Recovery from API stream timeouts

The supervisor’s own model stream can time out mid-sweep — most often while it is queueing several agent.feedback or agent.verified publishes in one iteration. Left unhandled, the natural reflex is to fall silent and wait for the next sweep tick, which silently drops the pending feedback: the agent that was about to hear about a regression keeps working unaware, and you get no signal anything went wrong.

The bundled supervisor skill teaches an explicit recovery discipline so a transient timeout no longer loses work:

  1. Recognise the failure shape — a mid-stream cutoff, or a transport / stream error in the CLI output. The phrasing is generic so it matches across CLI variants.
  2. Checkpoint before risky actions — before any iteration with more than one intended downstream publish, the supervisor publishes a single agent.status “checkpoint” enumerating what it is about to do, giving the recovery path a re-entry point.
  3. Replay the missing publishes — on recovery the supervisor re-reads its checkpoint and, for each intended target, polls /messages/<target> since the checkpoint timestamp to see which publishes landed, then re-publishes only the missing ones (the replay is idempotent, so duplicates are safe).
  4. Never assume a publish succeeded — a returned publish HTTP call is not proof the record landed; the supervisor confirms by polling or re-publishes idempotently before moving on.

Each successful recovery emits a recovery_cycles agent.learning record, so recurrent timeouts surface in the learnings output. See the “Stream-timeout recovery” section of the bundled assets/agent-skills/supervisor.md skill for the full doctrine, and Learnings Mode for how the recovery_cycles records are aggregated.

Broker-side conflict detector

Starting with v0.5.0 the broker auto-detects three failure shapes between parallel agents and emits agent.feedback (and, where configured, agent.question) on the supervisor’s behalf. All auto-emitted messages begin with the [conflict-detector] token so the supervisor can distinguish detector output from human-typed feedback. The three failure shapes are:

  • Forward conflict — two agents publish overlapping agent.intent declarations.
  • In-flight conflict — two agents’ filesystem-watched modified_files sets overlap on the same file.
  • Ownership violation — an agent’s modified_files include a file inside another agent’s active intent.

See Conflict Detection for the algorithm, configuration, and escalation behaviour.

Learnings aggregator

When [supervisor] learnings = true, the supervisor session records deterministic friction signals (stuck durations, recovery cycles, conflict events, permission patterns) into a markdown file you can review after the run. The supervisor also records qualitative learnings it reasons out over the run — recurring failure shapes, doc gaps, ADR drift, scope mistakes, and tooling friction with git-paw itself — publishing each through the bundled .git-paw/scripts/sweep.sh learn <category> <title> <body-json> helper (never a raw HTTP request). It captures them opportunistically during each sweep and again in a session-end synthesis pass. See Learnings Mode for the file format, the category set, and how to opt in.

When the user types in your session

The orchestrator runs in your own interactive agent session (outside tmux) — you can type at any time while its autonomous monitoring loop is running. The supervisor finishes the current step (spec audit, test run), responds, then resumes the loop. User input is a high-priority interrupt, not a replacement for the loop.

Each kind of user input maps to an existing mechanism — the supervisor does not invent new channels:

  1. Status question (“how’s feat-auth going?”, “anything blocked?”) — answered conversationally in the orchestrator’s session using sweep.sh status, sweep.sh inbox, and sweep.sh capture <pane>. Nothing is published to the broker — this is a conversation with the user, not a session-wide event.
  2. Directive (“ask feat-auth to use bcrypt”, “tell feat-api to skip the migration”) — published as agent.feedback to the named agent through sweep.sh feedback-gate with the [directive] gate, plus a conversational confirmation to the user.
  3. Judgment-call ask (“should we merge feat-a before feat-b?”) — the supervisor applies its normal escalation rules. If the user has already provided enough information to decide, the supervisor answers in its session using its reasoning. agent.question only fires when the call is genuinely ambiguous beyond what the user just provided — typically when the user is asking because they don’t know either.

The mechanisms (sweep.sh status / inbox or the MCP reads, sweep.sh capture, sweep.sh feedback-gate, tmux send-keys, agent.question) are unchanged. The addition is when to use which in response to user input.

Routing through the supervisor

The orchestrator is the single conversational surface: you work in its session and address individual agents through it, rather than tab-switching into each agent’s pane. Two commands you type to the orchestrator drive this.

/agents — the live inventory

Type /agents to the orchestrator, in its own session, and it responds with the current inventory: one row per agent plus the orchestrator’s own supervisor row once it has published a status, each coding-agent row carrying branch_id, status, last_seen, cli, the detected mode, and pane_index. The supervisor row has no pane index — the orchestrator has no pane, and pane 0 is the dashboard:

branch_id   status    last_seen  cli     mode          pane
feat-auth   working   3s         claude  accept-edits  2
feat-api    blocked   90s        claude  unknown       1
supervisor  working   1s         claude  —             —

The inventory is composed from broker GET /status (branch_id, status, last_seen, cli) joined with the live pane_current_path mapping (pane_index). Pane indices are path-resolved, never assumed from ordering — a mid-session git paw add/remove that renumbers the grid is reflected correctly after the next sweep. The mode column is best-effort (accept-edits, interactive, or unknown); unknown is treated as the safe case by /tell.

Known limitation — underscored branches show no pane_index. The join matches an agent’s agent_id against its worktree directory name. A branch containing _ (or a ., or upper case) keeps those characters in its directory while its agent_id maps them to -, so the two no longer agree and the pane goes unmatched: the agent still appears in the inventory with its status and last_seen, but pane_index is empty and mode reads unknown. /tell still reaches it — delivery falls back to the broker agent.feedback path, which is the safe route it uses for unknown mode anyway. Only the send-keys fast path and the pane-mode column are affected. Avoid _ in branch names if you want the pane columns populated.

The supervisor refreshes the inventory at its sweep cadence and caches it in memory. /agents and /tell reuse the cached snapshot while it is younger than [supervisor.tell] inventory_max_age_seconds (default 60), re-polling the broker only when it goes stale. The cache is not persisted — a restarted supervisor rebuilds it on the first command.

/tell <agent> <prompt> — route a prompt

Type /tell feat-auth rebase onto main and the supervisor:

  1. Validates feat-auth against the inventory (the slug feat-auth or slash form feat/auth both work). An unknown target is not delivered — the supervisor replies with the candidate list of available agents, e.g. unknown target feat/ghost​; available agents: feat-api, feat-auth.
  2. Chooses a delivery mode (see below) and delivers the prompt.
  3. Acknowledges in its own session and, when learnings mode is on, records the routing decision (see Learnings).

/tell routes exactly the agent you name and exactly the prompt you type — it never decides the recipient for you and never pipes your prompt into another CLI to generate content. There is no bulk broadcast form in this release; smart, supervisor-decided routing is a future candidate.

Delivery modes

[supervisor.tell] mode selects the default delivery channel:

  • feedback (the default) — the prompt is published as an agent.feedback broker message and the agent consumes it on its next inbox poll. Safe for mixed-mode sessions: the prompt is queued and recorded, never raced into a running prompt.
  • send-keys — the prompt is injected directly into the target’s pane with tmux send-keys. Faster, but only safe when the target is in accept-edits mode. When you configure send-keys but the target’s detected mode is interactive or unknown, /tell falls back to feedback and prints a note so you can investigate the mode-detection drift. Because of this fallback, send-keys is best reserved for sessions where your agents run in accept-edits mode.

Every /tell is auditable: when [supervisor] learnings = true, each invocation appends a line to a ### Supervisor routing section of .git-paw/session-learnings.md with the timestamp, target, delivery mode, and the prompt (truncated past 200 characters). With learnings disabled, nothing is written.

Proactive routing (opt-in, confirmed)

When a sweep finds an agent blocked on a question you have already answered earlier in the orchestrator’s session, the orchestrator may offer to forward that context — it asks you a yes/no question in its own session and waits. It never routes on its own: no proactive /tell fires without your explicit y. Reply n (or anything else) and the offer is dropped. This keeps the pattern from feeling surveillance-y while it earns trust through dogfooding.

opsx role gating

The bundled skills teach that /opsx:verify and /opsx:archive are supervisor-only, but a coding agent that ignores the prose can still archive a change — the skill is a convention, not a guard. When the session’s spec engine is OpenSpec, git-paw closes that gap with a post-commit role-gating guard.

The guard watches the broker’s agent.artifact { status: "committed" } events. When a commit looks like an archive operation and the committing agent is not the supervisor, it reacts per [opsx] role_gating (see the configuration reference):

  • warn (the v0.6.0 default) — publishes an agent.feedback to the offending agent and records an agent.learning with category permission_pattern so the violation surfaces in learnings.
  • block — the warn behaviour, plus an agent.feedback to the supervisor requesting a revert. The supervisor performs the revert through its merge-orchestration skill (confirming with the user first unless [supervisor] auto_revert = true); git-paw never runs git revert itself.
  • off — the guard is disabled.

Detection heuristic. A commit is archive activity when either signal fires:

  1. its subject line matches the canonical archive shape chore(specs): archive <name>; sync deltas to main specs, or
  2. its diff moves files into openspec/changes/archive/<name>/ and/or adds a main spec under openspec/specs/<capability>/spec.md.

The two-signal design is deliberately conservative — it would rather trip on the supervisor’s own archive (which the attribution check then clears, since agent_id == "supervisor") than miss a coding-agent archive. An unresolvable worktree is treated as a violation.

Reading the warning. The feedback text names the short SHA, the offending agent, and the trigger so you can spot a false positive at a glance:

opsx-role-gating: detected archive activity on commit abc1234 by agent feat-x
  (not the supervisor).
  Reason: commit message matched the archive heuristic ("chore(specs): archive
  feat-x; sync deltas to main specs").
  /opsx:verify and /opsx:archive are supervisor-only ...

If a warning is a false positive, the named reason tells you which signal fired so you can adjust (or flip role_gating = "off"). The guard is inert under the Spec Kit, Markdown, and Superpowers engines — those have no /opsx: commands or archive paths, so the capability and its skill sections are scoped to OpenSpec only.

v0.6.0 behaviour change. role_gating defaults to warn, so sessions where a coding agent archives a change now see guard feedback. Set role_gating = "off" for the prior (no-guard) behaviour.

Agents call git-paw’s helper scripts constantly, and under most agent CLIs every call raises a permission prompt. The supervisor does not make those prompts go away: a helper call runs without one only through the agent CLI’s own permission system and the rules in its settings.

You can pre-approve git-paw’s own helper calls in that CLI’s allow rules with git paw setup-cli <CLI> --allowlist. It is optional and consent-only — you run it, it prints its plan first, and --undo takes back exactly what it added. Nothing in the supervisor, the drive loop or the MCP surface ever runs it, and it refuses to run from a linked worktree.

The embedded cli-setup skill carries the rules an agent must follow around it. git paw init and git paw start export it to .git-paw/skills/cli-setup/SKILL.md beside the orchestrator skill (main checkout only), and the MCP get_skill tool serves it under the name cli-setup. The supervisor skill restates the three that bind the orchestrator directly: run the writing form only when the human asked in the current conversation, pass --yes only after they approved the exact plan shown, and never run it during an unattended run.

Merge orchestration

Once every spec’d agent has been verified — the orchestrator published agent.verified for it through sweep.sh verified (agents never publish it themselves) — or the user explicitly asks for a merge, the orchestrator runs the merge orchestration loop. v0.5.0 removed the Rust auto-merge loop; merging is the orchestrator’s responsibility, performed with shell, git, the configured test command and the bundled helpers or MCP tools — never a raw HTTP request.

Trigger. Either every spec’d agent has been verified (the orchestrator published agent.verified for it), or the user has explicitly requested the merge.

Unattended runs merge without asking first. When the session context carries the drive-loop directive (--unattended), the orchestrator merges as soon as the trigger holds — fast-forward only — without asking the human first. It holds a branch back and escalates only on:

  • a conflict — a non-fast-forward or conflicting branch (skipped, with agent.feedback to its agent), or a dependency cycle (escalated with agent.question);
  • an exhausted correction cycle — a branch whose correction budget ran out (escalated with agent.question);
  • a regression — a merge that fails the test command (reverted, with agent.feedback to its agent).

Every held-back branch is named in the final summary. In an attended run the orchestrator may still confirm the merge order with you before merging.

Merge order. The orchestrator reads the broker’s message log (sweep.sh inbox / sweep.sh events, or the MCP read tools) and builds a dependency graph from agent.blocked events: each event from agent X with payload.from = Y is an edge “X depends on Y”. The supervisor then topologically sorts the graph: agents with no incoming edges merge first; dependents follow.

Per-branch merge. For each branch in topological order, the supervisor checks out main and runs:

git merge --ff-only feat/<branch>

Never a merge commit — fast-forward only. If --ff-only fails (the branch diverges from main, or there is a conflict), the supervisor SKIPS that branch and publishes agent.feedback to the owning agent asking them to rebase or resolve. On a successful fast-forward, the supervisor runs the configured {{TEST_COMMAND}}; if tests fail, the supervisor reverts the merge with git reset --hard <prev-HEAD> and publishes agent.feedback tagged [regression]. If tests pass, it publishes agent.advanced-main with sweep.sh advanced-main <merged-branch> <summary> — the helper resolves the new default-branch SHA and the base branch name itself — so every dependent agent learns the base moved on its next inbox poll — see When Main Advances — then moves on to the next branch.

Cycle handling. If the dependency graph has a cycle, the supervisor does NOT merge any branch in the cycle. Instead, it publishes agent.question (through broker.sh --agent supervisor question) to the human and waits for guidance before continuing.

Final summary. When the loop completes, the supervisor publishes a final agent.status summarising which branches merged cleanly, which were skipped (and why), and any regressions encountered.

Branch refresh (opt-in)

Over a long wave the supervisor merges peer after peer into the default branch while every other live worker keeps committing on the baseline it started from, so each branch drifts further from what it will eventually be merged into. The only existing recourse is worker-side and advisory — see When Main Advances in Agent Coordination — which correctly tells a worker holding uncommitted work not to auto-rebase, but means refresh only happens when a worker chooses to act.

When [supervisor] branch_refresh = true is set, the supervisor’s unattended drive loop instead performs the refresh itself, at a moment it can prove is safe: after a successful merge (agent.advanced-main), for a branch that is provably idle and clean. This is a supervisor operation, not a worker judgment call — the two disciplines do not conflict, because the supervisor acts only under exactly the condition (no uncommitted work) that makes the worker-side “do NOT auto-rebase” rule unnecessary.

Default off. Rewriting a live agent’s history is opt-in, permanently — set [supervisor] branch_refresh = true to enable it. With the field absent or false, post-merge behaviour is unchanged from every prior version.

Every live worker branch is checked after every merge, and refreshed only when ALL of the following hold. Any single failure leaves the branch untouched — there is no partial or degraded attempt, and the skip is recorded, not retried or escalated:

  1. Clean. The worker’s working tree has no uncommitted changes, per the broker watcher’s tracked modified_files — never a fresh probe, so the supervisor and the watcher can never disagree about what “clean” means.
  2. Idle. The worker’s pane is not mid-response.
  3. Claimed. The supervisor holds the pane’s exclusive approval claim (the same TOCTOU guard approvals use), narrowing the race window between the cleanliness check and the rebase.
  4. Conflict-free. A git merge-tree prediction shows the rebase would not conflict — checked BEFORE the rebase starts, so the worktree never enters an in-progress rebase state a live agent could observe. A prediction that proves wrong once the rebase actually runs is recovered with git rebase --abort, leaving the branch at its pre-rebase HEAD.
  5. Behind. The branch actually has commits to pull in; an already current branch is left alone.

A branch that has already passed the five-gate supervisor verification and awaits merge is never refreshed — it has been verified at its current base, and rewriting it would silently invalidate that verification.

Notification. After a successful refresh the affected worker receives agent.feedback tagged [branch-refresh] (the same bracket-tag convention as [conflict-detector]) telling it HEAD moved, so it does not keep reasoning about file contents or commit SHAs from before the rewrite. This is deliberately NOT agent.advanced-main — that event means “the base moved” and the worker-side discipline above keys off that exact meaning; conflating it with “your own branch was rewritten” would corrupt that discipline.

Tiered-Model Workflow

A smart orchestrator running a fleet of cheaper workers is the workflow git-paw is built for: one capable model supervises, and several lower-cost models do the implementation in parallel worktrees, with little-to-no human babysitting. This chapter is the process — how to set a run up so the cheap fleet actually succeeds. For the mechanics of how the orchestrator drives the wave, see Supervisor → the loop is a pump, the supervisor is the brain.

The two roles

git-paw already gives you both halves:

  • Orchestrator — your own agent session outside the tmux session, reading the context a --supervisor launch renders into .git-paw/AGENTS.local.md ([supervisor].cli names its CLI). Its job is judgment: spawn order, answering cross-agent questions, sequencing merges, and deciding when a worker is unrecoverable. Give it your most capable model.
  • Worker — an ordinary CLI in a worktree (default_spec_cli / --cli). Its job is execution: implement one change against a concrete task list. This is where a cheaper model earns its keep — if the task list is concrete enough (see below).

Two things make the cheap fleet viable rather than a prompt-storm: the orchestrator handles the judgment calls the loop can’t instead of parking them in an inbox nobody reads, and the self-healing correction loop turns a gate failure into an automatic re-engagement rather than a human hop. Both ship in v0.14.0. What you bring is the two pre-steps this chapter is about: a plan, and a definition of “verified.”

Step 1 — Plan first; planning is not a git-paw command

git-paw does not author your change set — it executes one. Author the plan before git paw start, in any supported spec format: OpenSpec (openspec/changes/<name>/), Spec Kit, Superpowers plan documents, or plain Markdown-with-frontmatter. git-paw starts once those artifacts exist and turns each into a worker session.

Because planning is the one step that most rewards a capable model, do it with your smart model — a planning pass, or an /opsx authoring session — and only then hand the result to the cheap fleet.

Step 2 — Make the tasks task-level concrete (the rule that decides success)

This is the central rule of the whole workflow: a cheaper worker executes reliably only when the tasks artifact is concrete. Vague tasks are where cheap models diverge; concrete tasks are where they succeed — and an under-specified plan costs more in orchestrator correction cycles than a smart worker would have cost in the first place.

Concrete means, per task: the exact files to touch, the per-step verification command, and an explicit acceptance criterion.

# Too vague — a cheap worker will guess, and guess differently each run:
- [ ] Add validation for the config

# Concrete — a cheap worker can execute it deterministically:
- [ ] In src/config/supervisor.rs, reject max_cycles = 0 at load with an
      actionable error (mirror the invalid on_exhausted handling); add a unit
      test asserting 0 fails and 1 is accepted. Verify: cargo test --lib
      config::supervisor.

The OpenSpec/Superpowers task shape (a checkbox list where each item names its file, its verification, and maps back to a spec scenario) is the exemplar. If you find the orchestrator burning correction cycles on a worker, the fix is almost always a sharper task, not a smarter worker.

Step 3 — Define “verified” in your AGENTS.md, not in git-paw

What “verified” means — the exact test/lint/build commands, the acceptance criteria, the definition of done — is a per-project convention, so it lives in your repo’s injected instructions (AGENTS.md / CLAUDE.md), never baked into git-paw. This is the export-agnosticism principle: git-paw ships only the generic five-gate scaffold — testing → regression → spec audit → doc audit → security — and the supervisor follows your concrete gates when it verifies a worker’s branch. Spell them out, or the orchestrator falls back to the scaffold and guesses at your commands.

Wire the gate commands into [supervisor] (test_command, lint_command, build_command, doc_build_command, spec_validate_command, fmt_check_command, security_audit_command) and write the definition of done into AGENTS.md. git-paw’s own AGENTS.md — its “Change Checklist” and “Supervisor verification is a five-gate framework” sections — is the exemplar to model yours on.

Step 4 — Launch, and let the orchestrator drive

With the plan authored and “verified” defined:

# Attended: you watch the panes and answer the permission prompts yourself.
git paw start --specs my-change --supervisor

# Unattended: the in-process loop drives to completion, escalating every
# permission prompt to you rather than answering it.
git paw start --specs my-change --supervisor --unattended

Turn on the self-healing loop so a gate failure re-engages the worker automatically instead of waiting for you:

[supervisor.correction]
auto_loopback = true      # re-engage a gate-failed worker's pane with the feedback
max_cycles = 5            # give up on a branch after this many verify-fix rounds
on_exhausted = "escalate" # escalate | abandon

From here the orchestrator answers questions, sequences merges, and re-engages failing workers on its own; see the correction loop and the pump-to-brain hand-off for exactly what it handles.

Where the human still comes in

You are the exception handler, not the operator. A human is re-engaged only for what the orchestrator genuinely cannot decide, plus two structural cases: the heartbeat exit after ~25 minutes without progress, and a run with no orchestrator session at all (a pure --unattended run nobody orchestrates leaves every judgment call as a broker review item for you, exactly as it did before). Everything the fleet can resolve — safe approvals, cross-agent questions, verify-fix cycles, merge sequencing — resolves without you.

Dashboard

When the broker is enabled, one tmux pane runs the dashboard instead of an agent CLI, and it is pane 0 in every mode: the full-width top row above the agent grid, with the coding agents laid out row-major beneath it (no orchestrator CLI runs inside the session). That is the same layout in a supervisor-mode session, a bare one and a spec-driven one — every session git-paw builds uses the agent grid. It renders the same live status table that refreshes about once a second (and immediately on a key press), giving you an at-a-glance view of what each agent is doing. A supervised session builds the dashboard pane even with the broker off.

The dashboard is observation-only — it never sends actions back to agents, and no git-paw keystroke sender ever types into it. Beyond q to quit, its keystrokes drive the Broker log panel (toggle, filter, inspect). Human input (questions, directives, replies to agent.question events) happens in the orchestrator’s own agent session, outside tmux; the dashboard simply renders broker state. There is no inbox panel.

What the Dashboard Pane Shows

The dashboard renders a table with one row per agent. Once the orchestrator has published its own agent.status (as supervisor), its row is pinned to the top with a horizontal-line divider beneath it; coding-agent rows follow in alphabetical order:

┌────────────┬────────┬────────────┬─────────────┐
│ Agent      │ CLI    │ Status     │ Last Update │
├────────────┼────────┼────────────┼─────────────┤
│ supervisor │ claude │ 🔵 watching│ 12s ago     │
│ ────────── │ ────── │ ────────── │ ─────────── │
│ feat-api   │ claude │ 🟡 blocked │ 1m ago      │
│ feat-auth  │ claude │ 🔵 working │ 3m ago      │
│ fix-typo   │ codex  │ 🟢 done    │ 8m ago      │
└────────────┴────────┴────────────┴─────────────┘

When no orchestrator has published (e.g. broker-only mode — broker on, no --supervisor — or a supervised session before your orchestrator session has picked up its context), the divider is not rendered and coding agents fill rows alphabetically from the top.

Status Symbols

SymbolMeaning
🔵Working – agent is actively processing
🟢Done/verified – agent has completed its task
🟣Committed – agent has committed work
🟡Blocked – agent is waiting on something
⚪Idle / unknown phase – agent has not reported a recognised status

The Last Update column shows elapsed time since the agent’s last status update (30s ago, 3m ago, 1h 15m ago).

Supervisor row, cli field, and phase field

The supervisor row’s Status column does not show the wire-message type label that a coding-agent row shows. Instead the supervisor publishes a phase field on its agent.status messages (e.g. baseline, watching, approving, answering, merging, summary), and the dashboard prefers that label when rendering its row. This avoids the misleading status=feedback label the supervisor would otherwise show when it publishes agent.feedback to a coding agent.

The orchestrator has no pane and no worktree, so it is not a watch target and the broker cannot infer its cli from the watch-target map. To populate the CLI column for the supervisor row, the supervisor self-registration agent.status includes a cli field (e.g. "cli":"claude"). The broker upserts that value into its internal CLI map when it receives the message.

The CLI column is populated for every agent row, not just the supervisor. git-paw knows each pane’s CLI at launch and pre-fills it authoritatively — coding agents from the watch-target map (the per-repo session JSON), the supervisor from [supervisor].cli/default_cli. Agents do not self-report their CLI (they would only be guessing). A row appears once its pane publishes a status, and its CLI column shows the pre-filled value; if a CLI somehow can’t be resolved it shows a ? placeholder rather than a blank cell.

A coding-agent row can also show stuck-on-prompt in its Status column: the bundled sweep.sh helper’s stuck detector publishes this phase directly on the stalled agent’s own agent.status when its pane shows a permission-prompt marker and its heartbeat has stopped advancing (see Agent permissions). This is the one coding-agent phase outside the generic working/done/blocked/committed set in Status Symbols above.

The roster holds only real agents

The /status agent roster — and therefore the dashboard table — is built only from agents that publish agent.status. The from/target/verified_by identity fields on agent.feedback, agent.question, and agent.verified messages are routed and stored but never mint a roster row. This means a human- or supervisor-originated feedback can no longer create a phantom human agent that never heartbeats. The roster is in-memory, so any pre-existing phantom from an older broker also clears on the next git paw start.

When the supervisor row appears

The supervisor row appears after the orchestrator — your own agent session, outside tmux — publishes its first agent.status as supervisor, its first action on picking up .git-paw/AGENTS.local.md. The launcher never publishes on its behalf, and the drive loop’s escalations (published as agent.question from supervisor) never create the row either, so a session no orchestrator drives shows no phantom supervisor row.

Stalled-wave indicator

Agent rows answer “is this agent alive?” The status line at the bottom of the dashboard answers the question they cannot: “is the wave getting anywhere?” Whenever the broker reports the wave as stalled, the status line carries an indicator naming how long it has been idle:

wave stalled 1h

The reading comes straight from the broker’s wave staleness state, so the same two things clear it that count as progress anywhere else — a wave phase advancing or a branch tip moving. The indicator disappears on the first refresh that reports stalled = false; nothing is shown while the wave is progressing. Tune (or disable) the threshold with [broker] wave_stall_minutes — see the configuration reference.

This is the dashboard’s view of a session that looks healthy row by row and is not moving: every pane alive, every agent working, and nothing having actually advanced since the unanswered prompt.

Controls

KeyAction
qQuit the dashboard (shuts down the broker; agent panes keep running but lose broker communication)
lToggle the Broker log panel on/off
aReset the Broker log filter to All
1–9, 0Toggle the individual filter chips (status / artifact / blocked / verified / feedback / question / intent / verify-now / advanced-main / learning)
↑/k, ↓/jMove the highlight up/down the Broker log rows
EnterOpen the details overlay for the highlighted row
EscClose the details overlay

Broker log panel

The Broker log panel fills the screen region freed when v0.5.0 removed the prompt inbox. It renders a scrolling, type-filterable list of the broker messages observed during the current dashboard session, newest at the top, so you can watch the session’s wire-level activity at a glance instead of tailing /messages/<id> over curl.

The log is in-memory only — closing the dashboard drops it — and bounded by [dashboard.broker_log] max_messages (default 500). Older messages fall off the top as new ones arrive. The panel is read-only: replying to a question or directing an agent happens in the orchestrator’s own session.

When visible, the panel occupies a fixed 20 rows by default — materially more than its original 12 — so more coordination traffic is visible without constant scrolling. Tune it with [dashboard.broker_log] height_lines. The agent-status table keeps a positive minimum height and absorbs the terminal’s slack, so on a tall terminal the table grows while the panel stays at its configured height; on a terminal too short to grant both, the panel yields space before the table shrinks below its minimum.

Showing and hiding the panel

The panel is visible by default. Toggle it with l, or set the launch default and height in .git-paw/config.toml:

[dashboard.broker_log]
max_messages = 500
default_visible = true
height_lines = 20

When the panel is hidden the dashboard layout is identical to its v0.5.0 post-inbox-removal shape (title, agent table, status line) — the agent table expands to fill the freed space, and the status line gains a broker log hidden — press l to show hint. See the configuration reference for the table’s fields.

Filter chips

A header row of chips sits above the message list, one per broker message type plus an All reset. All is active by default (every message shows). Pressing a digit hotkey (1–9, then 0 for the tenth chip) narrows the view to that type; pressing more digits adds types inclusively; pressing an active chip again removes it (emptying the selection returns to All). Press a to reset to All at any time. Filtering is a view operation — the ring buffer always retains every message regardless of the active chips.

HotkeyChipMatches
1statusagent.status
2artifactagent.artifact
3blockedagent.blocked
4verifiedagent.verified
5feedbackagent.feedback
6questionagent.question
7intentagent.intent
8verify-nowsupervisor.verify-now
9advanced-mainagent.advanced-main
0learningagent.learning

agent.answer messages have no chip of their own; they show in the default All view.

Row format

Each row is a single line: HH:MM:SS · type · agent · summary, where the summary is a per-type one-liner derived from the message body (the status message, the first modified file of an artifact, the blocking need, the intent summary, and so on). Summaries that overflow the panel width are truncated with an ellipsis (…); the full body is available in the details overlay.

┌─ Broker log (7 shown / 7 held) — l hide · a all · 1-9·0 filter · ↵ details · Esc close ─┐
│  All  1:status 2:artifact 3:blocked 4:verified 5:feedback 6:question 7:intent 8:verify-now 9:advanced-main 0:learning │
│ 14:35:09 · status   · feat-auth · working: rebasing onto main                          │
│ 14:34:58 · intent   · feat-auth · wire AuthClient                                       │
│ 14:34:12 · blocked  · feat-api  · needs auth token format from feat-auth               │
│ 14:33:40 · artifact · fix-typo  · done: src/typos.rs                                    │
└────────────────────────────────────────────────────────────────────────────────────────┘

Status activity phrases

An agent.status payload may carry an optional activity field — a short phrase naming what the agent is doing right now. When present it is appended to the status summary in parentheses, so a heartbeat conveys the work rather than just a bare working:

│ 14:35:09 · status   · feat-auth · working (running the test suite)                     │

The field is optional and additive. A status that omits it renders exactly as it did before the field existed, and agents are never required to set one.

Heartbeat collapsing

Agents heartbeat often, and a wall of identical working lines buries the real state transitions between them. The panel therefore collapses a run of consecutive status rows that are identical in every rendered field — same agent, status, activity, and message — into one row carrying the run’s latest timestamp and a ×N repeat count:

│ 14:35:09 · status   · feat-auth · working ×12                                          │
│ 14:33:40 · artifact · fix-typo  · done: src/typos.rs                                    │

Anything that differs breaks the run and starts a new row — a changed status, a changed activity phrase, a different message, another agent, or any non-status message arriving in between. So a real transition such as working → verified always gets its own row, and two distinct working phases separated by other traffic are never merged.

Like filtering, the collapse is a view operation: the ring buffer still retains every individual message, the count in the panel title reflects the rows drawn, and opening the details overlay on a collapsed row shows the newest message of that run.

Details overlay

Highlight a row with the arrow keys (or j/k) and press Enter to open a modal overlay showing that message’s full, pretty-printed JSON in a scrollable view. Press Esc to close it and return to the panel. While the overlay is open, q still quits the dashboard.

Resilience across broker restarts

The panel never clears its buffer when the broker is unreachable, paused or restarted. Historical messages stay visible across the outage, and new messages resume appearing at the top once the broker answers again. A restarted broker host numbers its log from 1 again; the dashboard notices from the host’s uptime going down, re-reads the new log from the start, and appends each new entry once — nothing it already shows is duplicated. A gap in the timestamp column is the only visible sign that messages briefly stopped flowing.

Legacy messages panel

The earlier [dashboard] show_message_log flag rendered a simpler, unfiltered messages list. It is superseded by the Broker log panel and retained only for config compatibility.

Relationship to the Broker

The dashboard is a thin client of the broker. The broker runs in its own process, git paw __broker, in a detached tmux window named broker (outside the pane grid, so pane numbering is unchanged; switch to it to see the host’s stderr). The dashboard (git paw __dashboard, pane 0) holds no broker state: it reads the broker URL from the saved session and polls GET /status and GET /log?since=<seq> on its idle cadence. Closing the dashboard pane has no effect on the broker, its watchers, conflict detection or learnings.

When the broker is unreachable

If a poll cannot reach the broker — git paw pause stopped it, the host is restarting, or a fresh launch has not finished bringing it up — the status line shows ⚠ broker unreachable, the agent table keeps the last snapshot, and the dashboard keeps polling. The indicator is independent of the stalled-wave indicator, and it clears on the first successful poll. An unreachable broker never makes the dashboard exit.

Lifecycle and exit conditions

The dashboard process is bound to its session: when the session ends, the dashboard exits on its own rather than lingering and busy-rendering to a dead terminal (a leaked __dashboard process pegging CPU). Its draw loop checks a single lifecycle gate on every iteration — including any error or degraded path, so no branch can bypass it — and exits when any of the following holds:

  • Clean shutdown. tmux delivers SIGHUP when it kills the session or pane, which ends the process.
  • Orphaned to init. On Unix, if the parent process is gone the dashboard has been reparented to init (getppid() == 1) — the tell-tale of a session torn down without SIGHUP (an abrupt tmux kill-server, a crash, or the machine sleeping). The loop notices the reparent and exits. On non-Unix platforms this check is inert and only the SIGHUP shutdown path applies.
  • Controlling terminal gone. If the pane’s terminal hangs up, reading input (event::poll) errors, or a write to the terminal fails, the dashboard treats its controlling terminal as gone and exits. This catches the case where the pane was killed but the dashboard was reparented to a lingering shell (a live but unrelated parent), so the orphan-to-init check alone would miss it.

An unreachable broker is deliberately not on this list (see above). The broker host has its own lifecycle: it flushes the broker log and exits on SIGHUP (when pause closes its window or stop kills the session), when orphaned to init, or when its terminal hangs up, and it exits non-zero with a diagnostic naming the port if it cannot bind — it never retries in a loop.

Because the dashboard is cheap to relaunch (git paw start re-attaches to the session), exiting eagerly on any of these signals is preferred over risking a leaked, busy-looping process.

Replying to agent questions

Earlier dashboard versions included a “Questions” panel and a “Reply to” input field for human-typed answers to agent.question events. The panel was removed in v0.5.0; the dashboard has no inbox. Questions and replies go through the orchestrator — your own agent session, outside tmux — and its helpers, not through the dashboard.

agent.question messages still flow through the broker. The orchestrator reads incoming questions from the broker (through sweep.sh, the broker helper or the MCP server) and replies with sweep.sh answer (an agent.answer on the broker) plus tmux send-keys to the asking agent’s pane. See the Orchestrator mode chapter and the exported orchestrator skill (.git-paw/skills/orchestrator/SKILL.md) for the reply flow.

See the v0.5.0 changelog for the removal note.

Pause and Resume

git paw pause is the soft-stop verb for short breaks (lunch, a meeting, end-of-day). It freezes the running session without losing agent state, so you can resume mid-conversation an hour later by running git paw start.

The three teardown verbs

git-paw v0.5.0 ships three verbs for taking a session down:

VerbWhat it killsWhat survivesUse when
pauseTmux client attach, broker hostTmux session, dashboard, all CLI panes (in RAM)Short break — you’ll resume in minutes/hours
stopTmux session, every CLI paneWorktrees and branches on diskLonger break — you want RAM back, OK with fresh CLIs later
purgeEverything (tmux, worktrees, state)NothingYou’re done with the project, or you want to start clean

What pause does, mechanically

When you run git paw pause:

  1. Every tmux client attached to the session is detached (tmux detach-client -s <session>).
  2. When the broker is enabled, the broker host’s broker window is closed, which delivers SIGHUP to the __broker process, which drops the BrokerHandle, which gracefully shuts down the broker and flushes the session’s <session_name>.broker.log. The dashboard pane stays and shows ⚠ broker unreachable until you resume. (A session started by a git-paw from before the broker host split has no broker window — its dashboard pane is the broker — so pause kills that pane instead, as it always did.)
  3. The session state file flips from status: active to status: paused.
  4. Every coding-agent CLI pane keeps running. Their in-memory conversation, their CLI process, and the worktree they’re working in are untouched.

git paw status shows the paused state with a blue indicator and a “run git paw start to resume” hint.

The RAM trade-off

Pause is fast and state-preserving, but the CLI processes stay allocated. A typical Claude Code instance holds ~300 MB, so a 10-pane session is roughly 3–5 GB of RAM sitting idle while paused.

Pick the right verb based on duration:

  • Pause is right for short breaks where instant resume matters more than RAM.
  • Stop is right for long breaks where you’d rather get the RAM back. Resuming via git paw start spawns fresh CLI processes — you lose conversation context but the worktrees and branches carry over.
  • Hibernate (future v1.0.0) will be both: state preserved AND RAM released.

Resuming a paused session

git paw start

When git paw start detects a paused session that’s still alive in tmux, it takes the restart-from-pause path:

  1. Re-creates the broker host in its broker window and waits (up to 10 seconds) for it to answer.
  2. Re-creates the dashboard pane at its saved index only if none is running — normally the paused dashboard is still there and simply reconnects.
  3. Flips session status back to active.
  4. Re-attaches your tmux client.

Cost: one broker boot (plus a dashboard pane only for a session paused before the broker host split). No worktree creation, no CLI process spawn, no boot-prompt injection (beyond submitting the held boot prompt of any agent added with git paw add while paused — and, when that agent’s spec format declares a workflow entry point, the held entry point after it, once its pane goes idle). The agent panes are exactly as you left them — open conversations intact, prompt buffer intact.

If the tmux server died while paused (rare — typically only happens on machine reboot or tmux kill-server), git paw status shows the session as stopped instead, and git paw start falls through to the normal cold-recovery path (fresh CLI processes, resuming each conversation where the CLI supports it — see Per-CLI cold resume).

Idempotency

git paw pause is safe to run repeatedly:

  • Pausing an already-paused session prints “Session ‘NAME’ is already paused.” and exits 0 without changing state.
  • Pausing a stopped session prints “Session ‘NAME’ is already stopped; pause has no effect.” and exits 0.
  • Pausing with no session for the current repo prints “No active session for this repo.” and exits 0.

Per-CLI cold resume

Stop’s cold-recovery path (and the same recovery git paw start runs when every agent pane has dropped back to a bare shell) restores conversation context too, for CLIs that support it. At launch git-paw assigns each worker a fresh session identifier and passes it on the CLI’s own launch command (Claude Code’s --session-id <uuid>), then records it in the session state. When that worktree is later recovered, git-paw relaunches its CLI with the matching resume flag (--resume <uuid> for Claude Code) instead of a bare launch — the worker picks up its prior conversation instead of starting cold.

This solves the long-break case without holding RAM, complementing pause for the short-break case: stop releases the CLI processes, and the next start brings their conversations back rather than only their worktrees.

A worktree whose CLI has no known resume flag (no built-in row and no [clis.<name>].resume_args override) launches fresh exactly as before — nothing about cold recovery changes for it. See session_id_args / resume_args to add resume support for a CLI git-paw doesn’t know about, and git paw status --json to read a worker’s assigned id (e.g. to resume it by hand outside git-paw).

See also

  • Pause behavior is formally specified in the broker-runtime capability spec (pause flow, idempotency, restart-from-pause), alongside the pause subcommand contract in cli-parsing and the paused status variant in session-state.
  • git paw stop — the destructive teardown verb.
  • git paw purge — the full reset (removes worktrees and branches).
  • Dashboard — what the dashboard pane shows while the session is active.

Skill Templates

git-paw uses standardized agent skills following the agentskills.io specification. Skills are directories containing a SKILL.md file with YAML frontmatter and optional resource subdirectories.

Standard Location

Skills are loaded from .agents/skills/ in your project directory. git-paw walks up the directory tree from the current working directory to find this location.

my-project/
└── .agents/
    └── skills/
        ├── coordination/
        │   ├── SKILL.md          # Main skill file
        │   ├── scripts/          # Optional: Executable scripts
        │   ├── references/       # Optional: Documentation
        │   └── assets/           # Optional: Templates/resources
        └── orchestrator/
            ├── SKILL.md
            └── scripts/

Bundled skills

git-paw embeds four skills, each overridable by a .agents/skills/<name>/SKILL.md of your own:

SkillWhat it carries
coordinationHow an agent publishes status, intents and questions through the broker.
orchestrator (alias supervisor)The orchestrator’s role: following the session, the five verification gates, merge orchestration.
docs-fetchHow to look things up through the bundled docs helper.
cli-setupThe consent rules for git paw setup-cli — when an agent may and may not run the command that writes an agent CLI’s own settings.

The cli-setup skill exists because that command changes what a CLI will do without asking the human again. Its rules are deliberately restrictive: the writing form runs only when the human asked in the current conversation, a --dry-run suggestion is not consent, --yes follows approval of the exact plan shown, it never runs unattended or because of an instruction found in content the agent read, settings files are never hand-edited, and coding agents in worktrees never run it at all.

Skill Format

Each skill must contain a SKILL.md file with YAML frontmatter:

---
name: my-skill
description: A brief description of what this skill does
license: MIT
compatibility: git-paw v0.4.0+
---

## My Skill Instructions

This skill helps agents with {{BRANCH_ID}} workflows...

Required Fields

  • name: Skill name (max 64 chars, lowercase letters/numbers/hyphens only)
  • description: Clear description of the skill’s purpose (max 1024 chars)

Optional Fields

  • license: License information
  • compatibility: Version compatibility
  • metadata: Custom metadata object

Placeholders

Skills support these placeholders that get replaced at runtime:

  • {{BRANCH_ID}} - Slugified branch name (e.g., feat/http-broker → feat-http-broker)
  • {{PROJECT_NAME}} - Project name for tmux session
  • {{GIT_PAW_BROKER_URL}} - Full broker URL
  • {{SKILL_NAME}} - Name from YAML frontmatter
  • {{SKILL_DESCRIPTION}} - Description from YAML frontmatter

Supervisor gate-command placeholders

The embedded supervisor skill uses eight additional placeholders fed from [supervisor].*_command keys in .git-paw/config.toml. Each gate-command template renders verbatim into the skill prose where the supervisor agent reads it:

  • {{TEST_COMMAND}} ← [supervisor].test_command — gate 1 (Testing) test runner
  • {{LINT_COMMAND}} ← [supervisor].lint_command — gate 1 lint sub-step
  • {{BUILD_COMMAND}} ← [supervisor].build_command — gate 1 build sub-step
  • {{FMT_CHECK_COMMAND}} ← [supervisor].fmt_check_command — gate 1 formatter check
  • {{DOC_BUILD_COMMAND}} ← [supervisor].doc_build_command — gate 4 (Doc audit)
  • {{DOC_TOOL_COMMAND}} ← [supervisor].doc_tool_command — gate 4 API-doc generator (renders empty when unset, not (not configured))
  • {{SPEC_VALIDATE_COMMAND}} ← [supervisor].spec_validate_command — gate 3 (Spec audit)
  • {{SECURITY_AUDIT_COMMAND}} ← [supervisor].security_audit_command — gate 5 (Security audit)

(not configured) graceful skip. When a key is omitted from [supervisor], the matching placeholder substitutes to the literal string (not configured) in the rendered skill. The supervisor agent treats that sentinel as “skip the tooling invocation for this gate” and continues with the gate’s manual review only (e.g. the OWASP-category diff scan still runs for the security gate, the spec scenario coverage check still runs for the spec gate). Pre-v0.5.x configs that only set test_command continue to render the remaining gates as (not configured) and the supervisor agent runs them as manual-only — no behavior change for those configs.

The one exception is {{DOC_TOOL_COMMAND}}: when the underlying key is unset, the placeholder renders as the empty string rather than the (not configured) sentinel. The supervisor skill template is authored so the surrounding prose reads naturally without it for projects that don’t ship an API-doc generator.

Backend-aware and constant-driven placeholders

Two more placeholders make the bundled supervisor skill polyglot:

  • {{SPEC_PATH_DOCTRINE}} — substitutes a per-backend paragraph that names where specs live and the matching workflow. The variants:
    • OpenSpec: openspec/changes/<change>/{proposal,specs,tasks}.md plus openspec validate.
    • Spec Kit: .specify/specs/<feature>/{spec,plan,tasks}.md plus the Spec Kit checklist convention.
    • Markdown: flat .md files with paw_status: pending frontmatter.
    • Superpowers: plan files under docs/superpowers/plans/*.md, worked ### Task N step by step with - [ ] → - [x] checkboxes.
    • Multi-backend: one sentence per present backend, introduced by a “This session spans multiple spec backends” clause.
    • No backend resolved: a sentinel sentence pointing the supervisor at the project’s documentation.
  • {{CHANGE_ID}} — see the per-invocation discussion below.

{{CHANGE_ID}} is per-invocation, not per-render. Spec validators typically take a change name as argument (e.g. openspec validate my-change-id --strict). To support that, the spec_validate_command template MAY embed the literal substring {{CHANGE_ID}}. git-paw does not substitute {{CHANGE_ID}} at session boot — it passes through the render verbatim. The supervisor agent expands it at verification time using the change name it is currently auditing. This matches how {{BRANCH_ID}} behaves for coding agents (per-agent, not per-render).

Resource Subdirectories

Skills can include optional resource subdirectories:

  • scripts/ - Executable scripts referenced by the skill
  • references/ - Detailed documentation and references
  • assets/ - Templates, configuration files, and other resources

Example structure:

.agents/skills/my-skill/
├── SKILL.md              # Main instructions (< 500 lines)
├── scripts/
│   └── setup.sh          # Executable helper script
├── references/
│   └── api-reference.md  # Detailed API documentation
└── assets/
    └── config-template.json

Creating Custom Skills

To add a custom skill:

# Create skill directory structure
mkdir -p .agents/skills/my-skill

# Create SKILL.md with proper frontmatter
cat > .agents/skills/my-skill/SKILL.md << 'EOF'
---
name: my-skill
description: Custom workflow for our team
license: MIT
compatibility: git-paw v0.4.0+
---

## Custom Team Workflow

Follow these steps for {{BRANCH_ID}}:
1. Analyze requirements
2. Implement solution
3. Test thoroughly
4. Document changes
EOF

# Add optional resource directories
mkdir -p .agents/skills/my-skill/scripts
mkdir -p .agents/skills/my-skill/references

Skill Resolution

git-paw searches for skills in this order:

  1. .agents/skills/<name>/SKILL.md (walking up directory tree from current directory)
  2. Embedded defaults (compiled into git-paw binary)

The first match wins. If no skill is found, resolution fails with an error.

supervisor is an alias of orchestrator: both names resolve to the same skill, and an override directory under either name applies to both. When .agents/skills/orchestrator/ and .agents/skills/supervisor/ both exist, the orchestrator one wins, so an override written under the old name keeps working until you rename it.

Exported skills

git paw init (and every git paw start) also writes two bundled skills into the main checkout for the agent that drives the session, in the same standardized layout:

  • .git-paw/skills/orchestrator/SKILL.md — the orchestrator skill, rendered with your [supervisor] gate commands and configured spec format. It holds no session value (no broker URL, no session name); those live in the session context .git-paw/AGENTS.local.md.
  • .git-paw/skills/cli-setup/SKILL.md — the cli-setup skill, as embedded.

Each opens with name / description frontmatter whose name equals its directory name, so a skill-aware agent CLI can load the directory directly. .git-paw/skills/ is gitignored and binary-managed: a re-run overwrites it, and an override under .agents/skills/ is what the export renders from. See Supervisor → Driving the session from your own agent.

Validation

Skills are validated against the agentskills.io specification:

  • Required name and description fields must be present
  • Frontmatter must parse within the supported subset (below)
  • Skill directory must contain SKILL.md file
  • Clear error messages for validation failures

Supported frontmatter subset

git-paw parses SKILL.md frontmatter with a small, dependency-free parser rather than a full YAML library, so it supports a bounded subset rather than arbitrary YAML:

  • Top-level key: value pairs, where the value is a plain, single-quoted, or double-quoted scalar (surrounding whitespace is trimmed from plain scalars).
  • Literal (|) and folded (>) block scalars, including their strip (-) and keep (+) chomping indicators.
  • Full-line and trailing # comments outside quoted scalars.
  • A one-level metadata mapping of string keys to scalar values — every value is read as a string.
  • A key git-paw doesn’t consume (for example allowed-tools: followed by a list) loads fine; it’s skipped along with any indented content beneath it.

This parser does not silently misparse. If name, description, license, compatibility, or metadata uses a construct outside this subset — a flow mapping or list ({ a: 1 }, [a, b]), an anchor or alias (&foo, *foo), a YAML tag (!!str), or a nested mapping inside metadata — the skill fails to load with a validation error naming the field, rather than silently producing a different value. This applies to your own override skills too: a SKILL.md you hand-write under .agents/skills/ is held to the same subset as the embedded skills git-paw ships.

Examples

See the agentskills.io specification for more examples and best practices.

Migration from Older Versions

If you’re upgrading from git-paw v0.2.x or earlier:

  1. Move skills from ~/.config/git-paw/agent-skills/ to .agents/skills/
  2. Convert single .md files to directory structure with SKILL.md
  3. Add required YAML frontmatter to each skill
  4. Organize related resources into subdirectories

The new standardized format improves interoperability and enables skill sharing across different AI systems that support the agentskills.io standard.

When Skills Are Not Injected

Skill templates are only injected when the broker is enabled ([broker] enabled = true). If the broker is disabled, no coordination instructions are added to AGENTS.md.

Boot-Prompt Injection

In addition to skill template injection, git-paw automatically injects a standardized boot instruction block into every agent’s initial prompt. This ensures reliable agent self-reporting even if skill templates are not used or if agents don’t read the AGENTS.md file thoroughly.

The boot-prompt injection covers every essential coordination operation (register, done, blocked, question) with the agent’s id pre-expanded, as calls to the bundled .git-paw/scripts/broker.sh helper — or, for CLIs known to speak MCP (the built-in set is claude, codex, cursor-agent, agy), as MCP publish-tool calls — and is active in both supervisor and manual broker modes. Set [clis.<name>] mcp = true|false to override that set for a given CLI identity. See the Coordination documentation for details.

Docs Fetch Skill

git-paw bundles a docs-fetch skill and a matching helper script so an agent can consult git-paw’s own documentation on demand — pulling exactly the convention or example it needs, when it needs it, instead of carrying doc content in its boot prompt or guessing.

The design mirrors the other bundled helpers (broker.sh, sweep.sh): the skill teaches when and why to consult the docs, and the helper does the fetch. The agent invokes one stable script path rather than hand-rolling curl, so one permission rule in your agent CLI scoped to that exact path can cover every call, and the behaviour stays deterministic.

What the agent gets

When the docs-fetch skill is rendered into an agent’s instructions, the agent is told to reach for the helper — .git-paw/scripts/docs-fetch.sh — whenever a git-paw fact is unclear (a subcommand’s behaviour, a config field, a coordination or governance convention, a workflow). Documentation is fetched live and only when asked; none of it is embedded in the binary or injected wholesale into the boot prompt.

The two operations

The helper is a thin, dependency-free wrapper (curl + Python 3, the same primitives the broker helper uses). It exposes a discover-then-retrieve flow that matches the machine-readable surface published by the agent-friendly docs site (llms.txt and per-page anchors).

find <query> — fetch the site’s llms.txt index and print the pages that best match the query, each as a title, absolute URL, and one-line summary:

.git-paw/scripts/docs-fetch.sh find "worktree placement"

get <page-or-url> [anchor] — fetch a page (by the URL or path from find) and print its documentation content. Pass a section anchor to narrow the output to just that section instead of the whole page:

.git-paw/scripts/docs-fetch.sh get user-guide/worktree-placement.html
.git-paw/scripts/docs-fetch.sh get user-guide/worktree-placement.html child-vs-sibling

Configurable docs source

The docs base URL defaults to git-paw’s published documentation site and is overridable so a fork or mirror can point the skill at its own docs. Set the top-level docs_base_url field in .git-paw/config.toml:

docs_base_url = "https://docs.example.com/git-paw"

The helper resolves the base URL from config, falling back to the built-in default when the field is absent. Both find and get target the configured URL.

Best-effort by design

Documentation lookup is an aid, never a hard dependency of the agent’s task. If the docs site is unreachable, a page does not exist, or an anchor is unknown, the helper exits non-zero with a short diagnostic (and never hangs — every fetch is time-bounded). The skill instructs the agent to continue its task without the docs in that case rather than block or retry in a loop.

Least-privilege install

git paw init installs docs-fetch.sh into .git-paw/scripts/ alongside broker.sh, sweep.sh, and the shared _paw_common.sh preamble they source. Nothing is seeded into the agent CLI’s settings file, and git-paw approves no prompt for it. The point of the stable path is that you can write one permission rule naming .git-paw/scripts/docs-fetch.sh — invoked directly or as bash <path> / sh <path> — instead of a broad curl wildcard. That one by-path rule covers every subcommand, so an agent consults the docs without any wildcard network permission. Without such a rule the helper simply prompts like any other command, and the prompt is escalated to you.

Governance

git-paw can read your team’s existing governance documents as context for the supervisor agent — ADRs, a test strategy, a security checklist, a Definition of Done, and a project constitution. You point at the docs in .git-paw/config.toml; git-paw does not generate or vendor any of them.

Why governance docs

The supervisor is an LLM. It does the right thing more often when it can see the rules the team has already written down: which architectural decisions are settled, what tests are expected, what security review looks like, what “done” actually means. Without that context the supervisor has to infer from the diff alone, and inference drifts.

git-paw’s stance on governance is deliberately thin:

  • You own the documents. Their structure, format, and rubric are whatever your team already uses (Scrum, XP, OWASP, adr-tools, Spec Kit, hand-rolled). git-paw does not template them.
  • You opt in per doc. Empty [governance] table, all fields None, no behaviour change from v0.4. Add only the docs you have.
  • The supervisor applies judgment. There is no [governance.gates] table and no per-doc enforcement switch. The supervisor reads each configured doc as context during its audit and surfaces relevant findings via agent.feedback.

Pointing at your docs

Add [governance] to .git-paw/config.toml:

[governance]
adr = "docs/adr"
test_strategy = "docs/test-strategy.md"
security = "docs/security-checklist.md"
dod = "docs/definition-of-done.md"
constitution = ".specify/memory/constitution.md"

All five fields are optional. List only the docs you have. Paths are resolved relative to the repository root; absolute paths are accepted as-is. A missing file does not break config-load — the runtime flags it if the supervisor tries to read it.

[governance] also accepts two more optional pointers, readme and docs (your README and documentation root). They feed the MCP server’s documentation tools rather than the supervisor’s audit.

For Spec Kit projects, governance.constitution auto-wires to .specify/memory/constitution.md when [specs] type = "speckit". You only need to set constitution explicitly if you keep it somewhere else, or want to disable the auto-wiring (set it to "").

At launch, the configured paths reach the orchestrator as a ## Governance documents section in its context — the gitignored repo-root sidecar .git-paw/AGENTS.local.md that git paw start --supervisor writes and names, and that your orchestrating agent session (outside tmux) reads — placed after the supervisor skill content so the orchestrator reads them in the same context as its own instructions. The tracked AGENTS.md is never modified, and the context carries no per-agent spec or file-ownership content. A path left unset — or set to the empty string, as with the constitution = "" opt-out above — contributes no bullet, and a [governance] table with no paths at all adds no section.

See Configuration → Governance for the full field reference and merging rules.

Illustrative examples

The shapes below are examples, not templates. git-paw never reads structure — only the content of the file you point at. Use whatever format your team already uses.

ADR-0001 — Adopt PostgreSQL for the primary store

# ADR-0001: Adopt PostgreSQL for the primary store

Status: Accepted
Date: 2026-02-14
Deciders: backend team

## Context

We need a primary datastore for user, session, and billing tables.
Options considered: PostgreSQL, MySQL, CockroachDB, DynamoDB.

## Decision

We will use PostgreSQL 16 hosted on our existing managed service.

## Consequences

- All new schema lives in PostgreSQL; we will not introduce a second
  relational store without a follow-up ADR.
- Migrations go through `sqlx-migrate`; ad-hoc DDL in code review is
  rejected by default.
- Read replicas are an operational concern, not an application one —
  applications connect through the connection pool, not directly to
  replicas.

A typical ADR directory has one file per decision (0001-postgres.md, 0002-event-bus.md, …). Whether you use Nygard, MADR, or your own headings does not matter — git-paw passes the directory pointer to the supervisor, which reads the files it finds.

Definition of Done

# Definition of Done

A change is "done" when every box below is checkable:

- [ ] All new behaviour has at least one test covering the happy path
      and one covering the failure mode.
- [ ] `just check` passes locally and in CI.
- [ ] Public functions have rustdoc comments.
- [ ] If a config field was added, the configuration docs section
      that owns it is updated.
- [ ] If a CLI flag was added, `--help` text and `cli-reference.md`
      are updated.
- [ ] If user-visible behaviour changed, the changelog has an entry.
- [ ] The PR description names the spec/issue it implements and
      lists any deviations.

Anything left unchecked goes in the PR description as a known
follow-up with an owner.

Security checklist

# Security Checklist

For every change touching authentication, authorisation, network I/O,
or persisted data:

- [ ] User input is validated at the boundary (HTTP handler or CLI
      arg parser), not in the business logic.
- [ ] Secrets never appear in logs, error messages, telemetry, or
      stack traces. The redaction helpers in `crate::log::redact`
      are applied at the producer side.
- [ ] No new dependency on `unsafe` crates without a written
      justification in the PR description.
- [ ] If the change introduces a new external request, the URL is
      built with a typed builder, never with `format!` over user
      input.
- [ ] Authorization checks happen before side effects — never after.

Test strategy

# Test Strategy

## Pyramid

We invest most heavily in fast, deterministic tests:

- **Unit tests** (`#[cfg(test)] mod tests {}`) — pure logic, no I/O.
  Every public function in `src/` has at least one.
- **Integration tests** (`tests/`) — exercise CLI boundaries with
  `assert_cmd`. Use real filesystems via `tempfile`, never mocks.
- **End-to-end smoke tests** (`tests/e2e/`) — kicked off in CI for
  the happy path of each subcommand. Slow and few; tagged with
  `#[ignore]` so contributors can opt in locally.

## What we do not test

- Generated output of `--help` strings (already covered by clap).
- Rendering loops in the TUI — covered manually via the smoke list
  in `docs/src/user-guide/dashboard.md`.

## Test data

Always synthesise — never copy production data, even anonymised, into
fixtures. Fixtures live under `tests/fixtures/` and are kept small
enough to read at a glance.

Constitution (Spec Kit-style)

# Project Constitution

## Spec-driven

No behaviour ships without a spec. The spec is the contract; the
implementation matches the spec; the tests assert the spec. If the
implementation drifts from the spec, the spec is updated first and
re-reviewed.

## Behavioural tests, not implementation tests

Tests assert observable inputs and outputs. Tests do not assert
internal struct field values, internal function calls, or mock
interactions.

## One commit per logical change

Every commit builds, passes `just check`, and is independently
revertable. PRs may contain multiple commits, but no commit may leave
the tree broken.

Spec Kit users typically keep this file at .specify/memory/constitution.md; git-paw auto-detects it when [specs] type = "speckit".

What the supervisor does

When the supervisor verifies an agent’s change, it reads the configured governance documents alongside the diff. Findings flow through the existing agent.feedback channel — the supervisor does not crash or block on governance, it surfaces what it sees and lets the agent respond.

How the supervisor audits against these documents is described in the bundled supervisor skill, and the MCP server’s governance tools serve the same documents to MCP clients. This chapter and the [governance] config table (the core-governance-config capability) are the path-pointer slot only.

Rollout suggestion

Adopt incrementally — there is no requirement to fill in all five paths at once:

  1. Start with the doc you already have. Point governance.dod at your existing DoD, or governance.constitution at your principles doc.
  2. Run a supervised session. Note which findings feel useful and which feel noisy.
  3. Add the next doc. Each pointer narrows the gap between what the supervisor knows and what the team already agreed on.
  4. If the team does not have one of these docs at all, write the smallest version you can live with rather than copying a template. The supervisor reads what you wrote — not a generic checklist.

You do not need a full governance framework to benefit from this. A two-paragraph DoD pointed at by governance.dod is more valuable than a perfect-but-empty [governance] table.

Learnings Mode

Learnings mode records deterministic friction signals from a supervisor session into a markdown file you can review after the run. It is an opt-in feature: it requires supervisor mode to be active and the [supervisor] learnings = true flag to be set explicitly. The markdown file is always written; when the broker is enabled, each record is additionally published as an agent.learning broker message (see Programmatic consumption).

Contents

Why

Supervisor runs absorb a lot of recurring friction silently: sandbox warnings the agent retries past, approvals you reflexively click, brief stuck states, conflicts caught and resolved before they hit your eyes. Each event is too small to interrupt for, but the pattern is the most useful signal git-paw can surface for tool, prompt, or process improvement.

Learnings mode aggregates those events into four deterministic categories (plus the supervisor’s qualitative signals) and writes them to a markdown file you can read between sessions — turning silent friction into something you can act on.

Privacy & Sharing

Learnings mode performs no telemetry. The aggregator writes to exactly one place — the local .git-paw/session-learnings.md file in your repo. The broker it observes binds to 127.0.0.1 by default, and git-paw ships no outbound HTTP client: nothing about your session is collected, uploaded, or phoned home under any configuration. The feature is also fully opt-in — it runs only when you set [supervisor] learnings = true (default false), and a session that has not opted in behaves exactly as if the feature did not exist.

That makes the learnings file yours: a purely local artifact you can read, keep, or delete. When a session starts with learnings enabled, git-paw prints a one-line reminder of where the file lives and that nothing leaves your machine, so the privacy stance is visible at the point of use, not just here.

Optional sharing. If a session surfaces a recurring rough edge that looks worth fixing in git-paw itself, the most useful thing you can do is share that context with the maintainers. This is a deliberate, manual action — there is no “share now” command and no automatic upload. To contribute, open an issue on the git-paw issue tracker and attach (or paste) the relevant part of your session-learnings.md.

Review before you share. The file contains repo-specific details — branch names, file paths, spec IDs — that you may not want public. Read it first and strip or anonymise anything sensitive. git-paw deliberately does not scrub the file for you: only you know what is sensitive in your repo. Your own LLM or agent CLI can help here — ask it to redact paths and identifiers before you paste the result into an issue.

Enabling Learnings Mode

Set the master switch on the [supervisor] table (default false):

[supervisor]
enabled = true
learnings = true

The subsystem only activates when supervisor mode itself is active. With [supervisor] enabled = false (or no [supervisor] section), the learnings = true value is parsed and ignored — no aggregation runs and no file is written.

Output File

When active, the learnings subsystem writes to:

.git-paw/session-learnings.md

The file lives in the repository root’s .git-paw/ directory. It is:

  • Append-only across sessions. Each session’s first flush adds a ## Session Learnings — <ISO-8601 UTC start time> heading, and later flushes and later sessions append below it; nothing is overwritten or pruned.
  • Human-readable markdown. No JSON or binary; you can tail, grep, or open it in any editor between sessions.
  • Not committed by default. git paw init adds it to the project’s .gitignore alongside the other .git-paw/ runtime files.

The Deterministic Categories

The aggregator tracks four deterministic categories, each written under its own ### heading in the session’s block. Each bullet is a one-line summary; a category with nothing to report is omitted from that flush.

Conflict events

One bullet per conflict the broker conflict detector reported via a [conflict-detector]-tagged agent.feedback or agent.question, classified as:

  • Forward conflicts — two agents’ agent.intent declarations overlap (recorded with a forward-conflict-… label). Use these to spot specs that were decomposed too coarsely (two parallel agents both expected to own the same file). A forward conflict is labelled forward-conflict-intra-spec when both agents were launched from the same spec and forward-conflict-cross-spec when they came from different ones, using the spec each agent was launched from. The broker host learns that mapping from real launch data: a fresh launch registers every agent with the host together with its spec id, git paw add --from-spec does the same for a hot-added agent, and a recovered or resumed session’s host reads it from the saved session. An agent started without a spec (a bare git paw start, or git paw add without --from-spec) has no recorded spec, so its forward conflicts classify cross-spec; only the specs that are known are named in the bullet.
  • In-flight conflicts — two agents are modifying the same file at once.
  • Ownership violations — an agent edited a file claimed by another agent’s active agent.intent. Records the violator, the owner, and the file. Use these to spot agents that drifted out of their declared scope.

The detector’s symmetric warnings (one per agent in the pair) collapse into a single bullet.

Where agents got stuck

Stuck duration: the time between an agent’s agent.blocked (naming the agent it waits on in from) and its next agent.artifact. A block still open when the session ends is recorded as unresolved, with the duration measured up to shutdown.

Recovery cycles

The recovery-cycle count: how many agent.feedback messages an agent received before its agent.verified (or before session end, if it never verified). A high count marks an agent that needed many correction rounds. Zero cycles is not recorded.

Conflict acknowledgements are excluded from the count. A [conflict-ack] feedback (see Conflict Detection) tells an agent that an overlap it was warned about has been adjudicated — it asks the agent for nothing, so counting it would report correction rounds that never happened.

Permission patterns

Commands that repeatedly needed a human decision. git-paw approves no permission prompt, so this is not a count of automatic approvals: under --unattended, the first time the drive loop escalates a given command it emits a permission_pattern learning (when manual_approvals_log is also on). The list shows which commands your agents trip on most, so you can pre-approve them in your agent CLI’s own allow rules. git paw approvals reports the same patterns with their full counts.

Qualitative signals (v0.6.0)

The four categories above are deterministic — the broker derives them mechanically from message traffic. As of v0.6.0 the supervisor also records qualitative signals: observations that require reasoning over the whole session, which only the supervisor LLM can make during its normal sweep and audit work. They ride the same agent.learning wire format and land in their own sections of session-learnings.md. The supervisor publishes them through the bundled .git-paw/scripts/sweep.sh learn <category> <title> <body-json> helper — a least-privilege, by-path publish path, never a raw curl.

Because these are LLM judgments, the supervisor skill gates each one behind a heuristic and a “do not publish unless…” rule, and suppresses near-duplicates within a session. The aim is a short, high-signal list, not an exhaustive log — if a category is empty in a run, the supervisor simply didn’t see a confident instance.

Recurring failure shapes

The same error shape recurring across multiple feedback cycles from different branches (e.g. three branches all hitting the same import-cycle pattern). What to do: treat it as a systemic signal — a missing lint, an undocumented constraint, or a fixture/scaffold gap — rather than fixing each branch in isolation.

Documentation gaps

A spec audit found a convention the spec assumes but no checked-in doc explains. The record names the convention, the doc paths checked, and a suggested home for it. What to do: add the convention to the suggested doc (git-paw does not write docs for you — see Non-goals below).

ADR / architectural drift

Code introduced an architectural decision — a new dependency, framework, or boundary — not reflected in your configured ADRs. The record names the decision_area, the observed pattern, and a candidate ADR title. What to do: decide whether the drift is intentional (write the ADR) or accidental (revert or rework it).

Scope-mistake signals

Two or more branches coordinated heavily because the original spec scope drew the boundary in the wrong place. The record names the branches, the shared files, and a suggestion. What to do: consider re-cutting the scope for the next change so the same work lands in one branch.

Tooling friction

Friction the supervisor absorbed about git-paw itself — a tool behaviour that made it repeat work or work around the tool (a permission prompt cleared on every sweep, a helper too narrow so it fell back to raw curl, a detector that over-escalated) — as distinct from the four project-scoped categories above. The record names the friction, the occurrences (how many times it was absorbed this session), and a suggestion. The supervisor publishes it only when the same friction was absorbed at least twice in the session, so a one-off is never recorded. What to do: treat it as the prime tool-improvement signal — fold it into a git-paw issue or a config/allowlist change.

Correction-budget exhaustion

Emitted by the --unattended drive loop (not by the supervisor skill) when a branch spends its whole [supervisor.correction] max_cycles budget without converging on a passing gate. The record carries the branch, the worker cli, and the cycles spent, under category correction_exhausted. What to do: read it as a tiered-model tuning signal — a branch that burns its whole budget is usually a task whose worker model is under-powered for that class of work, so re-assign it to a stronger CLI or split the spec. Like every learning it is emitted only when [supervisor] learnings = true.

Unknown categories

Records whose category the aggregator doesn’t recognise (e.g. a category added by a newer supervisor skill) are never dropped — they appear under an Other learnings section so nothing is lost across version skew. The correction_exhausted category above renders there too, as a title plus its JSON body.

Non-goals. Qualitative signals flag issues; they do not fix them. git-paw does not auto-generate docs or ADRs, and it does not scan past sessions for cross-session patterns — each observation belongs to the session that saw it. The detection thresholds live in the supervisor skill prose, not in config (see below); if a category is too noisy or too quiet for your project, edit your local copy of the supervisor skill.

Sample Output

A short illustrative excerpt (agent names, paths, and counts are made up; headings and bullet shapes are the ones the aggregator writes):

## Session Learnings — 2026-05-13T14:30:05Z

### Conflict events
- forward-conflict-cross-spec: feat-api and feat-auth
- in-flight-conflict: feat-api and feat-billing
- ownership-violation: feat-api edited `src/auth/jwt.rs` owned by feat-auth

### Where agents got stuck
- feat-api: blocked 11m12s waiting on feat-auth
- feat-billing: blocked 4m03s waiting on feat-api (unresolved at session end)

### Recovery cycles
- feat-auth: 3 feedback cycles before verifying

### Permission patterns
- `cargo test` escalated for a human decision (first seen; see `git paw approvals` for counts)

### Recurring failure shapes
- import cycle between the auth and session modules: 3 instances across feat-auth, feat-api, feat-billing

### Documentation gaps
- agents run the linter before committing — add a Conventions section to AGENTS.md naming the pre-commit lint step
## Session Learnings — 2026-05-13T16:05:41Z
...

The exact bullet wording may evolve across releases; the category set and the underlying triggers above are stable.

Flush Cadence

The aggregator buffers entries in memory and flushes to disk on an interval (default 60s):

[supervisor.learnings_config]
flush_interval_seconds = 60

A shorter interval makes the file fresher (useful when you want to tail -f the file in another terminal); a longer interval batches more entries per write. The file is also flushed once more at broker shutdown so nothing is lost between sessions even if the interval has not elapsed.

See Configuration → Learnings mode tuning for the field reference.

Programmatic consumption: agent.learning (v0.6.0)

v0.5.0 shipped learnings as file output only. As of v0.6.0 the aggregator also publishes each flushed entry to the broker as an agent.learning message, so tools no longer have to re-parse the Markdown. The file output is unchanged — it remains the source of truth for cold-repo / no-broker scenarios — and the broker publish is purely additive.

Each agent.learning record carries:

FieldMeaning
idDeterministic 16-hex-char dedup hash (see below)
agent_idPublishing agent (supervisor for aggregator-produced records)
branch_idBranch the record is scoped to; omitted for cross-cutting ones
categoryconflict_event, stuck_duration, recovery_cycles, permission_pattern (open set)
titleShort human-readable summary (mirrors the Markdown bullet)
bodyCategory-specific structured object
timestampISO 8601 UTC

The id is a 64-bit FNV-1a hash (16 lowercase hex characters) of the canonical form (category, branch, body sorted, UTC hour bucket). FNV-1a is stable across Rust toolchain versions — each id is deterministic regardless of when or where it is computed. It is a dedup key, not a security primitive. Re-publishing the same logical record within one hour yields the same id, so a consumer can dedupe re-emissions; a genuine recurrence in a later hour gets a fresh id. This lets the aggregator re-run a sweep idempotently without flooding consumers with duplicates.

The bundled sweep.sh learn helper uses the same scheme. The supervisor publishes qualitative learnings through .git-paw/scripts/sweep.sh learn <category> <title> <body-json>, and that helper computes its id with exactly the form above — over the category, an empty branch id (supervisor learnings are cross-cutting), the canonical body, and the current UTC hour bucket. The two publishers therefore assign the same id to the same logical record, and a consumer can dedupe across both. The title is NOT part of the canonical form, so rephrasing it does not mint a new record.

Because the canonical form has to render byte-identically on both sides, the helper accepts only numbers it can reproduce exactly: 64-bit integers. A body carrying a non-integer number (0.5, 1e16) or an integer beyond the 64-bit range is refused with a diagnostic and nothing is published — the two serialisers disagree on those (1e+16 against 1e16), and a record published under an id the aggregator cannot reproduce would silently defeat the dedup. Learning bodies carry counts and labels, so this costs nothing in practice.

Note: record ids changed once in this release (v0.17.0): the aggregator’s hash moved from platform-dependent DefaultHasher to stable FNV-1a, and sweep.sh learn — which had been computing a truncated SHA-1 over a different canonical form, giving the same record two different ids depending on which publisher emitted it — moved onto the shared scheme. Existing ids from v0.16.x are now invalid; re-run your aggregator to regenerate them.

Branch-scoped records (stuck_duration, recovery_cycles) are routed to that branch’s inbox and are retrievable with:

curl -s http://127.0.0.1:9119/messages/<branch-id>

Cross-cutting records (conflict_event, permission_pattern) carry no branch_id and land in the supervisor inbox. Every record is also retained in the broker’s full message log (/log).

The MCP get_learnings() tool reads the Markdown file, not the broker: it returns each ### section as a { category, entries } record, and returns the four deterministic sections with empty entries when no file exists yet.

Publication is controlled by the broker_publish knob (default auto, which follows [broker] enabled); see Configuration → Learnings mode tuning.

Conflict Detection

When supervisor mode is active, the broker runs an in-process conflict detector that watches for three failure shapes across parallel agents. Detected conflicts surface as [conflict-detector]-tagged agent.feedback delivered to the involved agents, and — for unresolved in-flight shapes — as agent.question escalations to the supervisor inbox. The detector is automatic; you only opt out by setting [supervisor] enabled = false (or tuning the knobs in [supervisor.conflict]).

Contents

The Three Failure Shapes

git-paw’s own managed bookkeeping — everything under a worktree’s .git-paw/ directory (config, bundled scripts, session state) — is excluded from the overlap computation for forward and in-flight conflicts. Every worktree shares this untracked scaffolding, so its presence in modified_files is not evidence of a code conflict: two agents both reporting .git-paw/config.toml never fabricates a warning, while a genuine source overlap alongside it (src/a.rs present in both) still conflicts on the source path.

Forward conflict

Two agents publish agent.intent payloads whose files arrays overlap. The conflict is forward because it surfaces before either agent commits — intent is the early-warning channel.

Trigger. Any non-empty intersection between two active intents (an intent stays active until its valid_for_seconds TTL expires or the agent publishes a fresh intent, which replaces it). Each pair is warned once; the warning can fire again only after one side’s intent changes its file set or expires.

Action. Each agent receives an agent.feedback with the [conflict-detector] tag in its errors[0], naming the peer and listing the overlapping paths. Neither side blocks; the receiving agents decide how to retract or reshape their plans.

Region granularity (v0.6.0). When both intents declare regions for a shared file (function / class / block / range — see Declaring regions), the detector triggers only when the declared regions intersect, and the warning names the intersecting regions. When either side omits regions it falls back to the file-level trigger above, preserving v0.5.0 behaviour. Cross-kind comparisons (a named region vs a line range) intersect conservatively and append a hint.

Toggle. [supervisor.conflict] warn_on_intent_overlap (default true). When false, the detector still records intents (so in-flight classification and ownership detection keep working) but no agent.feedback fires for forward shape.

In-flight conflict

Two agents’ current agent.status modified_files sets overlap on the same file. The conflict is in-flight because both sides are already writing.

Trigger. Any non-empty intersection between one agent’s latest agent.status modified_files and another agent’s latest agent.status modified_files (each new status replaces that agent’s previous set). agent.artifact does not feed this check, and declared intents only decide how an unresolved overlap is classified (below).

Action. Both agents receive [conflict-detector]-tagged agent.feedback. The detector starts a window_seconds timer; if no side retracts before it elapses, the detector classifies the overlap from the two agents’ declared intent regions for the shared file and acts:

  • True collision — the declared regions intersect (same named region, same insertion anchor, overlapping line ranges, or a conservative cross-kind match), or either side declared no regions for the file (a file-level intent, or no active intent at all). The detector escalates to the supervisor inbox via agent.question (see Supervisor Inbox Routing).
  • Additive overlap — both agents declared regions for the file and the region sets are disjoint (well-separated hunks or differently named regions). The detector downgrades to a single informational [conflict-detector]-tagged agent.feedback delivered to both agents (“shared file, additive — resolve at merge”) and does not escalate to the human. The overlap is still recorded: the triple is marked decided so it neither re-escalates nor re-emits the downgrade on later ticks, and it clears only when one agent stops touching the file — so an additive overlap is never silently dropped.

The downgrade requires both sides to have declared regions; whenever the region data is insufficient to prove the hunks are disjoint, the detector escalates, preserving the conservative default.

Window. [supervisor.conflict] window_seconds (default 120).

Escalations persist across commits. The overlap is as volatile as modified_files: an agent commits, the watcher publishes an empty set, and the overlap vanishes for a tick or two before the file reappears. The decision — that this pair and this file have already been put to a human — is not volatile, and the detector keeps it separately from the live overlap view. So a commit does not re-escalate a conflict that has already been escalated, and it does not rewind the window timer for one that has not: an overlap first seen 100 seconds ago still escalates at the 120-second mark, not 120 seconds after the commit. A decision is discarded only once its conflict is genuinely over — the overlap has been continuously absent for the escalation window (at least 60 seconds) — or when the two agents’ declared regions for the file describe a materially different overlap.

Acknowledging an escalation. Once you have adjudicated an escalation, record it so the detector stops asking:

.git-paw/scripts/sweep.sh conflict-ack <agent-a> <agent-b> <file> "optional note"

This publishes a [conflict-ack]-tagged agent.feedback from supervisor to both agents. A note you pass rides along as a second errors entry so the agents see your reasoning; if you pass none, the helper supplies a default one — agents are taught that errors are problems to fix, and a bare tag line would read as a cryptic error.

While the acknowledgement stands, no further agent.question is emitted for that pair and file — but the detector keeps tracking the overlap rather than dropping it, so a later escalation can still be built on it. Acknowledging suppresses the question, not the fact. (No view currently renders the in-flight tracker, so “keeps tracking” means exactly that: the detector’s own state, not something you can read off the dashboard.)

The acknowledgement is invalidated automatically when the two agents’ region declarations for that file change materially — judged by the same intersection rules that drive the additive-versus-true classification above. A conflict that genuinely changes shape can therefore escalate again: an agent re-declaring agent.intent with a range that now overlaps its peer’s, or widening its claim from a named region to the whole file. An intent merely expiring is not a change — otherwise every settled conflict would re-open one TTL later.

What the supervisor-only rule does and does not do. The detector honours an acknowledgement only when the carrying agent.feedback has from = "supervisor". That filter stops an agent’s ordinary feedback from being mistaken for an adjudication. It is not an authentication boundary: the broker does not authenticate senders, from is self-asserted, and sweep.sh is present in every worktree — so an agent that set out to do so could publish conflict-ack for a conflict it is party to. What limits the damage is visibility, not prevention: an acknowledgement published through the helper reaches both named agents, and every acknowledgement — however it was published — stays in the broker message log. (The detector honours one whatever its envelope agent_id, so a hand-rolled one need not have been delivered to both; the log is what you audit.) Sender authentication is part of the v0.18.0 broker-authority work.

Ownership violation

An agent’s modified_files (from its agent.status) touches a file that another agent has claimed in its active agent.intent. Ownership is intent-based: a file is owned by whichever agent’s non-expired intent declares it.

Trigger. Any path in the agent’s modified_files that lies outside its own active intent (or the agent has no active intent) and inside another agent’s active intent.

Action. The violator receives agent.feedback naming the touched file and the agent whose intent claims it; each violator/file pair is warned once. When [supervisor.conflict] escalate_on_violation = true (default), the supervisor inbox also receives a follow-up agent.question so a human can decide whether to override the boundary or block the work.

Exemption. The shared spec-task-tracking artifact — the tasks file git-paw directs every agent to update with its own checkbox (a Spec Kit specs/<feature>/tasks.md or the OpenSpec tasks.md) — is exempt from ownership-violation detection. git-paw’s own writeback protocol has every agent tick its own line in that single shared file, so a writeback there is expected coordination, not a collision. The exemption only covers that designated task-tracking artifact; ownership violations on source files are still reported.

The [conflict-detector] Tag

Every auto-emitted agent.feedback from the detector carries a single errors entry that starts with a fixed tag:

{
  "type": "agent.feedback",
  "agent_id": "feat-auth",
  "payload": {
    "from": "supervisor",
    "errors": [
      "[conflict-detector] forward conflict: agent feat-api also intends to modify: src/auth/middleware.rs"
    ]
  }
}

The [conflict-detector] prefix distinguishes detector output from human-typed supervisor feedback. Agents (and dashboards) that filter or classify feedback can match on the tag without parsing payload semantics.

One further tag travels the other way. An agent.feedback from supervisor whose errors entry reads [conflict-ack] <agent-a> <agent-b> <file> is read by the detector as an acknowledgement of that conflict (see In-flight conflict); sweep.sh conflict-ack shapes it for you. The file path runs to the end of its errors entry, so the tag line carries nothing after the path — add any prose as a separate entry. The detector never emits [conflict-ack] itself.

The detector publishes from the "supervisor" agent ID (the from field in the payload) — same source as human-authored supervisor feedback — because routing and display logic already specialise on the supervisor identity. The tag, not the source, is the discriminator.

Supervisor Inbox Routing

When an in-flight conflict has not resolved within window_seconds and is classified a true collision (see In-flight conflict), the detector escalates to the supervisor by publishing an agent.question addressed to the supervisor (not to either of the conflicting agents). Additive overlaps are downgraded to an agent.feedback and never land in this inbox. The orchestrator (your own agent session outside the tmux session) sees the question in its broker inbox and can:

  1. Type a reply, which the supervisor skill forwards to both involved agents via tmux send-keys (the same dual-write pattern documented in Agent Coordination § Supervisor Acknowledgement).
  2. Resolve the conflict directly by editing one agent’s intent or pausing the offending agent until the other side commits.

Ownership-violation escalations follow the same routing. Forward conflicts never escalate — they are advisory.

Interaction with the Filesystem Watcher

The broker’s filesystem watcher polls git status --porcelain in each worktree every ~2 seconds and publishes agent.status (with a fresh modified_files array) whenever the set of reported paths — modified, staged, or untracked — changes. The conflict detector consumes these auto-published status messages, so in-flight conflicts surface as soon as edits land on disk — no manual agent.status curl required from the agent.

The watcher only reads git status — it never stages or commits — so the detector sees overlaps within one poll of a file being modified, even before git add or git commit. This is the mechanism that makes “in-flight” meaningfully earlier than the post-commit hook’s agent.artifact { status: "committed" }.

Configuration Knobs

All knobs live under [supervisor.conflict] in .git-paw/config.toml:

[supervisor.conflict]
window_seconds = 120
warn_on_intent_overlap = true
escalate_on_violation = true
FieldDefaultDescription
window_seconds120Seconds to wait before escalating an unresolved in-flight conflict to the supervisor inbox via agent.question.
warn_on_intent_overlaptrueForward-conflict feedback toggle. When false, intents are still tracked but no agent.feedback is emitted on intent overlap.
escalate_on_violationtrueOwnership-violation escalation toggle. When false, the violator still receives agent.feedback, but no follow-up agent.question lands in the supervisor inbox.

The [supervisor.conflict] table is fully optional. Setting [supervisor] enabled = false (or omitting the section) disables the detector subsystem entirely — no auto-emitted feedback fires regardless of the values above. See Configuration → Conflict detector tuning for the canonical field reference.

MCP Server

git paw mcp runs a Model Context Protocol (MCP) server over stdio, so any MCP-aware client — Claude Desktop, Cursor, ChatGPT Desktop, Windsurf, VS Code MCP extensions — can query a repository’s state without an active git-paw session.

It exposes the same information git-paw already tracks as deterministic, read-only tools, beside a bounded write surface (publish tools, and session lifecycle and verdict tools for a root session only — see below). The read-only tools cover:

  • Coordination — active agent intents and detected conflicts
  • Governance — your configured ADRs, test strategy, security checklist, Definition of Done, and constitution
  • Project knowledge — discovered specs and tasks (OpenSpec, Markdown, Spec Kit, Superpowers), the spec dependency graph, and rendered agent skills
  • Session state — the active session’s status/summary and the session-learnings file
  • Git context — branches, recent commits, and branch diffs
  • Source/Files — browse the local working tree, read a file, and search code contents (gitignored paths excluded; reads confined to the repo root)

Alongside those, the server exposes a bounded write surface: the Publish tools for every session, and — for a root session only (a server started in the repository’s main working tree) — the Session lifecycle tools and the two verdict publish tools, plus — only once the repository opts in with [mcp] allow_repo_writes — the Spec authoring write tools. validate_spec is read-only and every session gets it.

The server is standalone: it does not need a tmux session, broker, or supervisor for the read-only categories (the publish tools do need a reachable broker — see below). When a data source is unavailable (no broker, no session, no governance config) read tools return well-formed empty/null results rather than errors — so the client always gets an unambiguous answer.

Read/write boundary. Every tool category except Publish tools, Session lifecycle and the two Spec authoring write tools is read-only, deterministically sourced from files, git, and broker state. The write surface is a fixed set of tools: the four worker publish tools (the same four events the bundled broker.sh helper covers), each publishing only as the calling session; and, for a root session only, the session lifecycle tools (exactly what git paw start / pause / stop / purge do) and the two verdict tools (publish_verified, publish_feedback, exactly what sweep.sh verified / feedback-gate do). The spec-authoring write tools, for a consenting repository’s root session, write only inside one spec change directory. There is no tool that writes a file outside a spec change directory, runs a caller-supplied git or shell command, or writes configuration, and no generic “publish anything” tool. A server in an agent’s worktree never lists or runs the root-only tools, so a coding agent cannot verify its own work through MCP — see Publish tools. The server also never invokes an agent CLI (claude, codex, …) as an inference backend; every result comes from files, git, and broker state.

How it works

The MCP client spawns git paw mcp as a child process and talks to it over the process’s stdin/stdout using newline-delimited JSON-RPC 2.0. The client owns the lifecycle: the server starts when the client launches it and exits cleanly when the client closes its stdin.

git paw mcp [--repo <PATH>] [--log-file <PATH>]
FlagPurpose
--repo <PATH>Operate against a specific repository instead of the current directory. Required for Claude Desktop (see below).
--log-file <PATH>Also write diagnostics to a file. stderr is always used; stdout is reserved for the JSON-RPC stream.

Verbosity follows the standard RUST_LOG convention (default warn):

RUST_LOG=debug git paw mcp --repo /path/to/repo

Repository resolution

  1. If --repo <PATH> is given, the server uses that path (it must be inside a git repository).
  2. Otherwise the server walks up from the current directory to the nearest enclosing git repository. Inside a git worktree, it resolves to the worktree’s own root.

If no repository can be found, the server prints a clear error to stderr and exits non-zero — it never silently serves nothing.

Finding the session and its broker from inside a worktree. File-serving tools keep the resolution above: inside a worktree, the repository IS that worktree. The session lookup is deliberately wider — a worktree root never equals the repository root, so matching it directly would leave a server started by an agent’s CLI (its working directory is the agent’s worktree, and it is given no --repo) with no session and no broker URL. The server therefore resolves a worktree to the repository that owns it before looking the session up, trying the exact path first and falling back to the owner only on a miss. get_session_status and the broker URL in the server’s context come from that lookup.

This widening is confined to the MCP server’s read-only view. Every git paw command that mutates session state — stop, purge, add, remove, pause, and start’s recover/resume dispatch — keeps the exact match, so running one inside an agent worktree never acts on the parent session.

Server identity

In the MCP initialize handshake the server advertises serverInfo.name = "git-paw" and its real crate version. To distinguish several repositories that each run git paw mcp, set a custom name in the repo’s .git-paw/config.toml:

[mcp]
name = "my-project"   # advertised as serverInfo.name; defaults to "git-paw"

This is the server’s own advertised identity. It is distinct from the display label some clients show, which comes from the mcpServers key in the client config (e.g. "git-paw" in the snippet below) — that key is yours to rename per client entry regardless of [mcp].name.

Per-client setup

Each client needs its own entry pointing at this server. The command is git with args paw mcp … (git-paw installs as a git subcommand). If you prefer, git-paw mcp … works too.

Claude Desktop

Claude Desktop spawns MCP servers from its own application-support directory, not your project — so current-directory discovery cannot work. You must pass --repo with an absolute path.

  1. Open the config file:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows: %APPDATA%\Claude\claude_desktop_config.json
  2. Add a server entry:

    {
      "mcpServers": {
        "git-paw": {
          "command": "git",
          "args": ["paw", "mcp", "--repo", "/absolute/path/to/your/repo"]
        }
      }
    }
    
  3. Restart Claude Desktop completely (quit and reopen).

  4. Verify: open a new chat, click the tools/🔌 icon, and confirm git-paw is listed with its tools. Ask “what specs are pending in this repo?” and confirm a tool call runs.

Cursor

Cursor is workspace-aware and spawns from the workspace root, so bare git paw mcp works (or pass --repo to be explicit).

  1. Create .cursor/mcp.json in your project (or edit the global ~/.cursor/mcp.json):

    {
      "mcpServers": {
        "git-paw": {
          "command": "git",
          "args": ["paw", "mcp"]
        }
      }
    }
    
  2. Reload Cursor (or toggle the server in Settings → MCP).

  3. Verify: open Settings → MCP and confirm git-paw shows a green status with its tools listed.

ChatGPT Desktop (macOS)

ChatGPT Desktop supports local MCP servers in Developer Mode. (ChatGPT Web in a browser is not supported — see Known limitations.)

  1. Enable Settings → Connectors → Advanced → Developer mode.
  2. Add a connector with command git and args paw mcp --repo /absolute/path/to/your/repo (ChatGPT Desktop, like Claude Desktop, does not run from your project directory, so pass --repo).
  3. Restart ChatGPT Desktop.
  4. Verify: start a new chat, open the connector/tools menu, and confirm git-paw and its tools are available.

Windsurf

  1. Open Settings → Cascade → MCP Servers → Add Server (or edit ~/.codeium/windsurf/mcp_config.json):

    {
      "mcpServers": {
        "git-paw": {
          "command": "git",
          "args": ["paw", "mcp", "--repo", "/absolute/path/to/your/repo"]
        }
      }
    }
    
  2. Refresh the MCP server list (or restart Windsurf).

  3. Verify: the MCP panel shows git-paw connected with its tools.

VS Code (MCP)

VS Code (1.102+) supports MCP servers. It is workspace-aware, so bare git paw mcp works from a workspace folder.

  1. Create .vscode/mcp.json in your workspace:

    {
      "servers": {
        "git-paw": {
          "type": "stdio",
          "command": "git",
          "args": ["paw", "mcp"]
        }
      }
    }
    
  2. Run MCP: List Servers from the Command Palette and start git-paw (or reload the window).

  3. Verify: in Agent mode, open the tools picker and confirm the git-paw tools appear.

Known limitations

  • ChatGPT Web is not supported. The browser version of ChatGPT cannot spawn local processes, which stdio MCP requires. ChatGPT Desktop on macOS works (with Developer Mode). The server speaks stdio only — there is no HTTP transport.
  • Per-repo configuration is required. Each repository needs its own entry in the client config (with its own --repo path where the client spawns from a fixed directory). For many repos this is repetitive; one server instance serves exactly one repository.
  • Claude Desktop (and ChatGPT Desktop) need --repo. They spawn MCP servers from their own app-support directory, not your project, so current-directory discovery cannot find your repo. Workspace-aware clients (Cursor, VS Code MCP, Windsurf) work with a bare git paw mcp.

Tool reference

Every category below is read-only except Publish tools, Session lifecycle and the two write tools of Spec authoring. For the read-only categories, collection results are empty ([]) and single-record results are null when the underlying data is unavailable. Governance tools return a protocol error only when a configured document path exists but cannot be read.

Coordination

ToolInputResult
get_intents—{ intents: [{ branch_id, files, regions, summary, published_at, valid_for_seconds }] }
get_intent{ branch_id }{ intent: <intent> | null }
get_conflicts—{ conflicts: [{ shape, branches, files, detected_at }] }
wait_for_events{ since?, timeout_seconds? }{ entries: [<broker message>], last_seq, epoch, wave }

wait_for_events is the MCP form of the broker’s long-pollable event log. since is a broker log sequence number (default 0) and timeout_seconds defaults to 30, capped at 60. The tool long-polls GET /log, so it returns as soon as an entry newer than since is appended, or when the timeout elapses — whichever comes first. The wave object reports wave staleness, so a call that times out with no entries still tells the caller whether the wave has stalled.

epoch identifies the broker instance that answered. Sequence numbers restart at zero whenever a broker does — a pause + start cycle, for instance — so a cursor is only meaningful against the epoch it came from. Record the epoch with your cursor and pass since: 0 on the next call whenever it changes, or you will hold a cursor above everything the new broker appends and time out on every call. See The broker epoch.

With no reachable session broker it returns at once with empty entries, last_seq equal to the since it was given, and no_session: true. It mutates nothing: like every tool outside Publish tools, it only reads broker state.

Governance

ToolInputResult
get_adrs—{ adrs: [{ id, title, path, status }] }
get_adr{ query }{ adr: { id, path, content } | null }
get_test_strategy—{ content: <string> | null }
get_security_checklist—{ content: <string> | null }
get_dod—{ content: <string> | null }
check_dod{ branch }{ branch, items: [{ text, complete }] | null }
get_constitution—{ content: <string> | null }

Project knowledge

ToolInputResult
get_specs—{ specs: [{ id, backend, title, status, path }] }
get_spec{ id }{ spec: { id, backend, path, artifacts: [{ name, content }] } | null }
get_tasks{ spec }{ tasks: [{ id, phase, parallel, description, complete }] }
get_task{ spec, id }{ task: <task> | null }
get_dependency_graph—{ nodes: [{ id, backend }], edges: [{ from, to }] }
get_skill{ name }{ skill: { name, content, source } | null, message? }

source is one of standard (.agents/skills/), user_override, or embedded. An unknown skill returns skill: null with a message — not an error. The orchestrator skill is served under the name orchestrator (supervisor is an alias returning the same content) — the same procedure git paw init exports to .git-paw/skills/orchestrator/SKILL.md.

Session state

ToolInputResult
get_session_status—{ session: { name, mode, status, paused, agent_count, broker_url, agents:[…], wave? } | null }
get_session_summary—{ summary: { name, status, agent_count, agents_by_status } | null }
get_learnings—{ sections: [{ category, entries:[…] }] }

get_session_status includes the broker’s wave object — the session’s wave-staleness reading (last_progress_unix_secs, idle_seconds, stall_threshold_seconds, stalled) — whenever a broker is reachable, and omits the field entirely otherwise.

Git context

ToolInputResult
get_branches—{ branches: [{ name, head, current, worktree }] }
get_recent_commits{ branch, limit? }{ commits: [{ sha, author, timestamp, subject }] } (limit defaults to 20)
get_diff{ branch, base? }{ base, branch, diff, files_changed, insertions, deletions } (base defaults to the repo’s default branch)

Source/Files

Browse and read the repository’s local working tree — tracked files plus untracked-but-not-ignored files. Gitignored paths (build artifacts, secrets) are excluded throughout, and read_file is confined to the repository root (canonicalise + starts_with guard, the same as get_doc): a path escaping the root (../, an absolute path, a symlink target outside the root) is refused, and a gitignored path is refused, both with null content and a message rather than a read. Reads return the on-disk working-tree content, so uncommitted/branch state shows. Implemented over git ls-files / git grep, so a non-git directory or a no-match search degrades to an empty result.

ToolInputResult
list_files{ subpath? }{ files: [<path>] } (tracked + untracked-not-ignored, relative to the repo root, optionally scoped to subpath; empty when not a git repo)
read_file{ path }{ content: <string> | null, message? } (path relative to the repo root; confined to the root and gitignore-respecting — a path escaping the root or naming a gitignored file is refused with null content and a message; null with a message when absent)
search_code{ query, subpath? }{ matches: [{ path, line_number, line }], truncated } (matches across the working tree, binaries skipped, optionally scoped to subpath; capped with a truncated flag; empty when no matches or not a git repo)

Documentation

Read-only access to the repository’s own documentation, driven by the bring-your-own [governance].readme and [governance].docs config paths (see below). Locations are configured, never hardcoded — unset paths degrade to null/empty results.

ToolInputResult
get_readme—{ content: <string> | null } (null when [governance].readme is unset or the file is absent)
list_docs—{ docs: [{ path }] } (Markdown files under [governance].docs, paths relative to that dir; empty when unset)
get_doc{ path }{ content: <string> | null, message? } (path relative to [governance].docs; confined to that dir — a path escaping it, e.g. ../, is refused with null content and a message, not a file read outside the directory)

The documentation tools read two optional [governance] paths in .git-paw/config.toml:

[governance]
readme = "README.md"   # path to the repository README
docs   = "docs/src"     # path to the documentation root directory

Both default to unset, in which case get_readme returns null, list_docs returns an empty list, and get_doc returns null — identical to the pre-v0.7.0 surface. list_docs walks the docs directory recursively for *.md files; the relative paths it returns feed directly back into get_doc.

Publish tools

Four worker tools, one per agent boot event, each publishing the same message the bundled broker.sh helper does — this is the MCP tool form of the boot block for a CLI that speaks MCP — plus two verdict tools for a root session only, each publishing the same message sweep.sh verified / feedback-gate does.

ToolInputResult
publish_status{ message }{ published: true } — agent.status (status:"working", the message, modified_files:[])
publish_artifact{ exports?, modified_files? }{ published: true } — agent.artifact (status:"done") — the code-less DONE fallback; a task that produces a commit reports via the post-commit hook instead
publish_blocked{ needs, from }{ published: true } — agent.blocked with dependency info
publish_question{ question }{ published: true } — agent.question, routed to the supervisor inbox
publish_verified (root session only){ agent, message }{ published: true } — agent.verified for agent, verified_by:"supervisor"
publish_feedback (root session only){ agent, gate?, errors }{ published: true } — agent.feedback to agent from supervisor; with gate, each error is tagged [<gate>]

There is no inbox or poll tool: an agent reads its broker inbox with .git-paw/scripts/broker.sh poll, whichever form its boot block uses.

Three properties are structural, not incidental:

  • Bounded, not generic. These are distinct tool definitions, not one publish(type, payload) tool. A message type is never an accepted parameter, so the set of things a client can publish cannot grow by passing a new string — it can only grow by this server shipping a new tool.
  • Session-scoped identity. No publish tool accepts an agent_id or publisher parameter. In an agent’s worktree, each derives the publishing id from the worktree’s branch (the same conversion the broker.sh poll/status path uses), so a session can only ever publish as itself. In the repository’s main working tree — the orchestrator’s — every publish tool publishes as the root identity supervisor, never as an agent named after the default branch. The verdict tools take the target agent as agent, as the helpers do, and set the verifier / sender to supervisor themselves.
  • Verdicts belong to the root session only. agent.verified is what authorises a branch to be merged; agent.feedback from the supervisor is its corrective channel. The broker accepts either only with the session’s authority credential, which lives outside the repository. publish_verified and publish_feedback are registered only for a server started in the main working tree, which attaches the credential itself (it is never a tool parameter and never appears in a result or an error). A server in an agent’s worktree does not list them, and a by-name call returns an MCP-level error naming the restriction, so a coding agent cannot self-verify through MCP.

Publish tools need a reachable broker — unlike the read-only categories, there is no meaningful “empty” result for a publish that did not happen. When no session is active, or the broker is unreachable, or it rejects the message, the tool returns an MCP-level error and the server keeps running; nothing is ever written to stdout outside the JSON-RPC protocol stream. The verdict tools read the session’s broker and credential when they run, so a root server that started the session itself (start_session) can publish verdicts without restarting.

Session lifecycle (root session only)

Offered only to a server started in the repository’s main working tree (as git reports it, so a --separate-git-dir checkout counts); a server in an agent’s worktree does not list them, and a by-name call returns an MCP-level error. Each does exactly what the equivalent git paw command does, in-process, and returns structured JSON with the command’s output lines and warnings.

ToolInputResult
start_session{ cli?, branches?, specs?, from_all_specs?, supervisor?, dry_run? }{ outcome, session_name, attach_command, orchestrator_context, output, warnings }
pause_session{}{ no_session, session_name, outcome, output, warnings }
stop_session{}{ no_session, session_name, outcome, output, warnings }
purge_session{ confirm }{ no_session, session_name, deleted, retained, output, warnings }
  • start_session is detached and never prompts. It launches (or revives — restarts from pause, recovers a stopped session) the repository’s session without attaching a terminal, and returns once the launch’s readiness gates have run, with the session name, the command a person runs to attach, and the orchestrator-context file (.git-paw/AGENTS.local.md). It has no unattended parameter: run git paw start --unattended yourself for an unattended wave. Nothing is asked interactively — pass branches or a spec selection, and cli unless the repository sets default_cli; a choice the command would otherwise put to a picker is refused with the parameter to pass. supervisor absent means the repository’s [supervisor] config decides, as a non-interactive git paw start does. dry_run reports the plan and creates nothing.
  • stop_session takes no confirmation. Stopping is recoverable (the worktrees, branches and state stay; start_session recovers it), as with git paw stop.
  • purge_session requires confirm: true and is annotated destructive (destructiveHint). It removes the tmux session, every worktree (including uncommitted changes in them) and the session state, as a confirmed git paw purge does. Branches git-paw created are deleted only when merged; a branch with unmerged commits is kept and listed in retained with its tip.
  • With no session, pause_session, stop_session and purge_session return { "no_session": true } rather than an error.

Authoring specs over MCP is a separate surface, gated by the repository’s own consent; see Spec authoring.

Spec authoring (repository writes, root session only)

Three tools let an orchestrator author and check an OpenSpec change without its own file tools. create_spec and write_spec_artifact write files into the repository working tree, so they are off by default. validate_spec only reads, and every session gets it.

ToolInputResult
create_spec{ name }{ written }
write_spec_artifact{ change, path, content }{ written, replaced }
validate_spec{ change }{ valid, findings: [{ file, message }] }

Consent. The two write tools are listed and run only when both of these hold:

  • The repository’s own .git-paw/config.toml sets [mcp] allow_repo_writes = true. A value in the user-level config has no effect, and no environment variable or tool argument turns it on.
  • The server runs in the repository’s main working tree (a root session). A server in an agent’s worktree never gets them, even with the switch on.

The server reads the switch once, at startup, and logs whether repository writes are enabled. Changing it in either direction takes effect only after you restart the MCP server. When a condition fails, the tools are absent from tools/list. A by-name call returns an MCP error and writes nothing. The error names allow_repo_writes and .git-paw/config.toml when the switch is off, or the worktree restriction in a worktree session.

The tools:

  • create_spec creates <[specs] dir>/<name>/.openspec.yaml with schema: spec-driven and today’s date as created: YYYY-MM-DD. That is the scaffold openspec new change <name> creates, so the consumer needs no OpenSpec CLI installed. name must be lowercase letters, digits and hyphens, and an existing change is refused.
  • write_spec_artifact writes content (at most 1 MiB) verbatim to path inside the existing change directory <[specs] dir>/<change>/. It refuses .openspec.yaml, which create_spec owns. It creates missing parent directories inside the change and replaces an existing regular file. replaced says whether it did. A change that does not exist is an error.
  • validate_spec runs an in-process structural check on a change. It reports a missing proposal.md, no specs/<capability>/spec.md with an ## ADDED, ## MODIFIED, ## REMOVED or ## RENAMED Requirements header, an added or modified requirement whose first body line has neither SHALL nor MUST, and an added or modified requirement without a #### Scenario:. valid is true exactly when findings is empty. It spawns no process and checks only this subset, so your project’s own spec validator stays authoritative.
{ "name": "write_spec_artifact",
  "arguments": { "change": "add-export",
                 "path": "specs/data-export/spec.md",
                 "content": "## ADDED Requirements\n..." } }
{ "written": ["openspec/changes/add-export/specs/data-export/spec.md"], "replaced": false }

Returned paths are repository-relative. Each write logs one line on the server log with the path, byte count and whether it replaced a file. The tools never stage or commit, so git status and git diff show exactly what was authored.

Confinement. Before writing, the server refuses the call with an MCP error naming the offending input, and writes nothing, when:

  • path is absolute or has a .. or .git component;
  • change is not a single kebab-case change name (lowercase letters, digits and hyphens);
  • the spec directory resolves outside the repository, inside its .git, to the repository root itself, or inside .git-paw;
  • a directory between the change directory and the target is a symbolic link resolving outside the change directory;
  • the target itself is a symbolic link.

Content goes to a temporary file in the target directory and is renamed into place.

OpenSpec only. The spec directory and format come from the [specs] section of the repository’s own .git-paw/config.toml; a user-level [specs] section is ignored here, as it is for consent. Under any other [specs] type the three tools return “not supported for format <type>”. With no [specs] section in the repository config they return an error naming [specs].

The switch does not govern the lifecycle and verdict tools above. Those follow the root-session rule alone.

Doctor

git paw doctor answers “why won’t it launch?” in one command. It runs read-only preflight checks over your environment, configuration, and repository state and prints a grouped report — every check carries a ✓, ⚠, or ✗, and every non-✓ check prints an actionable remedy.

git paw doctor
Environment
  ✓ git                     git version 2.39.5 (/usr/bin/git)
  ✓ tmux                    tmux 3.6a (/opt/homebrew/bin/tmux)
  ✓ git repository          the working directory is inside a git repository
  ✓ sandbox                 sandbox-exec available for optional worker confinement

CLIs
  ✓ detected CLIs           2 available: claude, codex

Config
  ✓ config.toml             .git-paw/config.toml parses
  ✓ worktree placement      child — worktrees live in <repo>/.git-paw/worktrees/

Spec system
  ⚠ spec system             no spec system configured — spec-driven launch is unavailable
      ↳ add a [specs] section to .git-paw/config.toml (type = "openspec" | …) or pass --specs-format
  ✓ spec-worker CLI         no default_spec_cli configured — spec workers fall back to default_cli or the interactive picker
  ✓ spec workflow           no spec system configured — the format declares no workflow entry point

Bundled scripts
  ✓ _paw_common.sh          installed, executable, matches this binary
  ✗ sweep.sh                .git-paw/scripts/sweep.sh is missing
      ↳ run `git paw init` to (re)install the bundled helper scripts
  ✓ broker.sh               installed, executable, matches this binary
  ✓ docs-fetch.sh           installed, executable, matches this binary
  ✓ python3                 Python 3.12.4 (python3)

Broker
  ✓ broker                  disabled — the pure-manual baseline (no agent coordination, no dashboard feed)

Supervisor
  ✓ supervisor              disabled — agents run without a supervising pane

Hygiene
  ✓ .gitignore              every git-paw entry is ignored
  ✓ session state           no stale session receipt
  ✓ worktree registrations  every registered worktree exists on disk

18 ✓ · 1 ⚠ · 1 ✗

Diagnose, don’t repair

Doctor never writes. No file, config, session, or other persistent state is created, modified, or deleted — it only reads. Apply the remedies yourself; most of them are git paw init.

There is deliberately no --fix flag. A repair mode was considered for v0.13.0 and cut to keep the pre-v1.0.0 surface minimal; if it ever lands it will re-run only safe, idempotent init steps.

Exit codes

The exit code is the worst check, so doctor works as a pre-launch or CI gate that blocks only on true blockers:

Worst statusMeaningExit code
✓everything checked out0
⚠something worth fixing that will not stop a launch0
✗a hard blockernon-zero

A ⚠ alone never fails the process. Ambiguous states (a version banner that cannot be parsed, a port probe that timed out) resolve to ⚠ rather than ✗, so doctor does not block you spuriously.

git paw doctor || echo "fix the ✗ findings before launching"

What it checks

GroupChecks
Environmentgit and tmux on PATH and at or above their minimum versions (git 2.5 for git worktree, tmux 1.8); the working directory is inside a git repository. Missing or too old is ✗. Also whether this OS’s FS-scoped sandbox backend (sandbox-exec on macOS, bwrap on Linux/WSL) is on PATH — absent is ⚠ with a pointer to the sandbox guide, never ✗, because the sandbox is optional.
CLIsThe AI CLIs that resolve on PATH — the known roster plus your [clis.*] entries. None resolving is ⚠, surfacing the No AI CLIs found launch failure before you hit it. Plus three read-only reports about per-CLI setup: setup state, broad grants and grant residue (below).
Config.git-paw/config.toml exists and parses (unparseable is ✗, absent is ⚠); the resolved worktree_placement; any key this version does not recognise (⚠, naming the key).
Spec systemThe explicitly configured spec format and how many specs it discovered. Unconfigured is ⚠ with the “add [specs] or pass --specs-format” guidance — there is no filesystem auto-detection. Also reports the effective spec-worker CLI: the CLI each discovered spec would actually launch with (its own paw_cli when set, else default_spec_cli). Warns (⚠) when a configured default_spec_cli is never actually reachable by any discovered spec — every one of them pins its own paw_cli instead — surfacing a silent mis-resolution (such as a spec-driven --supervisor launch quietly using an unexpected, unsandboxed CLI) rather than passing green. Informational (✓) when no specs or no default_spec_cli are configured — there’s nothing to verify against yet. Finally, whether the workflow entry point the launch submits to spec-driven agents is reachable from your worktrees (below).
Bundled scriptssweep.sh, broker.sh, docs-fetch.sh, and the shared _paw_common.sh preamble they source exist under .git-paw/scripts/, are executable, and match this binary’s embedded copies. Missing or non-executable is ✗; content drift is ⚠ (“stale”). Also checks for a Python 3 interpreter, which every bundled script needs — absent is ⚠, not ✗, because core start/add/remove needs no Python.
BrokerWhen [broker] enabled = true, that the configured bind/port is free or already serving a git-paw broker for this repository. A git-paw broker serving another repository is ⚠ with the [broker] port remedy — it is the clash that would refuse your next git paw start. A non-git-paw service on the port is a separate ⚠ with its own remedy. When disabled, an informational ✓ noting the pure-manual baseline.
SupervisorWhen [supervisor] enabled = true, that each configured gate command’s binary resolves on PATH (✗ per missing binary) and that sweep.sh is installed. When disabled, an informational ✓.
HygieneThe required .gitignore entries (including .git-paw/worktrees/); session state; registered worktrees whose directory no longer exists. Each is ⚠. A receipt that claims active while its tmux session is gone gets the git paw purge --stale remedy, as does an orphaned worktree registration. A session whose tmux session is still up but whose agent panes have all dropped back to a bare shell (past a short launch grace period) is a separate finding: it names the session, says its agent panes exited, and its remedy is git paw start, which recovers it — purge --stale never purges such a session.

The per-CLI setup checks

Three checks report on git paw setup-cli. All are strictly read-only — they create, modify and delete nothing — and none can ever be the reason doctor fails: they never report ✗.

Setup state, one per CLI identity that has setup facts or record entries:

  • ✓ when nothing is applied, listing the available items and whether each is verified or plan-only;
  • ✓ when every recorded entry is present and unchanged, and the target settings file holds every entry this binary would plan;
  • ⚠ when a recorded entry is missing or has been edited, with a remedy naming --undo;
  • ⚠ when the applied entries are out of date against this version, with a remedy naming --undo followed by re-applying;
  • ⚠ when a granted helper script differs from the copy this binary bundles, naming its path. This one matters most: the allowlist lets that script run without a prompt, so drift in it is drift in what runs unprompted. Helpers are checked in the main checkout and in every session worktree, because a granted path is relative and resolves against whichever directory the session runs in.

What counts as applied is judged against the CLI’s target settings file, not against git-paw’s record: a planned entry is applied when that file holds it at its key path, whoever wrote it. That includes an entry you already had before you ran git paw setup-cli. Setup skips such an entry — there is nothing to write — and deliberately never records it, because the record lists only what git-paw itself wrote, so --undo can take back exactly that and nothing else.

The check reads the comparison in both directions. It also warns when the record holds a permission rule this version of git-paw no longer plans — a grant a previous release wrote and this one has withdrawn. The remedy is the same either way: git paw setup-cli <CLI> --undo, then re-apply the item.

Broad grants, an advisory over the allowlist item’s target file when it exists. It lists every permissions.allow entry whose shape grants more than one specific command — a bare tool rule, a single-word prefix grant, a shell or interpreter wrapper, a state-changing git or git-paw subcommand, a far-reaching command like rm or curl (a loopback curl is exempt), a file-tool rule anchored at the filesystem root or your home directory, or a helper grant broader than git-paw’s own entry for that helper.

Entries are abbreviated to the tool name and the first two words of the specifier, each capped in length. That bounds the common way a secret ends up in a rule — a long URL carrying a token — but it is a mitigation, not a guarantee: a short rule that is itself a secret cannot be told apart from a short rule that is not, so treat doctor output as you would the settings file it reads. An unparseable file is reported as “not inspected”, without its content. git-paw’s own planned entries are never listed, whoever added them.

This advisory is a report over stored rules, not a decision about any prompt. git-paw does not edit these entries and never acts on them — reviewing and removing the ones you did not intend is yours to do.

Grant residue (project CLI grant residue) sits beside the advisory and answers a narrower question: not “is this rule broad?” but “is this a known-dangerous leftover?”. It reads the main checkout’s .claude/settings.json and .claude/settings.local.json, and reports ⚠ when either holds one of exactly three things:

ResidueExampleWhy it matters
A permissions.allow entry granting curl with any argumentsBash(curl *), Bash(curl:*), Bash(curl*)an unrestricted network client
A permissions.allow entry naming a helper script with a keystroke or verdict subcommandBash(.git-paw/scripts/sweep.sh approve *), Bash(.git-paw/scripts/sweep.sh verified *), Bash(.git-paw/scripts/sweep.sh *)it lets an agent session approve its own permission prompts, or publish a supervisor verdict, without a prompt
A permissions.allow entry naming the pass-through fetch subcommandBash(.git-paw/scripts/docs-fetch.sh get *)it passes an arbitrary URL through, so a session can read any file it names or send repository content to any host
A permissions.allow entry naming any other subcommand the grant table withholdsBash(.git-paw/scripts/sweep.sh classify *)a helper subcommand the grant table deliberately withholds from the allowlist item
A top-level allowed_bash_prefixes key—the inert seed git-paw wrote before v0.16.0; it grants nothing in the current CLI and is pure leftover

Each entry is reported with the reason true of the subcommand it actually reaches, and a rule reaching several — a whole-script wildcard reaches all of them — is reported by the gravest.

The helper rules are matched by the helper’s name wherever it appears in the specifier, so a relative, absolute or interpreter-prefixed path is caught alike. A wildcard is matched in or across the subcommand position: both Bash(.git-paw/scripts/sweep.sh *) and Bash(.git-paw/scripts/sweep.sh ap*) reach approve. Two things are deliberately not residue: a specific loopback rule such as Bash(curl -s http://127.0.0.1:9119/status), which names one endpoint rather than granting the client; and Bash(.git-paw/scripts/broker.sh *), because the grant table grants that helper whole — it is one of git-paw’s own entries.

Only Bash rules can be residue — a Read or Edit rule naming a helper grants reading or writing that file, not running it.

The remedy is by hand. git-paw never rewrites a grant it did not write, so it names each file and each matching entry and leaves the removal to you. The check is ✓ when neither file holds a match, and ⚠ stating the file was not inspected when one of them exists but is not valid JSON. When neither file exists there is nothing to report and the check is not shown at all.

Entries are abbreviated as in the advisory above, with the same caveat — it bounds what is printed rather than guaranteeing a secret never appears. A helper grant gets one extra protection: it is rendered from the word naming the helper, so an environment assignment in front of it, which is exactly where a token would sit, is replaced by … instead of printed.

Like the other two, it is read-only, never reports ✗, and never influences how a prompt is handled.

A rule can appear in both reports — Bash(curl *) is both broad and residue. That is intended: one is advice about breadth, the other names a specific danger.

The spec-workflow reachability check

A spec-driven launch submits your spec tool’s workflow command — /opsx:apply for OpenSpec — as a second message once the agent’s pane goes idle. That command’s definitions are installed by the spec tool into an agent-CLI directory at the repository root (for claude: .claude/commands/opsx/apply.md and .claude/skills/openspec-apply-change/SKILL.md), and that directory is frequently gitignored. git worktree add populates a new worktree from a commit, so a sibling worktree never sees an uncommitted definition and the agent quietly replaces the spec workflow with an ad-hoc one. The spec workflow check surfaces that before you launch.

Each spec-driven worker CLI the discovered specs resolve to is judged on its own:

  • ✓ when a definition is committed at the main checkout’s HEAD — every new worktree checks it out, whatever the placement. Staging it is not enough: an index entry does not travel into a new worktree;
  • ✓ when worktree_placement is child and a definition exists at the repository root — the worktree sits under the root, so the CLI’s ancestor search still finds it;
  • ⚠ when the CLI’s locations are known and no definition reaches the worktrees — either placement is sibling and every definition found at the root is uncommitted, or none is found at all. The finding names the command, the unreachable CLIs (and only those — a reachable CLI alongside them passes silently), and the locations checked.

Under sibling placement the remedy offers both fixes: commit the command definitions so every new worktree checks them out, or set worktree_placement = "child" so the worktrees sit under the repository root. Under child placement the only cause left is that no definition exists at all, so the remedy points at installing it with your spec tool and committing it.

Definitions are looked for in the main checkout, so running doctor from inside an agent’s worktree — which never carries them itself — reports the same verdict as running it at the repository root.

Two states are reported rather than warned about:

  • ✓ not applicable when the resolved spec format declares no workflow entry point (Markdown, Spec Kit, Superpowers, or no spec system), or when no spec-driven worker CLI resolves — there is nothing to reach;
  • ✓ could not be verified for a CLI whose definition locations git-paw does not know. Where a CLI keeps its command definitions is per-CLI knowledge; git-paw lists only the locations verified against a real install (today, claude) rather than guessing a path and warning about a definition that never lived there.

Like the setup checks, this one is read-only and never ✗ — an unreachable workflow degrades a launch, it does not block one.

The supervisor check is project-agnostic

The gate-command verbs doctor probes come from your [supervisor] configuration — the resolved stack preset — never from a hard-coded toolchain. A Node project configured with test_command = "npm test" sees npm checked; a Go project sees go. git-paw never assumes every consumer builds with the toolchain it happens to use itself.

Machine-readable output

--json emits the same checks as one JSON document and suppresses the human rendering. The exit-code contract is identical, so scripts and agents can branch on either.

git paw doctor --json
{
  "status": "warn",
  "checks": [
    {
      "group": "Environment",
      "name": "git",
      "status": "ok",
      "detail": "git version 2.39.5 (/usr/bin/git)",
      "remedy": null
    },
    {
      "group": "Spec system",
      "name": "spec system",
      "status": "warn",
      "detail": "no spec system configured — spec-driven launch is unavailable",
      "remedy": "add a [specs] section to .git-paw/config.toml … or pass --specs-format"
    }
  ]
}

Every entry carries group, name, status ("ok" / "warn" / "fail"), detail, and remedy (null on a ✓). The top-level status is the worst check.

Live smoke check

Doctor is static by default: it inspects, and never spawns a tmux session or an AI CLI. Pass --live to add a Live smoke group that runs the full session lifecycle (start → add → remove → stop) against an isolated throwaway repository and a dummy CLI, and folds the verdict in as one more check:

git paw doctor --live

This is the same harness the internal git paw selftest command drives — see Selftest for the full isolation recipe. It is slower, it needs tmux, and it is the only arm of doctor that writes anything: strictly inside its own .git-paw/tmp/ sandbox, which it removes again on both the success and failure paths. A skip (no tmux) is ⚠, not ✗ — the Environment group already reports a missing tmux as the hard failure.

Configuration

Doctor introduces no configuration fields. It reads the configuration you already have ([broker], [supervisor], [specs], [clis.*], worktree_placement, default_spec_cli / default_cli) and reports what it resolved.

Selftest

git paw selftest runs an isolated, end-to-end session-lifecycle smoke check — the shipped form of the dogfood isolation recipe. It exercises the riskiest orchestration plumbing (start → add → remove → stop) against a throwaway repository and a dummy CLI, then reports a single pass/fail verdict — with no real LLM backend and no interactive terminal.

Internal diagnostic (hidden). selftest is not shown in the main command list — it is the live smoke-test arm of git paw doctor, whose static preflight checks it complements. It stays available for dogfooding and CI, and git paw doctor --live runs this same harness and folds its verdict into doctor’s grouped report as a Live smoke check.

git paw selftest

Why

git-paw’s session-management surface (start, add, remove, stop, purge) is its riskiest plumbing because it spans modules — worktrees (git.rs), tmux panes and sockets (tmux/), the broker HTTP roster (broker/), and session state (session.rs). Historically the only way to exercise the full path was to boot a real session with a real AI CLI in an interactive terminal — something CI cannot do and developers reproduced only through ad-hoc shell incantations.

selftest packages that recipe as a first-class subcommand, so the lifecycle is verifiable anywhere. It is a runtime smoke check of the orchestration plumbing, complementary to cargo test — not a replacement for the unit and integration suite.

What it does

The harness re-invokes the running git-paw binary as child processes — the same code path you exercise by hand — and drives the lifecycle:

  1. Allocate an OS-assigned ephemeral broker port.
  2. Create a throwaway git repository (one base commit) under .git-paw/tmp/.
  3. Start an isolated supervisor session with a dummy CLI, confirming it landed on the harness’s private tmux socket.
  4. Observe the roster through the per-repo discovery file — it holds exactly the initial agent.
  5. Add an agent worktree and observe the roster grow to include it.
  6. Remove that agent worktree and observe the roster shrink back, with the remaining entries unchanged.
  7. Stop the session.

Steps 4–6 make the add/remove roster transitions observable, closing the deferred live-verification that previously required a real session.

Isolation recipe

Every external resource is isolated so a run never disturbs your live work or a concurrently running selftest:

  • Private tmux socket — a per-run TMUX_TMPDIR, with TMUX and TMUX_PANE stripped from every child process so the spawned tmux server never attaches to the caller’s session. Your default tmux socket is never touched.
  • OS-assigned ephemeral broker port — the harness binds 127.0.0.1:0 and reads back the kernel-assigned port, so concurrent runs never collide on a fixed or PID-derived port.
  • Isolated HOME/XDG — the global session receipt is written under the throwaway tree, so your real sessions directory is never modified.
  • Throwaway repository under .git-paw/tmp/ — namespaced inside the repo’s own git-paw working tree so artifacts are discoverable and gitignorable. A stale-dir sweep removes any prior aborted run before a fresh one is created.
  • Dummy CLI (cat) in place of a real agent CLI — it holds its pane open without producing output or requiring input, so the session boots deterministically in detached mode with no LLM process spawned.

Cleanup runs on both the success and failure paths: the private-socket tmux server is killed and the .git-paw/tmp/ throwaway tree is removed.

Exit behaviour

Outcomestdout / stderrExit code
Lifecycle completedselftest passed0
tmux not on PATHselftest skipped: tmux not available0
A lifecycle step failedselftest failed at step '<step>': … (stderr)non-zero

Like the rest of the e2e suite, selftest reports a skip rather than a failure when tmux is unavailable, and exits non-zero only on an actual lifecycle failure. CI runners have tmux installed, so a skip never silently passes there. When a step fails the verdict names the failing step (one of pick-port, create-repo, start, roster-initial, add, roster-after-add, remove, roster-after-remove, stop).

Relationship to the broker-port fix

The same OS-assigned ephemeral-port helper the harness uses for the broker port is now the canonical scheme across the test suite. It replaced a PID-modulo port selection (24_000 + (process::id() % 200)) that yielded only a small number of distinct ports and collided under concurrency — the real cause of the intermittent “address already in use” broker-bind failures. Binding 127.0.0.1:0 is collision-proof at any concurrency because the kernel guarantees each bind returns a port not currently in use. See the test-isolation specification for the requirement.

CLI Prompt-Shape Profiles

git-paw’s supervisor and prompt detectors recognise an agent CLI’s terminal surface — its “ready” banner, its permission-prompt wording, its mid-response footer — by matching known markers against captured pane text. A prompt-shape profile is the data that describes what one CLI’s terminal looks like. It never describes what that CLI is allowed to do — see What a profile is not below.

git-paw ships one profile, embedded in the binary: Claude Code. Every detector falls back to it, field by field, so an installation with no profile configuration behaves exactly as it always has.

Why this exists

Before profiles, the markers describing Claude Code’s terminal were compiled into half a dozen Rust modules plus a bash mirror in the bundled sweep.sh helper. For a non-Claude CLI this was not cosmetic — an approval marker that never matched meant command extraction silently failed, and a three-option prompt with different wording was read as a two-option one. A prompt-shape profile gives each of those literals one home, so adding a new CLI’s markers is a data change, not a code change across six files.

Resolution order

For a given CLI name, each field resolves independently:

  1. The field’s value in that CLI’s [clis.<name>].prompt_profile table, if set.
  2. Otherwise, the embedded Claude Code default.

This is per-field, not per-profile: a [clis.<name>].prompt_profile table that sets only approval_markers still gets the embedded default for every other field (readiness markers, mode markers, and so on). A partial profile can never silently disable detection for the fields it omits, and a CLI with no prompt_profile table at all — including one git-paw has never heard of — resolves the full embedded default rather than an empty profile.

Fields

FieldMatchesUsed by
readiness_markersSubstrings that positively identify the CLI’s interactive ready state (as opposed to a bare shell prompt), gating boot-block injection.tmux::readiness::classify_pane_readiness (boot-prompt injection gate)
approval_markersSubstrings indicating the CLI is waiting for an approval decision.supervisor::permission_prompt approval-marker scan (reports only that a prompt is present)
live_prompt_markersThe confirmation-question lead-in ("do you want to") and the cancel footer ("esc to cancel") that anchor a LIVE (not scrolled-away) permission prompt. Detection searches upward from the end of the capture, tolerating up to 13 non-blank trailing lines of non-prompt content (a task list, a status strip) below the modal — but once the anchor has shifted past the capture’s true end, liveness there requires the modal’s actual SHAPE: the footer anchoring the START of a line (not merely appearing anywhere in it) together with a numbered option line nearby. A bare confirmation question, or the footer text only mentioned mid-sentence (narration, grep-style tool output), cannot re-confirm liveness there — an already-answered prompt can leave either behind followed by ordinary output.supervisor::permission_prompt::is_live_prompt, the sweep.sh approve re-confirm
mid_response_markersSubstrings identifying a pane actively producing a response (its interrupt footer showing) rather than sitting at its input box.supervisor::drive::pane_is_mid_response (unattended drive loop)
prompt_boilerplate_markersLower-cased substrings marking a captured line as prompt boilerplate (the question / choices) rather than the command awaiting a decision.supervisor::manual_approvals pattern extraction
command_header_markersLead-in forms of a command-confirmation header — a whole-line header form (e.g. "Bash command") or an inline-embedding form ending in ( (e.g. "Bash(").supervisor::permission_prompt::extract_command_slice
file_prompt_patternRegex source matching the lead-in of a filesystem-operation permission prompt, capturing the target path.supervisor::permission_prompt::extract_path_from_file_prompt
option_line_patternRegex source matching a numbered option line ("1. …", "2) …") once leading decoration is stripped.supervisor::permission_prompt::is_option_line
broad_grant_markerSubstring identifying a durable (“don’t ask again”) broad-grant option in a multi-option prompt.The sweep.sh approve option-1 guard: if the prompt’s first option carries this wording, pressing it would write a permanent permission rule, so the helper sends nothing.
input_sigilsPrefixes identifying the CLI’s input-box sigil (e.g. ">", "❯").supervisor::drive::input_box_text (stranded-input recovery)
mode_accept_edits_markersSubstrings identifying an accept-edits / bypass-permissions mode footer.coordination::inventory::detect_mode
mode_interactive_markersSubstrings identifying a visible interactive prompt.coordination::inventory::detect_mode
stream_error_markersPhrases indicating the CLI’s API call failed mid-turn (transport error, timeout, disconnect).the bundled sweep.sh helper’s stuck-shape detector
context_bloat_patternRegex source matching a context-bloat clear/compact hint, capturing the token count in thousands.the bundled sweep.sh helper’s stuck-shape detector
paste_buffer_patternRegex source matching a paste-aware CLI’s “pasted text” acknowledgement.the bundled sweep.sh helper’s stuck-shape detector
cli_unavailable_markersSubstrings/phrases indicating the CLI itself has stopped serving — a usage/session limit ("You've hit your session limit"), an organisation spend limit ("org's monthly spend limit"), or an expired/missing login — rather than a worker being merely stuck.the unattended drive loop (supervisor::drive::cli_unavailable_line), resolved per pane for its own CLI: an orchestrator-pane match is an immediate cli-unavailable exit, a coding-pane match a deduped escalation

Every marker-list field is matched as a case-insensitive substring, mirroring the behaviour each site had before profiles existed. Pattern fields are Rust regex crate syntax.

Authoring a profile for a new CLI

Add a prompt_profile table under that CLI’s [clis.<name>] entry, setting only the fields that differ from Claude’s terminal surface:

[clis.my-agent]
command = "my-agent"

[clis.my-agent.prompt_profile]
readiness_markers = ["my-agent ready"]
approval_markers = ["proceed? (y/n)"]
broad_grant_marker = "remember this choice"

Every field you omit falls back to the embedded Claude default (see Resolution order) — start with just the fields you know differ, and add more once you observe a detection gap in practice.

What a profile is not

A prompt-shape profile describes what a CLI’s terminal looks like. It has no field through which it could express what a command is allowed to do — git-paw decides that for no command anyway. What a profile also cannot touch is the discipline around sending a keystroke. The following stay compiled (or mirrored in the bundled sweep.sh), are never read from a profile, and cannot be widened by one:

  • the live-prompt gate’s anchoring windows and trailing-region bound — a profile supplies the marker text, never how far from the end of a capture a prompt may sit and still count as live
  • the approval send gate’s fresh-capture re-confirm
  • the pane-0 refusal, which holds whatever a capture contains
  • the sweep.sh approve option-1 rule: it always answers with option 1 and never another digit. A profile’s broad_grant_marker can only make the helper refuse to send (by marking option 1 as a durable grant), never make it press something else.

This boundary exists because a profile is configuration a coding agent’s own worktree can influence (via .git-paw/config.toml); if a profile could alter where a keystroke lands or when one is allowed, an agent could widen the very gate meant to constrain it. If you find yourself wanting a profile field to affect whether or where a keystroke is sent rather than how the prompt looks, that is a sign that a compiled rule change is needed instead.

Do not ship a guessed profile

git-paw ships only the Claude Code profile — the one CLI its detection is tested against. A profile for a CLI nobody has verified against live output is worse than no profile at all: it produces confident misdetection instead of an obvious fallback to Claude’s markers. If you author a profile for another CLI, verify it against that CLI’s actual terminal output before relying on it, and expect to iterate.

sweep.sh reads the same profile

The bundled supervisor helper (sweep.sh) resolves its stuck-shape and live-prompt regexes via the internal git paw __prompt-profile command rather than carrying its own copies of the markers — the same profile every Rust detector reads. This means an authored profile applies to both the in-process supervisor and the shell helper without being declared twice.

Configuration

git-paw uses TOML configuration files at two levels, with repo-level settings overriding global ones.

Config File Locations

LevelPathPurpose
Global~/.config/git-paw/config.toml (Linux; $XDG_CONFIG_HOME/git-paw/config.toml when that is set) or ~/Library/Application Support/git-paw/config.toml (macOS)Default CLI, custom CLIs, global presets
Per-repo.git-paw/config.toml (in repo root)Repo-specific overrides

Both files are optional. git-paw works with sensible defaults when no config exists.

Full Config Example

# CLI pre-selected in the interactive picker when --cli is not provided
default_cli = "my-cli"

# Default CLI for spec-mode launches (--from-all-specs, --specs); bypasses picker when set
# default_spec_cli = "my-cli"

# Prefix for spec-derived branch names (default: "spec/")
# branch_prefix = "spec/"

# Documentation site the bundled docs-fetch helper consults on demand
# (default: git-paw's published docs). Set to retarget a fork or mirror.
# docs_base_url = "https://bearicorn.github.io/git-paw"

# Enable mouse mode in tmux sessions (default: true)
mouse = true

# Where agent worktrees are created: "child" (in-repo .git-paw/worktrees/)
# or "sibling" (../<project>-<branch>). Absent = "sibling"; git paw init
# writes "child" for new repos and backfills it into a config that omits it.
worktree_placement = "child"

# Pane affordances: double-line borders, per-pane labels, active-pane highlight
# (default: true; set false to inherit your own tmux styling)
# [layout]
# border_affordances = true

# Per-worktree runtime isolation (all opt-in; absent = no copying, no
# .env.local, no hook). Files are copied verbatim into each worktree; each
# worktree gets its own port block base + slot * stride (keep stride >= vars
# length); hooks run your own commands at worktree create/remove.
# [worktree.env]
# copy = [".env", ".env.development"]
# [worktree.ports]
# base   = 3000
# stride = 10
# vars   = ["PORT", "VITE_PORT"]
# [worktree.hooks]
# on_create = "scripts/paw-db-branch.sh {worktree_id}"
# on_remove = "scripts/paw-db-drop.sh {worktree_id}"

# Custom CLI definitions
[clis.my-agent]
command = "/usr/local/bin/my-agent"
display_name = "My Agent"

[clis.local-llm]
command = "ollama-code"
display_name = "Local LLM"

# A custom CLI is also the seam for the optional FS-scoped sandbox: point
# `command` at a PATH-resolvable wrapper that execs the real CLI under
# sandbox-exec/bwrap. git-paw launches a CLI by its name on PATH, so the wrapper
# must be on PATH (not just a script path). See the FS-Scoped Sandbox guide.

# Named presets for quick launch
[presets.backend]
branches = ["feature/api", "fix/db"]
cli = "claude"

[presets.frontend]
branches = ["feature/ui", "feature/styles"]
cli = "codex"

# Spec scanning configuration
# [specs]
# dir = "specs"
# type = "openspec"    # "openspec", "markdown", "speckit", or "superpowers"

# opsx (OpenSpec) role gating — active only under the OpenSpec engine
# [opsx]
# role_gating = "warn"  # "warn" (default), "block", or "off"

# Session logging
# [logging]
# enabled = false

# Agent coordination broker
# [broker]
# enabled = false
# port = 9119
# bind = "127.0.0.1"
# wave_stall_minutes = 30

# Pointers to user-maintained governance docs (all optional)
# [governance]
# adr = "docs/adr"
# test_strategy = "docs/test-strategy.md"
# security = "docs/security-checklist.md"
# dod = "docs/definition-of-done.md"
# constitution = ".specify/memory/constitution.md"
# readme = "README.md"
# docs = "docs/src"

Upgrading an Existing Config

git paw init is the upgrade path after a version bump: re-run it in an already-initialised repo and it brings .git-paw/config.toml up to the current defaults. The migration is strictly additive — it appends

  • a [supervisor] section when the file has none (init asks whether to enable supervisor mode in an interactive terminal, and writes enabled = false otherwise), and
  • any top-level default key the file is missing, written with its generated-default value (for example worktree_placement, added in v0.8.0).

An existing but empty config.toml is instead seeded with the full generated template. The other sections appear in the template only as commented examples, so they are not appended to a file you have already written.

It never modifies or removes a section, a key, or a commented example you have already set, so your customised settings survive verbatim. Once [supervisor] and every top-level default key are present the config is left untouched, and — if nothing else needed creating — init reports Already initialized. Nothing to do. (The bundled .git-paw/scripts/ helpers, the exported .git-paw/skills/ and the AGENTS.md pointer section are refreshed on every run; each is reported as Updated only when its content changed, and any such change closes with Initialized git-paw. instead.)

When something is added, init names it:

  Updated .git-paw/config.toml (added missing keys: worktree_placement)

One backfilled key can change behaviour — see worktree_placement for the warning init prints in that case.

Settings Reference

default_cli

The AI CLI pre-selected in the interactive CLI picker when --cli is not passed — you can still choose any other listed CLI. It does not skip the picker: the assignment-mode prompt and the CLI picker still appear, and in per-branch mode every branch’s picker starts on this CLI too. Unset, or naming a CLI that is not available, the picker starts on its first entry. --cli skips the CLI prompt entirely.

default_cli = "my-cli"

default_spec_cli

The AI CLI to use by default when launching with --from-all-specs or --specs. When set, skips the CLI picker for any specs that don’t have a paw_cli override.

default_spec_cli = "my-cli"

See Spec-Driven Launch for the full CLI resolution chain.

branch_prefix

Prefix prepended to spec-derived branch names. Defaults to "spec/".

branch_prefix = "spec/"

For example, a spec with ID add-auth produces branch spec/add-auth.

mouse

Enable or disable tmux mouse mode for git-paw sessions. When enabled, you can click panes to switch, drag borders to resize, and scroll with the mouse wheel. This is set per-session and does not affect your other tmux sessions.

mouse = true  # default

worktree_placement

Controls where git-paw creates an agent’s worktree, relative to the repository:

  • "child" — inside the repo at .git-paw/worktrees/<branch-slug>. This is the contained layout: worktrees live under the project root, so a single permission grant for .git-paw/worktrees/ covers every agent (no scattered sibling directories outside the repo). git paw init writes this for new repos.
  • "sibling" — beside the repo at ../<project>-<branch-slug> (the v0.7.0 layout). This is the default when the field is absent, so pre-existing repos and sessions created before this field behave exactly as in v0.7.0.
worktree_placement = "child"

The <branch-slug> for the child layout is derived from the branch name alone: / becomes - and characters outside [A-Za-z0-9._-] are stripped (the project name is not prepended, since the directory already lives under that project’s .git-paw/worktrees/). For example branch feat/auth-flow maps to .git-paw/worktrees/feat-auth-flow/, and fix/issue#42 maps to .git-paw/worktrees/fix-issue42/.

Gitignore note. Child worktrees must be ignored or git would try to stage them as part of the repo. git paw init adds .git-paw/worktrees/ to .gitignore automatically. If you opt into worktree_placement = "child" by editing the config manually (without re-running git paw init), add .git-paw/worktrees/ to your .gitignore yourself.

Placement only governs new worktree creation. Existing worktrees stay where they are: each session records the concrete worktree path it created, and resume/status/purge operate on that recorded path — so flipping the config mid-project never orphans an already-created worktree.

Backfilled by git paw init. Because worktree_placement is a top-level default key, re-running git paw init on a config that omits it writes worktree_placement = "child" — the generated default, not the "sibling" the field resolves to when absent. That is deliberate: the backfill exists to bring a config up to the current defaults.

If the repo already has worktrees under the previous sibling layout, that changes where the next worktree is created, so init prints a warning:

warning: init backfilled worktree_placement = "child" — new agent worktrees will be created inside the repo at .git-paw/worktrees/.
  1 existing worktree(s) remain in their previous (sibling) location; init did not move them:
    /work/my-project-feat-auth
  To keep the previous layout, set worktree_placement = "sibling" in .git-paw/config.toml;
  otherwise leave it as "child" and new agents will use the contained layout.

Init never moves, deletes, or re-registers a worktree — it only warns. You choose:

  • Keep the old layout — set worktree_placement = "sibling" in .git-paw/config.toml. Init leaves an explicitly-set value alone on every later run.
  • Adopt the contained layout — leave the backfilled "child". Existing worktrees stay beside the repo and keep working (their recorded paths are unchanged); only newly created ones land in .git-paw/worktrees/.

A config that already sets worktree_placement (to either value) is never touched, and the key is never duplicated.

docs_base_url

Base URL of the documentation site the bundled docs-fetch skill consults on demand. Defaults to git-paw’s published documentation site; set it so a fork or mirror can point the helper at its own docs.

docs_base_url = "https://bearicorn.github.io/git-paw"

The docs-fetch.sh helper resolves this value from .git-paw/config.toml for both discovery (llms.txt) and page retrieval, falling back to the built-in default when the field is absent. Documentation lookup is best-effort: an unreachable site or missing page never blocks the agent.

Worktree runtime provisioning

git-paw isolates each agent’s source — every agent gets its own worktree — but not its runtime. Sibling worktrees otherwise read the same .env and collide on the same ports the moment two agents start a dev server, preview, or debugger. The [worktree] table gives each worktree its own copy of the env files you name, its own block of ports, and a pair of hooks for whatever else your stack needs isolated.

Every sub-table is opt-in. With none present, worktree creation behaves exactly as it did before this table existed: no files are copied, no .env.local is generated, and no command runs. git-paw ships the mechanism only — it assumes no filename, no port, and no stack, so you declare what your project actually needs.

[worktree.env]
copy = [".env", ".env.development"]

[worktree.ports]
base   = 3000
stride = 10
vars   = ["PORT", "VITE_PORT"]

[worktree.hooks]
on_create = "scripts/paw-db-branch.sh {worktree_id}"
on_remove = "scripts/paw-db-drop.sh {worktree_id}"

[worktree.env]

KeyTypeDefaultMeaning
copylist of strings[]Repository-root-relative files copied into each new worktree

Each entry is copied verbatim from the repository root into the new worktree, after the worktree is created and before the agent’s CLI starts — so the agent’s first read already sees a provisioned checkout.

The propagated file is an independent copy, never a symlink. That is deliberate: a symlinked .env is shared mutable state across every agent, which is the exact silent runtime corruption worktrees exist to prevent. A copy diverges cleanly instead, and any drift surfaces at merge time.

A declared file that does not exist at the repository root is skipped with a warning and the worktree is still created — per-branch divergence is normal and a missing optional env file should not cost you a session. Entries that are absolute paths, or that escape the repository root with .., are rejected with a warning; provisioning only ever reads from the repository and writes into the worktree.

[worktree.ports]

KeyTypeDefaultMeaning
baseinteger (u16)0First port of the first worktree’s block
strideinteger (u16)0Block size per worktree
varslist of strings[]Env-var names that receive sequential ports within the block

Each worktree holds a runtime slot and its block starts at base + slot * stride. The Nth entry of vars receives base + slot * stride + N. With the example above, the first worktree gets PORT=3000 / VITE_PORT=3001 and the second gets PORT=3010 / VITE_PORT=3011.

The assignments are written into a delimited managed block in the worktree’s .env.local — the override layer most stacks load last:

# >>> git-paw (managed) >>>
PORT=3000
VITE_PORT=3001
# <<< git-paw (managed) <<<

Anything you keep in .env.local outside that block is preserved, and regenerating the block is idempotent. A copied .env is never modified; offset ports live only in .env.local.

An empty vars list writes no port lines at all — git-paw supplies no default port variables.

stride must be at least vars.len(). A smaller stride would make adjacent worktrees’ blocks overlap and hand two live agents the same port. git-paw clamps the stride up to the number of vars and warns when it loads a config that gets this wrong, but set it correctly — leave headroom for vars you add later.

Ports are deterministic, not probed. git-paw does not scan the host for free ports, so an assigned port can still be occupied by an unrelated process. The trade is simplicity and stability: the same worktree keeps the same ports across restarts. If a block clashes with something else on your machine, change base or stride.

Slots across add/remove churn

The slot is recorded in session state (runtime_slot on the worktree entry) rather than derived from the worktree’s position in the list. Position would shift when a middle worktree is removed, handing a newly added agent a live agent’s ports.

A new agent takes the lowest slot no live agent holds, so blocks stay bounded instead of climbing forever, and removing an agent frees its block for the next git paw add. Two concurrently active worktrees are never assigned overlapping blocks. Session state written before this field existed loads with no slot and is assigned one on next provisioning.

[worktree.hooks]

KeyTypeDefaultMeaning
on_createstringunsetCommand run in a new worktree after env/port provisioning, before the agent’s CLI starts
on_removestringunsetCommand run in the worktree at git paw remove / git paw purge, before the directory is deleted

The third shared runtime resource — a database — cannot be isolated by a built-in mechanism, because branching one is entirely stack-specific: a Neon or PlanetScale branch API, a local CREATE DATABASE, a Docker volume, a Redis namespace. git-paw therefore ships no driver and instead runs your command at the two lifecycle points, letting you provision and tear down whatever per-worktree resource your stack needs.

[worktree.hooks]
on_create = "scripts/paw-db-branch.sh {worktree_id}"
on_remove = "scripts/paw-db-drop.sh {worktree_id}"

Both keys are optional and independent — declare one, both, or neither. With no [worktree.hooks] table, no command runs and worktree lifecycle is byte-identical to configuring [worktree.env] / [worktree.ports] alone.

Placeholders

Before the command is executed, git-paw substitutes:

PlaceholderExpands to
{worktree_id}The worktree’s stable identifier — the branch-derived slug that also names its directory (feat/auth-flow → feat-auth-flow)
{worktree_path}The worktree’s absolute path

The id is restricted to [A-Za-z0-9._-], so it is always safe to interpolate. {worktree_path} is substituted verbatim — quote it in your command (db.sh '{worktree_path}') if your repository path may contain spaces.

Each command runs through /bin/sh -c with the worktree as its working directory, so relative paths in your script resolve against the checkout and scripts/paw-db-branch.sh means the copy in that worktree. Nothing extra is injected into its environment.

on_create stdout becomes environment

git-paw reads on_create’s standard output and merges every KEY=value line into the same .env.local managed block the port assignments are written to. That is how a hook hands its connection details to the agent:

$ cat scripts/paw-db-branch.sh
#!/bin/sh
createdb "paw_$1" >/dev/null
echo "DATABASE_URL=postgres://localhost/paw_$1"

$ cat .git-paw/worktrees/feat-two/.env.local
# >>> git-paw (managed) >>>
PORT=3010
VITE_PORT=3011
DATABASE_URL=postgres://localhost/paw_feat-two
# <<< git-paw (managed) <<<

A line counts as an assignment only if it is an identifier ([A-Za-z_][A-Za-z0-9_]*) followed by =. Everything else — progress text, blank lines, your script’s chatter — is ignored, so a hook is free to be talkative. The value is taken verbatim to end of line, so a URL keeps its own = signs. Content outside the managed block is untouched, and re-provisioning the same worktree is idempotent.

Because the ports are written before the hook runs, a hook that needs its agent’s port can read .env.local and use it.

Failure policy

The two hooks fail in deliberately opposite directions:

  • on_create non-zero fails that worktree’s provisioning, reporting the hook’s exit status and its stderr. Handing an agent a half-built runtime is worse than refusing to start it.
  • on_remove non-zero only warns and the removal continues. A failed teardown must never strand a worktree on disk — so write your teardown script to be idempotent and reconcilable out of band. git-paw does not track external resource state, and a crash or a force-purge can skip the hook entirely.

Secrets and trust

Print secrets to stdout, diagnostics to stderr. git-paw logs only the names of the variables a hook merged, never their values, so a DATABASE_URL carrying a password never reaches a log line or an error. A failing hook’s stderr is surfaced, so anything secret there would be printed.

.env.local is a plain file — gitignore it. git-paw writes the hook’s values into the worktree’s .env.local with ordinary permissions. If your repository does not already ignore it, add it, or an agent may commit a credential.

Hooks run arbitrary consumer commands. This is by design and is the same trust model as [clis.*].command and the [supervisor] command keys: the command comes from your repository’s config file and runs with your privileges. git-paw never builds a hook out of broker, network, or agent-supplied input. Treat write access to .git-paw/config.toml as equivalent to command execution on your machine, and review changes to it in the same way you review a build script.

Custom CLIs

Register custom AI CLIs that aren’t in git-paw’s built-in detection list.

A [clis.<name>] entry has two distinct names, and they are used for different things:

  • command is what the pane launches. Every launch path — the bare start, the spec-driven start, the supervised start, git paw add, and session recovery — spawns the entry’s command, not the key. It is emitted as exactly one shell word: single-quoted (with embedded single quotes escaped) whenever it contains any character outside the portable safe set A-Z a-z 0-9 _ - . / + , : @ %, so a path containing spaces or shell metacharacters is neither word-split nor expanded. Approval flags and session-id / resume arguments are appended after it.
  • The <name> key is the CLI’s identity. Approval-flag, session-id and resume-template, prompt-profile, settle-delay, and MCP-capability lookups all resolve against the key, as does the cli recorded in each saved worktree entry. A CLI name with no matching entry (a built-in detected CLI, or an explicit pass-through name) launches under that name unchanged.

So [clis.claude-variant] command = "claude" launches claude while every per-CLI override and the saved session still read claude-variant.

Via config file

[clis.my-agent]
command = "/usr/local/bin/my-agent"   # absolute path
display_name = "My Agent"              # optional, shown in prompts

[clis.local-llm]
command = "ollama-code"               # binary name (resolved via PATH)
display_name = "Local LLM"

# A claude-family variant that reads a non-default config directory.
[clis.claude-variant]
command = "claude"
# Boot-prompt settle delay (ms) before the submit Enter. git-paw injects
# the boot block, waits this long for a paste-aware CLI to settle the
# paste, then sends Enter separately. Default suits most CLIs; raise it
# for a CLI whose large-paste handling needs longer before submit lands.
submit_delay_ms = 1500
# Path to this CLI's claude-format settings file. Parsed and preserved for
# compatibility only: git-paw never reads, writes, or seeds this file, and
# nothing in git-paw acts on it today. A leading `~` is expanded to the home
# directory.
settings_path = "~/.config/claude-variant/settings.json"
# Per-approval-level flag overrides, consulted BEFORE git-paw's built-in
# flag table when composing launch commands. Keys are the approval level
# names ("manual" | "auto" | "full-auto"); values are appended verbatim to
# the launch command at that level. This is how a variant CLI with no
# built-in table row gets native permission flags (e.g. full-auto for a
# claude-oss variant). Unknown level keys fail the config load.
approval_args = { "full-auto" = "--dangerously-skip-permissions" }
# Templates for assigning and resuming a worker CLI's own session identity.
# Each MUST contain the {{SESSION_ID}} placeholder; a template lacking it is
# rejected at config load, naming the key. Resolution is override → built-in
# → none, same order as approval_args. The built-in table covers `claude`
# (`--session-id {{SESSION_ID}}` / `--resume {{SESSION_ID}}`); a CLI without
# a built-in row and without these overrides launches unchanged (no id
# assigned, no resume on recovery).
session_id_args = "--session-id {{SESSION_ID}}"
resume_args = "--resume {{SESSION_ID}}"
# Arguments that make this CLI run one non-interactive turn reading its
# prompt from stdin — used when the unattended drive loop hands a judgment
# call to a headless orchestrator. Resolution is override → built-in
# (`claude` → `-p`, `codex` → `exec`) → none. An empty string fails the load.
headless_args = "-p"
# Whether this CLI speaks MCP, deciding which form of the coordination boot
# block it receives. Unset by default (and omitted from written config), in
# which case the built-in MCP-capable set decides. Only set `true` once
# git-paw's MCP server is registered for THIS CLI — see the field table.
# mcp = true

# Per-field overrides of the CLI's terminal-shape markers (readiness
# banner, approval-prompt wording, …), consulted before the embedded
# Claude Code default. See "CLI Prompt-Shape Profiles" in the user guide.
[clis.claude-variant.prompt_profile]
approval_markers = ["proceed? (y/n)"]
FieldRequiredPurpose
commandyesCommand or path to the CLI binary — what the pane launches, emitted as one shell word. Must be non-empty; a blank value is rejected at config load, naming the entry.
display_namenoHuman-readable name shown in prompts.
submit_delay_msnoBoot-prompt settle delay (ms) before the submit Enter; per-CLI so the launcher stays CLI-agnostic.
settings_pathnoPath to the CLI’s claude-format settings file. Parse-only: accepted and preserved across a load/save round-trip so existing configurations keep working, but git-paw never reads, writes, or acts on it — not even git paw setup-cli, whose only target is the main checkout’s project-local settings file.
approval_argsnoMap from approval-level name ("manual", "auto", "full-auto") to the flags appended to the launch command at that level. Consulted before the built-in flag table; an unknown level key is rejected at config load with an error naming the key.
session_id_argsnoTemplate (containing {{SESSION_ID}}) for the flag(s) that assign a fresh session identifier at launch. Consulted before the built-in table (claude only); a CLI resolving neither launches unchanged, with no id recorded.
resume_argsnoTemplate (containing {{SESSION_ID}}) for the flag(s) that resume a previously assigned session. Appended on recovery when the worktree’s recorded id and this CLI’s resolved template are both present; a template omitting the placeholder is rejected at config load.
headless_argsnoArguments that make the CLI run one non-interactive turn reading its prompt from stdin, used for the drive loop’s headless orchestrator hand-off. Consulted before the built-in table (claude → -p, codex → exec); a CLI resolving neither has no headless form, and the hand-off is unavailable for it. An empty string is rejected at config load, naming the key. The hand-off spawns this entry’s command directly, not through a shell, so it must name a single program (a path or a name on PATH); the headless arguments and approval flags are split on whitespace, so quoted arguments are not supported there.
prompt_profilenoPer-field overrides of the CLI’s terminal-shape markers, consulted before the embedded Claude Code default. Carries no permission policy — see CLI Prompt-Shape Profiles.
mcpnoBoolean. Whether this CLI identity speaks MCP, selecting the MCP publish-tool form of the coordination boot block over the broker.sh helper form. Unset by default and omitted when the config is written back.

settings_path used to feed a protected-path set that the auto-approve classifier consulted before approving a write. That classifier is retired, so the field now has no effect at all — it is kept so existing configurations load and round-trip unchanged. Agent memory isolation is carried by the bundled skills’ doctrine, and mechanically by deny rules in the agent CLI’s own settings or by the sandbox. See Agent permissions.

mcp and the boot-block form

Whether a CLI receives the MCP tool form of the coordination boot block is resolved in this order:

  1. mcp = true or mcp = false on the matching [clis.<name>] entry;
  2. otherwise the built-in MCP-capable set — exactly claude, codex, cursor-agent, and agy;
  3. otherwise not MCP-capable, so the broker.sh helper form is used.

The lookup uses the CLI identity (the [clis.<name>] key, or the built-in / pass-through name), never the resolved command. That is why [clis.claude-oss] command = "claude" does not inherit claude’s MCP form: a variant typically reads a different config directory, which may not have git-paw’s MCP server registered.

Warning: setting mcp = true for a CLI that does not have git-paw’s MCP server registered strands the agent at boot — the boot block names publish tools the agent cannot call, so its very first coordination step fails. Register the server for that CLI first (see MCP), then opt in.

A config without an mcp key loads and behaves exactly as it did before the field existed.

Files git-paw writes

setup-cli adds no configuration field. It does write two files outside .git-paw/, both only when you run it:

PathWhat it is
<main checkout>/.claude/settings.local.jsonThe claude allowlist item’s only target. Merged into — every key and its order are preserved.
<git-common-dir>/git-paw/cli-setup.jsonThe setup record: what git-paw wrote, so --undo can take back exactly that. Owner-only, inside the git directory, so it can never be committed and never shows up in any worktree’s git status.

See Per-CLI Setup.

Via command line

# Add with absolute path
git paw add-cli my-agent /usr/local/bin/my-agent

# Add with binary name on PATH
git paw add-cli my-agent my-agent

# Add with display name
git paw add-cli my-agent my-agent --display-name "My Agent"

# Remove
git paw remove-cli my-agent

The add-cli and remove-cli commands modify the global config (see Config File Locations). add-cli stores a binary name given on the command line as its resolved absolute path.

Listing CLIs

git paw list-clis

Shows both auto-detected and custom CLIs with their source:

NAME            SOURCE     PATH
claude          detected   /usr/local/bin/claude
codex           detected   /usr/local/bin/codex
my-agent        custom     /usr/local/bin/my-agent

Presets

Presets save branch + CLI combinations for one-command launch.

Defining presets

[presets.backend]
branches = ["feature/api", "fix/db-migration"]
cli = "claude"

[presets.full-stack]
branches = ["feature/api", "feature/ui", "feature/styles"]
cli = "agy"

Using presets

git paw start --preset backend

This skips all interactive prompts and launches with the preset’s branches and CLI.

Specs

Configure spec file scanning for --from-all-specs and --specs mode.

[specs]
dir = "specs"         # Directory containing spec files (relative to repo root)
type = "openspec"     # "openspec" (OpenSpec changes), "markdown" (flat .md files), "speckit" (GitHub Spec Kit), or "superpowers" (obra/superpowers plans)
FieldDefaultDescription
dir"specs"Directory to scan for spec files
type"openspec"Spec backend: "openspec" (directory-based OpenSpec changes), "markdown" (flat .md files with YAML frontmatter), "speckit" (GitHub Spec Kit .specify/specs/<feature>/), or "superpowers" (obra/superpowers docs/superpowers/plans/*.md)

git-paw does not auto-detect the spec system from the filesystem: the [specs] section is the source of truth. git paw init prompts you to choose the spec system and writes this section. When [specs] is omitted, launching from specs errors until you add it — or pass the --specs-format CLI flag, which selects the spec system for a single launch.

See Spec-Driven Launch for format details.

Logging

Configure session output logging.

[logging]
enabled = true
FieldDefaultDescription
enabledfalseWhether to capture pane output to log files

When enabled, logs are written to .git-paw/logs/<session>/ using tmux pipe-pane. See Session Logging for details.

Broker

Configure the HTTP broker for agent coordination. When enabled, git-paw starts a lightweight HTTP server that lets agents share status updates, artifacts, and blocked requests.

[broker]
enabled = true
port = 9119
bind = "127.0.0.1"
wave_stall_minutes = 30
FieldDefaultDescription
enabledfalseWhether to start the coordination broker
port9119HTTP port for the broker server
bind"127.0.0.1"Bind address – never bind to 0.0.0.0 on shared machines
wave_stall_minutes30Minutes without wave progress after which the broker reports the wave as stalled on GET /status, in git paw status, and on the dashboard status line. “Progress” is a wave phase advancing or a branch tip moving, and nothing else — see Wave staleness. 0 disables the stalled flag while still reporting idle_seconds. Omitted → the documented default

The default of 30 minutes sits just above the unattended drive loop’s 25-minute heartbeat, so a wave that is merely between heartbeats is not reported as stalled. A config written before the key existed loads unchanged and gets the default.

When the broker is enabled, git-paw injects the GIT_PAW_BROKER_URL environment variable into each agent pane, pointing to http://<bind>:<port>. Agents use this URL to communicate with the broker.

Warning — a non-loopback bind removes a protection and adds exposure. The broker authenticates nothing: whoever reaches the port can read the whole coordination log and publish any message, including guidance that coding agents will act on — up to whatever their CLI’s permission mode lets them run without asking. On the default loopback bind, git-paw checks every request’s Host header so a web page cannot reach the broker by DNS rebinding. Setting bind to "0.0.0.0" or a LAN address disables that check — git-paw cannot know your network’s names for the machine — and makes the broker reachable from the network with no authentication at all. It also stops blocking DNS rebinding: the Origin check still runs on every bind and still blocks browser writes and cross-origin reads, but a rebinding page is same-origin with the broker and its GETs carry no Origin, so it can read /log, /status and /messages/*. Only a loopback bind prevents that. Both the broker and the command that launches the session print a warning on stderr when you set a non-loopback bind. See Security Posture.

Filesystem watcher

The broker’s filesystem watcher publishes agent.status: working whenever an agent’s git status changes. By default, an agent that publishes agent.artifact status: "committed" and then keeps editing is re-entered into the working state — a file modification observed within a TTL window after the commit re-publishes working, so the dashboard reflects the agent’s continued activity instead of sticking on committed.

[broker.watcher]
republish_working_ttl_seconds = 60
FieldDefaultDescription
republish_working_ttl_seconds60Seconds after a committed event during which a file write re-publishes working. 0 disables the auto-republish (restoring the prior “committed is terminal until the agent itself republishes” behaviour). Non-zero values below 5 are clamped to 5 with a warning.

Past the TTL window the agent is considered settled at committed; only an explicit agent.status from the agent itself transitions it out.

Multi-repo port assignment

If you run git-paw sessions for multiple repositories at the same time, each session needs a different port. Set a unique port in each repo’s .git-paw/config.toml:

# Repo A
[broker]
enabled = true
port = 9119

# Repo B (in its own .git-paw/config.toml)
[broker]
enabled = true
port = 9120

This is required, not advisory. Before binding, git-paw probes the configured port and asks the listener which repository it serves (GET /status reports repo_root). A broker for this repository is reattached to; a broker for a different repository is refused:

port 9119 is held by a git-paw broker for another repository
(/home/you/other-repo) — set a distinct [broker] port in
.git-paw/config.toml for one of the repositories

Give one of the two repositories its own port and start again. The refusal exists because reattaching would route this repository’s agents into the other repository’s broker while this repository’s dashboard rendered an empty, unwatched state.

The check runs before anything is created — and before you are asked anything. git paw start (including --from-specs, the supervised launch, and session recovery) probes the port as soon as it has read your config, ahead of the branch and CLI pickers, so you are never asked to choose for a launch that cannot proceed and a refused launch leaves no session, no panes, and no session receipt behind. That ordering matters: git-paw injects GIT_PAW_BROKER_URL into every agent pane, so a refusal that came only when the dashboard started would already have handed your agents the other repository’s broker.

Recovering a stopped session probes the port recorded in the saved session rather than the configured one, because that is the URL the rebuilt agent panes are given — they can differ if you changed [broker] port after the session was saved. --dry-run creates nothing and never probes.

git paw status and git paw doctor use the same repository-aware probe, so neither reports a neighbouring repository’s broker as your session’s own:

Broker:  http://127.0.0.1:9119 (held by another repository's git-paw broker)
⚠ broker          enabled — 127.0.0.1:9119 is held by another repository's git-paw broker
                  → set a distinct `[broker] port` in .git-paw/config.toml for one of the repositories

A variant of the message appears when the port holder does not report a repository at all:

port 9119 is held by a git-paw broker for another repository (it does not
report which — an older git-paw broker may be holding the port) — set a
distinct [broker] port in .git-paw/config.toml for one of the repositories

An unknown owner is never assumed to be yours. This is what you see when a session launched by an older git-paw is still running on the port — stop that session, or move this one to a different port.

A port held by something that is not git-paw at all reports the separate port ... is already in use by another process error.

Each session also keeps its own broker log, <session_name>.broker.log, so two repositories never write to — or purge — the same file. A session saved by an earlier git-paw recorded a single shared broker.log; that recorded path stays authoritative for both the broker and purge until the session is resumed or recovered, which migrates it to the per-session name.

See Agent Coordination for usage details.

Supervisor

Configure the supervisor agent for orchestrating parallel coding sessions. When enabled, the supervisor monitors agents, runs tests, verifies work, and coordinates merges.

[supervisor]
enabled = true
cli = "claude"
test_command = "just check"
lint_command = "cargo clippy -- -D warnings"
build_command = "cargo build"
fmt_check_command = "cargo fmt --check"
doc_build_command = "mdbook build docs/"
doc_tool_command = "cargo doc --no-deps"
spec_validate_command = "openspec validate {{CHANGE_ID}} --strict"
security_audit_command = "cargo audit"
agent_approval = "auto"
approval = "full-auto"
verify_on_commit_nudge = true
strict_branch_guard = true
manual_approvals_log = true
no_progress_window_seconds = 1500
context_bloat_threshold_k = 250
blocked_on_supervisor_window_seconds = 900
branch_refresh = false
FieldDefaultDescription
enabledfalseWhether to use supervisor mode by default (can also use --supervisor flag, or override with --no-supervisor for a single session)
cli(uses default_cli)The orchestrator’s CLI, named in its context (.git-paw/AGENTS.local.md); no pane runs it, and no orchestrator CLI is needed to launch. Also the coding agents’ last CLI fallback (--cli > default_cli > [supervisor].cli)
test_command(none)Test runner — gate 1 (e.g. "just check", "cargo test", "npm test", "pytest")
lint_command(none)Lint check — gate 1 (e.g. "cargo clippy -- -D warnings", "npm run lint", "ruff check .", "golangci-lint run")
build_command(none)Compile step — gate 1 when build is distinct from test (e.g. "cargo build", "npm run build", "mvn package", "go build ./...")
fmt_check_command(none)Formatter check — gate 1 (e.g. "cargo fmt --check", "prettier --check .", "gofmt -l .", "black --check .")
doc_build_command(none)Documentation build — gate 4 (e.g. "mdbook build docs/", "sphinx-build", "mkdocs build")
doc_tool_command(none)API-doc generator — distinct from doc_build_command; gates the per-language extractor for changed public items (e.g. "cargo doc --no-deps", "sphinx-build -W docs docs/_build", "javadoc", "npx typedoc", "go doc"). Renders empty (not (not configured)) when unset so the surrounding prose reads naturally
spec_validate_command(none)Spec validator — gate 3 (e.g. "openspec validate {{CHANGE_ID}} --strict" for OpenSpec). {{CHANGE_ID}} is substituted by the supervisor agent at verification time with the change name being audited; it is not expanded at config load
security_audit_command(none)Security audit tooling — gate 5 (e.g. "cargo audit", "npm audit", "bandit -r .", "gosec ./...")
agent_approval"auto"Permission level for coding agents: "manual", "auto", or "full-auto"
approval(unset)The orchestrator’s own permission level: "manual", "auto", or "full-auto", distinct from agent_approval. No pane runs an orchestrator, so it changes no pane command; it is the approval level of the drive loop’s headless orchestrator invocations, whose flags resolve for (cli, approval) and fall back to agent_approval when unset. full-auto on a CLI with no flag row and no approval_args override warns and runs the invocation without flags. Coding agents resolve from agent_approval alone
verify_on_commit_nudgetrueWhen on, the broker posts a supervisor.verify-now message to the supervisor inbox on every agent.artifact { status: "committed" }, so the supervisor verifies each commit promptly on an explicit event instead of batching. Set false to fall back to sweep-cadence verification
strict_branch_guardtrueWhen true, a per-worktree pre-commit hook refuses any commit whose checked-out branch differs from the branch the worktree was created for, blocking cross-worktree contamination (linked worktrees share .git/refs, so a stray cd can otherwise advance the wrong branch). Set false to disable enforcement — the post-commit hook still publishes an agent.feedback + agent.learning record when it detects a mismatch (detection without enforcement)
manual_approvals_logtrueWhen true, every live permission prompt the --unattended drive loop escalates for a human decision is appended to .git-paw/sessions/<session>.manual-approvals.jsonl and surfaced via git paw approvals. Applies to --unattended runs only — an attended session runs no drive loop and records nothing. On a pattern’s first sighting a permission_pattern learning is also emitted (when learnings = true). Set false to suppress both the log writes and the learnings emission; the opt-out affects writes only, so git paw approvals still reads any pre-existing log
no_progress_window_seconds1500 (~25 min)Read by .git-paw/scripts/sweep.sh: an agent is flagged no-progress when BOTH its completed-task-checkbox count AND its branch commit count stay unchanged for this many seconds. Set longer to tolerate long build/research steps, shorter to nudge sooner. Omitted → the documented default
context_bloat_threshold_k250 (thousand tokens)Read by .git-paw/scripts/sweep.sh: when an agent’s pane shows a /clear to save <N>k tokens hint whose N meets or exceeds this value, the agent is proactively flagged context-bloat so the supervisor can pre-empt the eventual freeze. Omitted → the documented default
blocked_on_supervisor_window_seconds900 (~15 min)Read by .git-paw/scripts/sweep.sh: an agent whose latest unanswered agent.blocked names the supervisor as the blocker is flagged blocked-on-supervisor once it has waited longer than this window, forcing the supervisor to answer. Omitted → the documented default
branch_refreshfalseWhen true, the unattended drive loop rebases an idle, clean worker branch onto the default branch after a successful merge — see Branch refresh for the full precondition gate list. Default off: history is rewritten under a live agent only on explicit opt-in
learningsfalseMaster switch for learnings mode: when true (and the broker is enabled), the session’s friction signals are appended to .git-paw/session-learnings.md. Tuned by the [supervisor.learnings_config] sub-table
auto_revertfalseUsed by opsx role gating in block mode: when true, the supervisor reverts a coding agent’s archive commit without first confirming with you. Default off — a revert always asks first

Approval flag resolution. When composing a pane’s launch command, git-paw resolves the permission flags for a (CLI, level) pair in order: (1) the per-CLI [clis.<name>] approval_args override, (2) the built-in table below, (3) no flags. Coding-agent panes always resolve at agent_approval; no pane resolves at approval — only the drive loop’s headless orchestrator invocation does. This resolution applies on every launch path that spawns a worker CLI — supervisor mode, a bare git paw start, a non-supervisor --specs / --from-all-specs launch, and git paw add alike — not only supervisor sessions, even though agent_approval is declared under [supervisor]. A bare or spec-driven launch with no [supervisor] section at all still resolves agent_approval against its default ("auto").

CLIfull-autoauto
claude--dangerously-skip-permissions--permission-mode acceptEdits
codex--dangerously-bypass-approvals-and-sandbox--sandbox workspace-write
agy--dangerously-skip-permissions(none)
qwen--yolo(none)

Any other CLI (or manual anywhere) resolves to no flags — use approval_args to supply native flags for CLIs not in the table.

auto resolves to a deterministic flag wherever the CLI has a low-friction “accept edits” mode distinct from manual and from full bypass, rather than the empty string — an auto pane no longer silently inherits whatever default its CLI’s config directory happens to carry. For Claude, --permission-mode acceptEdits has the CLI accept file edits without asking; shell commands still raise a prompt.

These resolved flags are git-paw’s only automatic influence on what an agent runs without a prompt. git-paw approves no prompt itself: anything the chosen mode leaves prompting is escalated for a human decision under --unattended, and answered only by an explicit sweep.sh approve.

One further influence exists, and only because you ask for it: git paw setup-cli <CLI> --allowlist writes path-scoped allow rules for git-paw’s own helper scripts into the agent CLI’s settings. Running that command is the consent — no launch, recovery, drive loop, dashboard or MCP tool ever applies it, it prints its plan before writing, and --undo removes exactly what it added. Those rules are then the CLI’s rules, enforced by the CLI’s own permission system; git-paw still decides nothing about any prompt. See Agent permissions.

Gate-command templating. The eight *_command keys feed the supervisor skill’s five verification gates (testing, regression analysis, spec audit, doc audit, security audit). For each key set on this section, the supervisor skill substitutes the matching {{...}} placeholder at session boot and the supervisor agent runs the literal command during that gate. For each key omitted, the placeholder renders as (not configured) and the supervisor agent skips that tooling step — the gate’s manual review still applies (e.g. the OWASP-category diff scan for the security gate, the spec scenario coverage check for the spec gate). Pre-v0.5.x configs that did not name any of the six new keys continue to work; they just run a less-rigorous verification cycle until the keys are filled in. A user wanting to explicitly opt out of a single gate’s tooling can set the field to "(not configured)" verbatim — the supervisor agent recognises that as the same skip token.

Resolution chain — git-paw picks supervisor mode using the first matching rule:

  1. --no-supervisor → off (highest precedence; overrides everything below).
  2. --supervisor → on.
  3. [supervisor] enabled = true → on.
  4. [supervisor] enabled = false → off.
  5. No [supervisor] section + --dry-run → off.
  6. No [supervisor] section + interactive TTY → prompts you.
  7. No [supervisor] section + non-TTY → off.

--supervisor and --no-supervisor are mutually exclusive — passing both produces a parse error.

Approval levels:

LevelBehavior
manualAgents prompt for every action (safest, slowest)
autoLow-friction mode — the CLI’s mapped auto flag from the table above (e.g. accept-edits for Claude); a CLI with no mapped flag keeps its default behavior
full-autoSkip all permission prompts (fastest, agents run unattended)

The supervisor translates the approval level into CLI-specific flags at launch (e.g. --dangerously-skip-permissions for Claude in full-auto mode).

Deprecated auto-approval tables

[supervisor.auto_approve] and [supervisor.common_dev_allowlist] are deprecated and have no effect. They configured a git-paw command classifier that decided an agent’s command was safe and then answered the permission prompt for it. That classifier is retired: three successive security reviews each found new command shapes it wrongly called safe, and each fix opened the next hole. git-paw now makes no safety judgement about an agent’s command and approves no permission prompt.

Both tables, and every field in them, still parse and round-trip, so an existing configuration loads and saves unchanged — nothing breaks and no migration is forced. But:

  • none of their values is consulted by anything;
  • each configuration file that declares either table prints one deprecation warning per process, naming the tables it found, on stderr;
  • git paw init no longer writes either table into the default config.
# Deprecated — parsed, warned about, and ignored.
[supervisor.auto_approve]
enabled = true
safe_commands = ["just lint", "just test"]
stall_threshold_seconds = 30
approval_level = "safe"
approve_worktree_writes = true

[supervisor.common_dev_allowlist]
enabled = true
stacks = ["rust"]
extra = ["just check"]

Delete them whenever convenient. A configuration that git-paw v0.16.0 saved may carry a [supervisor.common_dev_allowlist] table you never wrote by hand; it will warn until you remove it.

What to use instead. Deciding whether a command may run is the agent CLI’s job:

  • Move safe_commands, stacks and extra entries into your agent CLI’s own allow rules — its project-level or user-level allowlist. That is the only place a standing grant can live, and it is what makes a routine cargo test stop prompting. git paw approvals reports which prompts recur, with a hint for which of those two allowlists fits.
  • For a run that must not stop for prompts at all, set [supervisor] approval = "full-auto" and run inside the documented sandbox. Note that full-auto skips the prompts entirely, so nothing is escalated either.
  • approve_worktree_writes and the protected-path and .git/-write rules it interacted with are gone. Those writes now reach a human like any other prompt; for mechanical enforcement use deny rules in the CLI’s own settings, or the sandbox.

See Agent permissions and the Security Posture.

Durable grants the classifier already accepted. Up to v0.16.0 the classifier preferred the “Yes, and don’t ask again” option for prompts it judged safe, and your agent CLI persisted those as permanent permission rules in its own settings file. Retiring the classifier stops new ones being created; it removes none. Review that file and delete grants you did not intend — git-paw never edits or deletes it.

Conflict detector tuning

When supervisor mode is enabled, the broker runs an in-process conflict detector that auto-emits agent.feedback (and optionally agent.question) on forward, in-flight, and ownership conflicts. See Agent Coordination § Automatic Conflict Detection for the runtime semantics; the table below documents the configuration surface.

[supervisor.conflict]
window_seconds = 120
warn_on_intent_overlap = true
escalate_on_violation = true
FieldDefaultDescription
window_seconds120Seconds the detector waits before escalating an unresolved in-flight conflict to the supervisor inbox via agent.question.
warn_on_intent_overlaptrueMaster switch for forward-conflict feedback. When false, two agents declaring overlapping agent.intent files no longer trigger agent.feedback, but the intent tracker still records them (so in-flight and ownership detection continue to work).
escalate_on_violationtrueWhether ownership violations escalate to the supervisor inbox. When false, the violator still receives agent.feedback, but no follow-up agent.question lands in the supervisor inbox.

The [supervisor.conflict] table is fully optional. A v0.4 config with [supervisor] and no [supervisor.conflict] loads cleanly with every field at the defaults above. Setting [supervisor] enabled = false (or omitting the section) disables the detector subsystem entirely — no auto-emitted warnings fire regardless of the values here.

Correction loop tuning

The [supervisor.correction] table governs the self-healing correction cycle the --unattended drive loop runs when a supervisor gate fails: instead of publishing agent.feedback into an inbox a blocked worker never polls, the loop injects the gate-tagged feedback straight into the worker’s pane and re-verifies. See Supervisor § Unattended correction loop for the runtime walkthrough.

[supervisor.correction]
auto_loopback = true       # default false — opt in to enable the loop
max_cycles = 5
on_exhausted = "escalate"  # "escalate" (default) | "abandon"
escalate_after_cycles = 3
gate_tags = ["testing", "regression", "spec audit", "doc audit", "security audit", "scope"]
FieldDefaultDescription
auto_loopbackfalseWhether a failing gate automatically re-engages the worker’s pane with that gate’s feedback. Off by default so an existing unattended setup behaves identically after an upgrade; the recommended config above turns it on. When false, the feedback is published exactly as before and no keystrokes are sent.
max_cycles5Verify↔fix rounds the loop drives for one branch before giving up and applying on_exhausted. A branch that passes its gate leaves the cycle early and its counter resets. Must be a positive integer — 0 is a config error; to turn the loop off set auto_loopback = false.
on_exhausted"escalate"What to do with a branch that reaches max_cycles. "escalate" flags it to the orchestrator/human as unrecoverable; "abandon" marks it failed and leaves it un-corrected without an escalation. Neither re-engages the branch again. Any other value is a config error naming the accepted set.
escalate_after_cycles3Cycle count at which a slow-converging worker is flagged once, as an early heads-up, while re-engagement continues to max_cycles. No distinct early flag is emitted when this is greater than or equal to max_cycles. Must be a positive integer — 0 is a config error; to suppress the flag, set it to max_cycles or higher.
gate_tags["testing", "regression", "spec audit", "doc audit", "security audit", "scope"]Gate names recognised as a [<gate>] prefix on a supervisor agent.feedback error line; only a matching tag (case-insensitive) counts as a failing gate verdict that starts a correction cycle. The default reproduces git-paw’s own five-gate framework plus scope exactly. A consumer whose review process names different gates lists them here instead — the correction loop itself is unaffected, only the tag vocabulary it recognises. Must be non-empty; an empty list is a config error (it would silently disable the loop even with auto_loopback = true — set auto_loopback = false instead).

The [supervisor.correction] table is fully optional. A v0.13.0 config with [supervisor] and no [supervisor.correction] loads cleanly with every field at the defaults above — and because auto_loopback defaults to false, its observable behaviour is unchanged. Cycle counts live in the drive loop’s in-process state and are not persisted: restarting the supervisor resets every branch’s budget.

When [supervisor] learnings = true, a branch that exhausts its budget also appends a correction_exhausted learning carrying the branch, the worker CLI, and the cycle count — the signal that tells you a worker’s model tier is under-powered for a class of task. With learnings disabled, nothing is recorded.

Headless escalation hand-off

The [supervisor.escalation] table controls whether the --unattended drive loop hands a judgment call — an ambiguous question, a merge decision or a correction-exhausted branch, never a permission prompt — to a one-shot, non-interactive run of the orchestrator CLI. See Supervisor § Headless hand-off of judgment calls.

[supervisor.escalation]
enabled = true
timeout_seconds = 1800
FieldDefaultDescription
enabledtrueWhether to hand judgment calls to a headless one-shot when one resolves ([supervisor].cli, else default_cli, with a headless_args form). false keeps the broker-only path — set it when you drive the session from your own interactive orchestrator.
timeout_seconds1800Upper bound on one invocation; on expiry its whole process group is killed and the outcome recorded as timeout. Must be a positive integer — 0 is a config error naming the key.

The table is optional: a config without it loads with both defaults. The invocation’s permission level is [supervisor] approval (falling back to agent_approval).

Routing through the supervisor (/tell)

The [supervisor.tell] table tunes the /agents and /tell commands you type to the orchestrator to route prompts to individual agents (see Routing through the supervisor).

[supervisor.tell]
mode = "feedback"
inventory_max_age_seconds = 60
FieldDefaultDescription
mode"feedback"Default delivery channel for /tell. "feedback" queues an agent.feedback the target picks up on its next inbox poll (safe for mixed-mode sessions). "send-keys" injects the prompt straight into the target pane with tmux send-keys — used only when the target is in accept-edits mode, otherwise /tell falls back to feedback and prints a note.
inventory_max_age_seconds60How stale the cached /agents inventory may be before /tell//agents re-poll the broker. Lower it for tighter freshness at the cost of more frequent polling.

The [supervisor.tell] table is fully optional. A v0.5.0 config with [supervisor] and no [supervisor.tell] loads with both defaults above and round-trips unchanged.

Learnings mode tuning

When supervisor mode is active, the parent [supervisor] learnings = true flag (default false) activates the learnings subsystem. Entries are appended to .git-paw/session-learnings.md covering the four deterministic categories (conflict events, where agents got stuck, recovery cycles, permission patterns — see Learnings). The [supervisor.learnings_config] sub-table tunes the flush cadence; the master switch lives on the parent table.

[supervisor]
learnings = true

[supervisor.learnings_config]
flush_interval_seconds = 60
broker_publish = "auto"
FieldDefaultDescription
flush_interval_seconds60How often the learnings aggregator flushes accumulated entries from memory to .git-paw/session-learnings.md. The file is append-only across sessions; a longer interval batches more entries per write.
broker_publish"auto"Whether flushed entries are also published to the broker as agent.learning messages. "auto" follows [broker] enabled (publish when the broker is running, file-only when it is not); "force_off" keeps file-only output even with an active broker. The Markdown file is written either way.

There are no configuration fields for the qualitative signals (recurring_failure_shape, doc_gap, adr_drift, scope_mistake, tooling_friction). Their detection thresholds live in the supervisor skill prose, not in [supervisor.learnings_config] — to tune how readily the supervisor publishes a category, edit your local copy of the supervisor skill rather than a config value. The supervisor publishes each qualitative learning through the bundled .git-paw/scripts/sweep.sh learn helper, so no broad curl allowlist grant is required.

See the Learnings Mode chapter for the category-by-category walkthrough, the output-file format, and how to consume learnings programmatically via the agent.learning broker variant and the MCP get_learnings() tool.

opsx role gating

[opsx]
role_gating = "warn"  # "warn" (default) | "block" | "off"

git-paw cannot add a permission check to the /opsx:verify and /opsx:archive slash commands themselves (they live in the OpenSpec project). Instead, when the session’s spec engine is OpenSpec, a post-commit guard watches for archive activity committed from a coding-agent worktree and reacts per role_gating:

ModeBehaviour on a coding-agent archive
warn (default)publish an agent.feedback to the offending agent and record an agent.learning with category permission_pattern
blockwarn behaviour plus publish an agent.feedback to the supervisor requesting it revert the offending commit (the supervisor performs the revert via its merge-orchestration skill — git-paw never runs git revert itself)
offguard disabled entirely

The guard is inert under non-OpenSpec engines (speckit, markdown, or no spec source) regardless of the mode, and the /opsx: forbidden-command sections are omitted from the bundled coordination/supervisor skills there too.

A commit is treated as archive activity when either its message matches the canonical archive shape chore(specs): archive <name>; sync deltas to main specs or its diff moves files into openspec/changes/archive/<name>/ and/or adds a main spec under openspec/specs/<capability>/spec.md. The supervisor’s own archives (agent_id == "supervisor") never count as a violation. See the Supervisor guide for how to read the warning text and tune block mode with [supervisor] auto_revert.

v0.6.0 behaviour change. role_gating defaults to warn. Sessions where a coding agent archives a change will now see guard feedback and a learnings record. Set role_gating = "off" to restore the v0.5.0 (no-guard) behaviour.

Governance

Point git-paw at your project’s existing governance documents so the supervisor can read them as context. All fields are optional — list only the docs you have.

[governance]
adr = "docs/adr"                          # directory of ADR files
test_strategy = "docs/test-strategy.md"   # single Markdown file
security = "docs/security-checklist.md"   # single Markdown file
dod = "docs/definition-of-done.md"        # single Markdown file
constitution = ".specify/memory/constitution.md"  # single Markdown file
readme = "README.md"                      # repository README
docs = "docs/src"                         # documentation root directory
FieldKindDescription
adrdirectoryArchitecture Decision Records. git-paw does not care which convention (Nygard, MADR, adr-tools) — point at the folder where they live.
test_strategyfileThe team’s test-strategy document.
securityfileSecurity checklist (OWASP-style, project-specific, whatever the team uses).
dodfileDefinition of Done for completed work.
constitutionfileProject constitution. Spec Kit users normally let this auto-wire (see below).
readmefileRepository README. Surfaced by the MCP get_readme tool (MCP server); unset → the tool returns null.
docsdirectoryDocumentation root. Surfaced by the MCP list_docs/get_doc tools, which enumerate and read *.md files confined to this directory; unset → those tools return empty/null.

git-paw does not dictate the structure, format, or rubric of any of these documents. The supervisor LLM reads them as context and applies judgment during its existing audit flow. There is no [governance.gates] table and no per-doc enforcement switch — gating-per-doc would require git-paw to define “failure” for each doc type, and that is a process choice your team owns.

Paths are stored verbatim and resolved against the repository root at use time. Relative paths point at files inside the repo; absolute paths are accepted as-is. A path that does not exist still loads cleanly — git-paw does not stat the filesystem at config-load. If you point at a missing file, the runtime consumer flags it.

Spec Kit constitution auto-wiring

When governance.constitution is unset AND [specs] type = "speckit", git-paw probes for <specs_dir>/../memory/constitution.md and, if present, populates governance.constitution automatically. This means a typical Spec Kit project (with .specify/specs/ and .specify/memory/constitution.md) gets the constitution wired up without any [governance] entry.

Explicit values always win. If governance.constitution is set to anything — including a path that does not exist or an empty string — auto-wiring is skipped:

[governance]
constitution = ""   # disables auto-wiring without deleting the slot

Auto-wiring only runs for the Spec Kit backend. With [specs] type = "openspec", type = "markdown", or no [specs] section, governance.constitution stays whatever the TOML says (defaulting to None).

What the supervisor does with these paths

This [governance] table is the storage slot. The runtime consumer is the supervisor’s boot prompt, which lists each configured doc so the supervisor can read it and apply it to its audit. See the Governance chapter of the user guide for what that looks like end-to-end.

MCP

[mcp]
name = "my-project"        # optional; default: "git-paw"
allow_repo_writes = false  # optional; repository config only
FieldDefaultPurpose
name"git-paw"The identity the git paw mcp server advertises as serverInfo.name in the MCP initialize handshake. Set a custom value to distinguish multiple repositories that each run an MCP server. Distinct from the client-side mcpServers key (the display label in clients like Claude Desktop), which you rename independently. See the MCP server chapter.
allow_repo_writesfalseWhether the MCP server may write file content into this repository. When true, a root session (a server in the main working tree) gets the spec-authoring write tools create_spec and write_spec_artifact. See Spec authoring.

The section is optional; omitting it (or name) makes the server advertise git-paw.

allow_repo_writes is the repository’s consent, so it is honoured only from the repository’s own .git-paw/config.toml. A value in the user-level config has no effect. The server reads it once at startup, so restart the MCP server after changing it. It never enables writes from a worktree session. It governs only tools that write file content into the repository: it does not gate the root-session lifecycle tools (start_session, pause_session, stop_session, purge_session) or the verdict tools (publish_verified, publish_feedback), which follow the root-session rule alone. The read-only validate_spec is available whatever its value.

Dashboard

Configure the dashboard TUI rendered in its own pane when the broker is enabled (pane 0 in every mode; in supervisor mode it is the full-width top row).

[dashboard]
show_message_log = true

[dashboard.broker_log]
max_messages = 500
default_visible = true
height_lines = 20
FieldDefaultDescription
show_message_logfalseWhen true, the dashboard renders the legacy scrolling broker-message panel. Superseded by the type-filterable Broker log panel below; leave false for a more compact layout.

Broker log panel

The [dashboard.broker_log] table configures the v0.6.0 Broker log panel — a scrolling, type-filterable view of recent broker messages that fills the screen region freed when v0.5.0 removed the prompt inbox.

FieldDefaultDescription
max_messages500Maximum number of messages retained in the panel’s in-memory ring buffer. When the buffer is full, the oldest message drops off as new ones arrive. The log is in-memory only and is not persisted across dashboard restarts.
default_visibletrueWhether the panel is shown when the dashboard first launches. The l hotkey toggles visibility at runtime regardless of this value.
height_lines20Number of terminal rows the panel occupies when visible — raised from the v0.6.0 fixed 12 so more messages show without scrolling. The agent-status table keeps a positive minimum height and absorbs slack, so the panel stays at this height on tall terminals and yields space before the table shrinks on short ones.

An absent [dashboard.broker_log] table — as in any v0.5.0 config — loads these defaults, so existing config files parse unchanged.

See Dashboard for the panel’s hotkeys, filter chips, and details overlay.

Multi-repo configuration

Each repository can have its own dashboard settings in .git-paw/config.toml. The repo-level config overrides the global config.

Layout

Configure the visual styling git-paw applies to the tmux sessions it creates.

[layout]
border_affordances = true  # default
FieldDefaultDescription
border_affordancestrueWhen true, git-paw applies pane affordances to the sessions it creates: double-line pane borders (═║ instead of the default ─│), a per-pane label bar showing each pane’s index and role/branch (e.g. 0: supervisor, 1: dashboard, 2: feat/foo), a dim border on inactive panes, and a cyan-bold border on the focused pane. Set to false to opt out and inherit your own tmux styling instead.

These options are scoped to git-paw-managed sessions (paw-*) only — your other tmux sessions are never touched. They apply to both git paw start and supervisor-mode sessions.

This setting is purely visual. Independently of it, git-paw tags every pane it creates with a pane-scoped @paw_role tmux option naming that pane’s role (supervisor, dashboard, or the agent’s branch). The option draws nothing on its own — only the label bar above renders it — but git paw status and git paw doctor use it to tell a coding-agent pane from an infrastructure pane when they check whether a session’s agents are still running. Turning affordances off therefore changes how a session looks, never how accurately its state is reported.

When to disable. Turn border_affordances off if you run a tmux theme you prefer, are on tmux older than 3.2 (where the double border lines aren’t recognised — git-paw warns and continues, but you may prefer the consistent default look), or find the label strip noisy on small terminals.

See Supervisor for how the labelled layout looks in a supervisor session.

Merging Rules

When both global and repo configs exist, they merge with these rules:

FieldMerge behavior
default_cliRepo wins
default_spec_cliRepo wins
branch_prefixRepo wins
mouseRepo wins
worktree_placementRepo wins
docs_base_urlRepo wins
clisMaps merge (repo overrides per-key)
presetsMaps merge (repo overrides per-key)
specsRepo wins
loggingRepo wins
brokerRepo wins
supervisorRepo wins
dashboardRepo wins
governancePer-field merge (repo wins on each set field, unset fields fall back to global)
layoutRepo wins
opsxRepo wins
mcpPer-field merge (repo wins on each set field, unset fields fall back to global)
worktreeRepo wins (the whole table, including its env, ports, and hooks sub-tables)

Example: If global config defines [clis.my-agent] and repo config defines [clis.my-agent] with a different command, the repo version wins. But a [clis.other-tool] in global config still appears — maps are merged, not replaced.

Graceful Absence

If no config files exist, git-paw uses defaults:

  • No default CLI (prompts for selection)
  • Mouse mode enabled
  • No custom CLIs
  • No presets

Checking your configuration

git paw doctor reports what git-paw actually resolved from the files above — whether .git-paw/config.toml parses, the effective worktree_placement, the resolved spec system and its spec count, the [broker] bind/port state, and whether each [supervisor] gate command’s binary is on PATH. It also flags keys this version does not recognise, which is the quickest way to catch a typo’d field that would otherwise be silently ignored.

Doctor adds no configuration fields of its own — it only reads the settings documented on this page. See the Doctor chapter.

git paw doctor          # grouped report
git paw doctor --json   # same checks, machine-readable

Supported AI CLIs

git-paw auto-detects these AI coding CLIs on your PATH:

CLIBinaryDescriptionInstallCold resume
Claude CodeclaudeAnthropic’s AI coding assistantnpm i -g @anthropic-ai/claude-codeYes (built-in)
CodexcodexOpenAI’s coding agentnpm i -g @openai/codexNo
Antigravity CLIagyGoogle’s agentic coding CLI (replaces the retired Gemini CLI)See project docsNo
AideraiderAI pair programming in the terminalpip install aider-chatNo
VibevibeMistral AI’s coding CLISee project docsNo
QwenqwenAlibaba’s Qwen coding CLISee project docsNo
AmpampSourcegraph’s AI coding agentSee project docsNo
opencodeopencodeOpen-source terminal coding agentSee project docsNo
ClineclineCline’s coding agent CLISee project docsNo
DroiddroidFactory’s coding agent CLISee project docsNo
PipiTerminal coding agentSee project docsNo
JuniejunieJetBrains’ coding agent CLISee project docsNo
Cursor Agentcursor-agentCursor’s agent CLISee project docsNo
GitHub Copilot CLIcopilotGitHub Copilot in the terminalSee project docsNo
cncnContinue’s coding agent CLISee project docsNo
Kilo CodekiloKilo Code’s coding agent CLISee project docsNo
KimikimiMoonshot AI’s coding agent CLISee project docsNo

“Cold resume” means git-paw can restore the CLI’s prior conversation when recovering a stopped session or relaunching a dead pane, rather than starting it fresh — see Per-CLI cold resume. A “No” here reflects the CLI having no known session-assignment/resume flags, not a defect in it; add session_id_args / resume_args to a [clis.<name>] override (see Custom CLIs) to enable resume for a CLI that supports it but isn’t in git-paw’s built-in table yet.

The Cursor row names cursor-agent, Cursor’s agent CLI. The bare cursor binary is the Cursor editor launcher on most installations, so git-paw no longer auto-detects it as an AI CLI. This affects auto-detection only — if cursor is the command you want a pane to launch, any of these still resolves it unchanged:

  • git paw start --cli cursor,
  • a [clis.cursor] entry (see Custom CLIs),
  • a saved session whose recorded cli is cursor.

Spec-driven selection and git paw add validate availability against the detected list, so there a [clis.cursor] entry is the route to take. default_cli is not an escape hatch: it only pre-selects a row in the interactive picker, and the picker is built from the detected list — an undetected cursor has no row to pre-select, so the setting has no effect.

Headless (one-shot) form

The --unattended drive loop can hand a judgment call to the orchestrator CLI as a single non-interactive turn that reads its prompt from stdin — see Headless hand-off of judgment calls. git-paw knows the headless form of two CLIs:

CLIHeadless arguments
claude-p
codexexec
Every other CLInone — set [clis.<name>].headless_args

Any CLI that can run one turn from stdin gains the hand-off through a headless_args override in its [clis.<name>] entry; the override also wins over a built-in row. Without a form, the drive loop keeps every judgment call on the broker and says so at launch.

Per-CLI Setup Support

git paw setup-cli writes git-paw’s setup items into a CLI’s own settings. An item is available only when git-paw’s compiled fact table declares it for that CLI and those facts have been verified against an installed copy of it.

CLI--allowlist--trust--hooks
claudeSupported, verified against Claude Code 2.1.220 (2026-10-15)Not supportedNot supported
Every other CLINot supportedNot supportedNot supported

Nothing is withheld here as a judgement about those CLIs — their settings formats simply have not been checked yet. The per-CLI audit that adds them is planned for v0.23.0. Until then setup-cli reports the item as not supported and writes nothing, which is the honest answer rather than a guess at a file format.

A Claude Code variant registered under another name (a wrapper binary in a [clis.<name>] entry) has no row of its own, but is still covered by git paw setup-cli claude: the target settings file belongs to the repository, not to the binary.

git paw doctor reports the current status for your installation.

How Detection Works

git-paw scans your PATH for each known binary name. If found, it records the full path and makes the CLI available for selection.

Detection runs every time you start a session, so newly installed CLIs are picked up automatically.

Adding Custom CLIs

Any AI CLI not in the list above can be registered as a custom CLI:

# Register by path
git paw add-cli my-agent /usr/local/bin/my-agent

# Register by binary name (resolved via PATH)
git paw add-cli my-agent my-agent --display-name "My Agent"

Custom CLIs appear alongside detected ones in the selection prompt. See Configuration for more details.

Deduplication

If a custom CLI has the same binary name as a detected one, the custom definition takes precedence. This lets you override the path or display name of a detected CLI.

Missing CLIs

If a custom CLI’s command cannot be found (the binary doesn’t exist at the specified path and isn’t on PATH), it is excluded from the selection list with a warning. This prevents launching sessions that would immediately fail.

Prompt-shape detection

The list above is git-paw’s binary detection — it says which CLIs it can launch. Separately, the supervisor’s prompt detectors recognise a launched CLI’s terminal surface (its ready banner, permission-prompt wording, and so on) via a prompt-shape profile. git-paw ships only the Claude Code profile, embedded in the binary; no other CLI in the table above has a shipped profile, because a guessed one would misdetect confidently rather than falling back visibly. Every CLI — listed or custom — resolves the embedded Claude default for any field it has no profile override for. See CLI Prompt-Shape Profiles for the field reference and how to author one.

CLI Reference

git-paw is invoked as git paw (or git-paw). Below is the reference for all subcommands and flags.

git paw

Running with no subcommand is equivalent to git paw start.

Parallel AI Worktrees — orchestrate multiple AI coding CLI sessions across git worktrees

Usage: git-paw [COMMAND]

Commands:
  start        Launch a new session or reattach to an existing one
  add          Attach a new worktree + agent pane to a running session
  remove       Detach a single agent from a running session
  pause        Pause the session (detaches client, stops broker, leaves CLIs running)
  stop         Stop the session (kills tmux, keeps worktrees and state)
  purge        Remove all worktrees and session state; keeps branches with unmerged commits
  status       Show session state for the current repo
  attach       Reattach the current terminal to the running session for this repo
  list-clis    List detected and custom AI CLIs
  add-cli      Register a custom AI CLI
  remove-cli   Unregister a custom AI CLI
  setup-cli    Apply git-paw's setup items to one AI CLI's own settings
  init         Initialize .git-paw/ directory and configuration
  replay       View captured session logs
  approvals    Report manually-approved command patterns for a session
  mcp          Run a Model Context Protocol (MCP) server over stdio
  doctor       Diagnose the environment, configuration, and repository state
  completions  Print a shell completion script to stdout
  help         Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

git paw init

Initializes a repository for git-paw. Creates the .git-paw/ directory, a default config.toml, the logs directory, the bundled helpers and the exported orchestrator skills, adds the orchestrator pointer to AGENTS.md, and sets up .gitignore.

Usage: git-paw init

Options:
  -h, --help  Print help

Running init is idempotent — it’s safe to run multiple times.

When it writes a fresh config.toml, init asks which spec system the project uses (recorded in [specs]; git-paw never auto-detects it) and whether to enable supervisor mode by default. A non-interactive run writes a commented [specs] template and [supervisor] enabled = false instead. Re-running init on an existing config is the upgrade path after a version bump — see Upgrading an Existing Config.

What it creates:

  • .git-paw/config.toml — default configuration
  • .git-paw/logs/ — log directory (added to .gitignore)
  • .git-paw/tmp/ — repo-local scratch for isolated verify worktrees and self-test sessions (added to .gitignore; preferred over OS temp because it is OS-independent)
  • .git-paw/scripts/sweep.sh — bundled supervisor helper
  • .git-paw/scripts/broker.sh — bundled agent-broker helper (the agent side of sweep.sh); the agent boot block calls it to self-report to the broker. See Coordination → Broker helper
  • .git-paw/scripts/docs-fetch.sh — bundled agent helper that fetches documentation on demand (see Docs Fetch Skill)
  • .git-paw/scripts/_paw_common.sh — shared preamble the bundled helpers source for repository-root resolution, Python 3 detection, and broker / docs URL discovery. Binary-managed like the helpers themselves: it is overwritten on every git paw init, and a helper cannot run without it
  • .git-paw/skills/orchestrator/SKILL.md — the orchestrator skill, rendered with your [supervisor] gate commands and configured spec format, for the agent you drive the session with; .git-paw/skills/cli-setup/SKILL.md beside it. git paw start re-renders both. See Quick Start: Orchestrator Mode
  • an orchestrator pointer section in the root AGENTS.md, between <!-- git-paw-orchestrator:start --> and <!-- git-paw-orchestrator:end --> (the file is created when absent; see AGENTS.md)
  • .gitignore entries for .git-paw/logs/, .git-paw/tmp/, .git-paw/worktrees/ (where agent worktrees live under the default child placement), .git-paw/skills/, .git-paw/session-summary.md, and .git-paw/session-learnings.md

A re-run reports a binary-managed file (a helper, an exported skill, the pointer section) as Updated only when its content changed, and closes with Already initialized. Nothing to do. only when nothing was created or changed.

Example:

git paw init

git paw start

Smart start: reattaches if a session is active, recovers if stopped/crashed, or launches a new interactive session. Reattach is checked before anything else — including entering supervisor mode — so re-running start against a repository that already has a live session never forks a second, parallel one; see Reattaching instead of forking.

Usage: git-paw start [OPTIONS]

Options:
      --cli <CLI>                    AI CLI to use (skips CLI picker)
      --branches <BRANCHES>          Comma-separated branches (skips branch picker)
      --from-all-specs               Launch from every discovered spec across all configured formats
      --specs [<SPECS>...]           Comma-separated spec names; bare flag opens picker (TTY required)
      --specs-format <SPECS_FORMAT>  Override spec format (openspec, markdown, speckit, superpowers) [possible values: openspec, markdown, speckit, superpowers]
      --dry-run                      Preview the session plan without executing
      --preset <PRESET>              Use a named preset from config
      --supervisor                   Enable supervisor mode for this session
      --no-supervisor                Disable supervisor for this session, overriding any [supervisor] enabled = true in config
      --force                        Bypass uncommitted-spec validation warning
      --no-rebase                    Skip rebasing existing agent branches onto the default branch before opening worktrees
      --unattended                   Drive the supervisor wave to completion unattended: escalate every permission prompt for a human decision without blocking the wave, detect completion, exit with a summary (approves nothing). On a repo with a live supervised session, binds a new drive loop to it instead of launching
  -h, --help                         Print help
FlagAccepted valuesPurpose
--cliname of a detected or custom CLISkip the interactive CLI picker; assign this CLI to every agent that doesn’t otherwise pin one.
--branchescomma-separated branchesSkip the interactive branch picker; launch one worktree per branch.
--from-all-specs(flag)Launch every discovered spec across the configured backend. Mutually exclusive with --specs.
--specscomma-separated spec names; bare flag opens a multi-select picker (TTY required)Narrow the session to named specs or open the picker. Mutually exclusive with --from-all-specs.
--specs-formatopenspec, markdown, speckit, superpowersSelect the spec system for this launch (overrides [specs] type in config). git-paw does not auto-detect the spec system from the filesystem.
--dry-run(flag)Print the session plan; create no worktrees and run no tmux commands.
--presetpreset name from configUse a named [presets.<name>] entry.
--supervisor(flag)Force supervisor mode on. Mutually exclusive with --no-supervisor.
--no-supervisor(flag)Force supervisor mode off (highest precedence in the resolution chain). Mutually exclusive with --supervisor.
--force(flag)Bypass the uncommitted-spec validation warning when launching from specs.
--no-rebase(flag)Skip the default-on rebase of existing agent branches onto the repository’s default branch.
--unattended(flag)Run the supervisor wave to completion with no human in the seat: engages supervisor mode and drives an in-process loop that escalates every live permission prompt for a human decision without blocking the wave, detects completion, and exits with a summary. It approves nothing. On a repository that already has a live supervised session, binds a new drive loop to it instead of launching. Mutually exclusive with --no-supervisor.

--from-all-specs and --specs are mutually exclusive — one launches every discovered spec, the other narrows to a subset or opens the picker.

--unattended engages supervisor mode (equivalent to also passing --supervisor) and then drives the wave to completion in-process instead of returning with an attach hint: it polls the panes on a ~15-second cadence, escalates every live permission prompt it finds — on a coding pane or the supervisor’s own — for a human decision WITHOUT blocking the rest of the wave, and exits with a summary on completion, a stuck signal, or a heartbeat after ~25 minutes without progress (no new commit on a coding branch and no agent phase advance). It is designed for detached operation and needs no attached terminal. It is mutually exclusive with --no-supervisor.

It approves nothing. git-paw sends no approval keystroke on its own initiative; whether a command runs without a prompt is decided by the agent CLI’s own permission system. For a run that truly does not stop, pre-approve the commands in that CLI’s own allow rules, or use [supervisor] agent_approval = "full-auto" inside the documented sandbox. See Supervisor → Unattended mode.

On a repository that already has a live supervised session, --unattended binds a new drive loop to that session instead of launching or refusing: an active session is driven as it stands (no pane rebuilt, no worktree touched, nothing injected), a paused one is revived first, a stopped one recovered first. The terminal is never attached. Launch-selection flags are not applied to a live session, so re-running the command that launched a wave is what drives it again after its loop exits. The loop drives the session’s own roster and reaches the broker at the endpoint recorded for that session, not the configured one. A session launched without supervisor mode is refused — it leaves its prompts for a human to submit. At most one drive loop may drive a session at a time; a second --unattended refuses, naming the holding process, before changing anything. See Re-attaching a drive loop to a live session.

No orchestrator runs inside the session — it drives from your own agent session, pointed at .git-paw/AGENTS.local.md — so the loop sweeps and types into coding-agent panes only and records every judgment call (a live prompt, a worker’s question, a merge decision, a non-converging branch) as a broker review item, injecting nothing into any pane. The loop never waits on a reply. Nudges aimed at a worker pane stop once that worker has finished (done/verified) — but not while it is merely quiet at blocked/committed, where the correction loop must still reach it. See the loop is a pump, the supervisor is the brain.

The selection flags compose with --supervisor: git paw start --supervisor --specs a,b launches a supervisor session for only the named subset (a and b), exactly matching the non-supervisor --specs behaviour. --supervisor --from-all-specs launches every discovered spec, and --supervisor --specs (bare) opens the multi-select picker.

Examples:

git paw start
git paw start --cli claude
git paw start --cli claude --branches feat/auth,feat/api
git paw start --dry-run
git paw start --preset backend

# Launch every discovered spec
git paw start --from-all-specs
git paw start --from-all-specs --cli claude
git paw start --from-all-specs --dry-run

# Narrow to specific specs or open the multi-select picker
git paw start --specs add-auth,fix-session
git paw start --specs   # interactive picker (requires a TTY)

# Supervisor mode honours the same selection flags
git paw start --supervisor --specs add-auth        # one agent, for add-auth only
git paw start --supervisor --from-all-specs        # one agent per discovered spec

# Skip supervisor for this session even when `[supervisor] enabled = true` is set
git paw start --no-supervisor
git paw start --from-all-specs --no-supervisor

# Run the whole wave to completion with no human in the seat (detached)
git paw start --unattended --from-all-specs
git paw start --unattended --branches feat/auth,feat/api

# After the loop exits (heartbeat, stuck, interrupted): the same command again
# binds a new loop to the still-live session
git paw start --unattended --from-all-specs

Every launch path refuses, before creating any worktree or pane, a session whose branches do not all derive distinct broker agent_ids. The id is the slugified branch name, so two different branches can map to one: git paw start --branches feat/x,feat-x fails naming both branches and the shared id feat-x, because the two agents would otherwise share an inbox and receive each other’s feedback and verdicts. Rename one branch so the ids differ. (Only when the broker is enabled; with it off, nothing addresses agents by id and the launch proceeds as before.)

Supervisor mode resolution chain

git-paw decides whether to enter supervisor mode using this order (first match wins):

  1. --no-supervisor flag present → supervisor disabled (no prompt, regardless of config).
  2. --supervisor flag present → supervisor enabled (no prompt).
  3. [supervisor] enabled = true in config → supervisor enabled (no prompt).
  4. [supervisor] enabled = false in config → supervisor disabled (no prompt).
  5. No [supervisor] section + --dry-run → supervisor disabled (skip prompt).
  6. No [supervisor] section + interactive TTY → prompt “Start in supervisor mode?”.
  7. No [supervisor] section + non-TTY → supervisor disabled (fallback).

--supervisor and --no-supervisor are mutually exclusive at parse time; passing both is rejected by clap before any command runs.

See Spec-Driven Launch for details on spec formats and configuration.

git paw add

Hot-attaches a worktree and agent pane to a running session — bare, spec-driven or supervisor-mode — without a stop/purge/restart. The agent grid re-tiles to the layout a start of that many agents would produce, the new branch is registered in the session, and the agent boots with the same broker boot block + initial prompt a start-time agent receives. The orchestrator discovers it passively on its next broker poll, and an in-process drive loop picks it up on its next sweep — no restart.

Provide a branch name, or use --from-spec to derive the branch (and CLI) from a spec discovered in the configured spec system — OpenSpec, Markdown, Spec Kit, or Superpowers (same resolution as start --specs NAME). Adding past the 25-agent cap is rejected with the same “split into multiple sessions” message start uses. Adding to a paused session leaves the new pane paused until git paw start.

Usage: git-paw add [OPTIONS] [BRANCH]

Arguments:
  [BRANCH]  Branch to attach (omit when using --from-spec)

Options:
      --cli <CLI>              AI CLI for the new pane (defaults to the session's default CLI)
      --from-spec <FROM_SPEC>  Derive branch + CLI from a spec (OpenSpec change, Markdown spec, Spec Kit feature, or Superpowers plan)
  -h, --help                   Print help

Examples:

git paw add feat/new-thing
git paw add feat/api --cli codex
git paw add --from-spec add-export

Validation happens before any side effect: an unknown --cli errors with the detected CLI ids, and an unknown --from-spec errors with the discovered spec candidates — no worktree or pane is created in either case.

A branch whose broker agent_id is already taken by an agent in the session is refused the same way: the id is the slugified branch name, so git paw add feat-x against a session already running feat/x fails naming both branches and the shared id feat-x rather than attaching a second agent to one inbox. (Only when the broker is enabled; with it off nothing addresses agents by id.)

git paw remove

Detaches a single agent from any active session: closes its tmux pane, re-tiles the grid for the smaller agent count, removes its worktree (reusing git paw purge’s per-worktree teardown), and drops it from the session. The other agents are untouched; the orchestrator notices the departure on its next poll (the agent stops heartbeating) and a running drive loop stops driving it on its next sweep.

Safe by default: remove refuses to delete a worktree with uncommitted changes — listing the changed files — unless --force is passed. --keep-worktree detaches the pane and session entry but leaves the worktree and branch on disk (and skips the uncommitted-work check, since nothing is deleted). git paw remove supervisor is refused — use git paw stop to end the whole session.

Usage: git-paw remove [OPTIONS] <BRANCH>

Arguments:
  <BRANCH>  Branch of the agent to remove

Options:
      --keep-worktree  Leave the worktree + branch on disk; only detach the pane and session entry
      --force          Remove even with uncommitted changes (bypass the safety check)
  -h, --help           Print help

Examples:

git paw remove feat/done-thing
git paw remove feat/wip --force
git paw remove feat/keep --keep-worktree

Both add and remove operate on every session git-paw builds — every one uses the agent grid, which is the layout that can be re-tiled incrementally. One branch per invocation.

A live session started by an earlier git-paw with the whole-window tiled layout is the one exception: it is refused before any worktree, pane or session entry is touched, with an error naming the tiled layout. Stop the session and run git paw start to relaunch it in the grid layout, after which add and remove work normally.

git paw pause

Soft-stops the session: detaches the tmux client, stops the broker, and leaves every CLI pane running in the background. Preserves agent conversation state for instant resume via git paw start. RAM stays allocated (~300 MB per Claude pane).

Use pause for short breaks (lunch, meetings, end-of-day). For longer breaks, use git paw stop to kill the CLIs and release RAM. See Pause and Resume for the full trade-off discussion.

Usage: git-paw pause

Options:
  -h, --help  Print help

Example:

git paw pause

Idempotent: pausing an already-paused or already-stopped session is a friendly no-op.

git paw stop

Kills the tmux session and every CLI pane process, but preserves worktrees and session state on disk. CLI conversation context is lost. Run git paw start later to recover the session with fresh CLI processes.

stop never prompts for confirmation. Stopping is recoverable: worktrees and session state survive, and git paw start recovers the session with fresh processes. Therefore, a confirmation prompt would protect nothing and is not rendered.

Usage: git-paw stop

Options:
  -h, --help  Print help

Example:

git paw stop

git paw purge

Nuclear option: kills the tmux session, removes all worktrees (including any uncommitted changes in them), and deletes session state. When the broker was enabled, also removes that session’s <session_name>.broker.log. Requires confirmation unless --force is used.

Remove all worktrees and session state; keeps branches with unmerged commits

Usage: git-paw purge [OPTIONS]

Options:
      --force      Skip the confirmation prompt; never deletes a branch with unmerged commits
      --stale      Purge only stale sessions (receipt claims active but tmux is gone); live sessions untouched
      --all-repos  With --stale, sweep every repository's stale receipts instead of just the current one
  -h, --help       Print help (see more with '--help')

Which branches purge deletes

Purge deletes a branch only when git-paw created it (so a branch that existed before the session is never touched) and its tip is an ancestor of the default branch — or of the branch it was created from, see below. Each deletion prints the SHA the branch was at:

Deleted branch feat/done-thing (was 1a2b3c4)

A branch carrying commits that are in neither — or one whose merge state cannot be determined, for instance because the default branch cannot be resolved — is kept, with or without --force. Only its worktree (including any uncommitted changes) and the session state go away. Each retained branch is reported with its tip and the two ways to finish the job by hand:

Kept branch feat/wip at 9f8e7d6 (3 commit(s) not in main)
  resume it with: git worktree add '/path/to/worktree' feat/wip
  discard it with: git branch -D feat/wip

The discard command is git branch -d when git would accept the safe delete (the branch is already contained in whatever you have checked out) and git branch -D otherwise, so the suggestion always runs.

Every comparison names refs explicitly (refs/heads/<branch>). A tag that happens to share a branch’s name therefore cannot answer the merge question on the tag’s history, which would otherwise let a merged tag clear the way for deleting an unmerged branch.

Branches created from an integration branch

git worktree add -b takes no start point, so a branch git-paw creates starts at whatever the main checkout has checked out. Launch a wave from an integration branch and every agent branch inherits that branch’s commits. git-paw records the start point on the session entry, and teardown asks whether the branch is contained in the default branch or in that recorded base — so work merged into the integration branch is recognised as finished, and a branch that really is unmerged is counted against the base rather than being reported with the whole integration backlog folded into its number.

Sessions written before this was recorded carry no base and are compared against the default branch alone, exactly as they were before.

Before the confirmation prompt, purge lists every branch it is going to keep, so you see the outcome before agreeing to it. --force skips the prompt, not the retention rule.

--stale purges only sessions whose tmux session no longer exists (a stale receipt). By default the sweep is scoped to the current repository — it never touches a stale receipt belonging to a different repository. Pass --all-repos to broaden the sweep to every repository’s stale receipts on the machine (meaningful only together with --stale; used alone it is rejected with an error). When nothing is stale in scope it exits 0 with a “No stale sessions to purge.” message. Pairing --stale with --force is a no-op (--force is redundant since a stale entry is never prompted for).

In every case the live guarantee is fail-safe: a session that is live per the liveness probe — or whose liveness cannot be positively confirmed (e.g. the tmux binary is unavailable) — is never purged by --stale, in the current repository or any other.

Examples:

git paw purge
git paw purge --force
git paw purge --stale               # current repository only
git paw purge --stale --all-repos   # every repository on the machine

git paw status

Displays the current session status, branches, CLIs, and worktree paths for the repository in the current directory. When the session was launched with the broker, also shows the broker URL and whether it is running (with its agent count and uptime), paused, not responding, or held by another repository’s git-paw broker (see Multi-repo port assignment). The broker is reported running only when this repository’s broker answers and the session’s broker host is alive; a process still answering on the port after git paw stop is reported as a leftover broker with no live session host, never as running.

Usage: git-paw status [OPTIONS]

Options:
      --json  Emit machine-readable JSON
  -h, --help  Print help

The status line shows one of:

DisplayMeaning
🟢 activeReceipt says active and the tmux session is alive
🔵 pausedReceipt says paused and the tmux session is alive
🟡 stoppedReceipt says stopped, a paused session whose tmux server died, or a session whose tmux session is up but whose agent panes have all exited
🔴 staleReceipt claims active but the tmux session is gone (a crash or a release-boundary carry-over)

When every agent pane has exited — the CLIs died and the panes fell back to a bare shell — the session is 🟡 stopped, not stale: the tmux session itself is still running. Status says so with a hint under the status line (agent panes exited — run 'git paw start' to recover them), reports Tmux: running, and sets "tmux_running": true in JSON. git paw start recovers such a session; git paw purge --stale deliberately leaves it alone.

🔴 stale (and the JSON "stale" value) means only that the tmux session is gone. When the status is 🔴 stale, run git paw start to self-heal (the stale receipt is invalidated automatically before launching) or git paw purge --stale to clear it. The --json output is an object with session, status, tmux_running, and worktrees fields, plus wave when the session’s own broker is reachable (just { "session": null } when the repository has no session); its status field carries the matching lowercase value (active, paused, stopped, or stale).

When the session’s broker reports the wave as stalled, status prints one extra line under the Broker: line naming how long nothing has progressed:

  ↳ wave stalled — no progress for 3h12m

Nothing extra is printed when the wave is not stalled. “Progress” is a wave phase advancing or a branch tip moving, and nothing else — see Wave staleness for the full rule and [broker] wave_stall_minutes for the threshold.

--json is not conditional: whenever a broker is reachable, its wave object (last_progress_unix_secs, idle_seconds, stall_threshold_seconds, stalled) is always included, stalled or not.

Each entry in --json’s worktrees array carries:

FieldTypeMeaning
branchstringThe agent’s branch name.
clistringThe CLI id assigned to this worktree.
worktree_pathstringAbsolute path to the worktree.
cli_session_idstring or nullThe session identifier git-paw assigned this worker’s CLI at launch, or null when the CLI has no assignment template. Resume it by hand with the CLI’s own resume flag (e.g. claude --resume <id>), or let git paw start do it automatically on recovery.

The human-readable table (no --json) never shows cli_session_id — it is operator/tooling data surfaced only in JSON.

Example:

git paw status
git paw status --json

git paw attach

Reattaches the current terminal to the tmux session running for the repository in the current directory, resolving the session the same way git paw status does (by repository path). Equivalent to tmux attach -t paw-<project>, without needing to know or type the exact session name.

Usage: git-paw attach

Options:
  -h, --help  Print help

Exits with an actionable error when no session is running for this repo — run git paw start to launch one.

Example:

git paw attach

Note the distinction from session revival: attach reattaches to an already-running session; git paw start is what recovers a stopped/crashed session or launches a new one.

git paw list-clis

Shows all AI CLIs found on PATH (auto-detected) and any custom CLIs registered in your config.

Usage: git-paw list-clis

Options:
  -h, --help  Print help

Example:

git paw list-clis

git paw add-cli

Adds a custom CLI to your global config (~/.config/git-paw/config.toml on Linux, ~/Library/Application Support/git-paw/config.toml on macOS — see Config File Locations). The command can be an absolute path or a binary name on PATH; a binary name is resolved to its absolute path when it is registered.

Usage: git-paw add-cli [OPTIONS] <NAME> <COMMAND>

Arguments:
  <NAME>     Name to register the CLI as
  <COMMAND>  Command or path to the CLI binary

Options:
      --display-name <DISPLAY_NAME>  Display name shown in prompts
  -h, --help                         Print help

Examples:

git paw add-cli my-agent /usr/local/bin/my-agent
git paw add-cli my-agent my-agent --display-name "My Agent"

git paw remove-cli

Removes a custom CLI from your global config. Only custom CLIs can be removed — auto-detected CLIs cannot.

Usage: git-paw remove-cli <NAME>

Arguments:
  <NAME>  Name of the custom CLI to remove

Options:
  -h, --help  Print help

Example:

git paw remove-cli my-agent

git paw setup-cli

Writes git-paw’s setup items into an AI CLI’s own settings, so the CLI stops prompting for git-paw’s helper-script calls. Running this command is the consent — nothing else in git-paw ever writes those settings. See Per-CLI Setup for the grant table, the protection rules and the residual risks.

Usage: git-paw setup-cli [OPTIONS] <--allowlist|--trust|--hooks|--undo> <CLI>

Arguments:
  <CLI>  CLI identity to set up (a detected CLI or a [clis.<name>] key)

Options:
      --allowlist  Pre-approve git-paw's own helper-script calls, with the rules
                   that stop an agent rewriting what they run
      --trust      Pre-accept the repository as a trusted folder (not supported
                   by any CLI yet)
      --hooks      Install git-paw's lifecycle hooks (not supported by any CLI yet)
      --dry-run    Print the plan and exit without writing anything, even with --yes
      --yes        Apply the printed plan without asking for confirmation
      --undo       Remove the entries git-paw recorded for this CLI, limited to the
                   named items when item flags are given
  -h, --help       Print help

At least one of --allowlist, --trust, --hooks or --undo is required. Run it from the repository’s main checkout; it refuses to run from a linked worktree.

Exit codes:

CodeMeaning
0The plan was applied, there was nothing to do, or --dry-run printed it.
1Refused: an unknown CLI, a linked worktree, an unsafe target (tracked, symlinked, malformed JSON, a wrong-typed key), or a target that changed between planning and writing.
2Nothing attempted: no item flag, a requested item that is not supported, an item whose facts are unverified, a declined confirmation, or no terminal and no --yes.

Examples:

# Print the plan and write nothing
git paw setup-cli claude --allowlist --dry-run

# Apply it, confirming on a terminal
git paw setup-cli claude --allowlist

# Apply it without a prompt
git paw setup-cli claude --allowlist --yes

# Remove exactly what git-paw added
git paw setup-cli claude --undo --yes

git paw replay

Replay captured session logs. Requires logging to be enabled.

Usage: git-paw replay [OPTIONS] [BRANCH]

Arguments:
  [BRANCH]  Branch to replay

Options:
      --list               List available log sessions and branches
      --color              Display with colors via less -R
      --session <SESSION>  Session to replay from (defaults to most recent)
  -h, --help               Print help

The branch argument matches either the original branch name or its log filename (/ becomes --), so feat/add-auth and feat--add-auth select the same log. An unmatched name errors with the list of available branches.

Examples:

# List all logged sessions and branches
git paw replay --list

# Replay a branch (stripped of ANSI codes)
git paw replay feat/add-auth

# Replay with colors
git paw replay feat/add-auth --color

# Replay from a specific session
git paw replay feat/add-auth --session paw-my-project

git paw approvals

Report the permission prompts the --unattended drive loop escalated for a human decision during a session, sorted by how often each recurred. Attended sessions run no drive loop and record nothing, though this command still reads any log an earlier unattended run left behind. See Manual approvals for the full workflow.

Usage: git-paw approvals [OPTIONS]

Options:
      --session <SESSION>  Session to read from (defaults to the active session)
      --limit <LIMIT>      Show at most N patterns (top N by count)
      --json               Emit machine-readable JSON
  -h, --help               Print help

Each row carries a SUGGEST hint naming which of the agent CLI’s own allowlists could absorb the pattern — project allowlist for project-specific patterns (a ./-rooted script, or the project/branch name), user allowlist otherwise. The hint is a starting point; the command never edits any settings file for you.

Examples:

# Active session, text table
git paw approvals

# Machine-readable JSON
git paw approvals --json

# A specific session
git paw approvals --session paw-my-project

# Top 5 patterns by count
git paw approvals --limit 5

The --json output is a { "session", "approvals": [...] } object where each entry carries pattern, count, suggested_target, first_seen, and last_seen. An empty or missing log yields { "session": "...", "approvals": [] }.

git paw mcp

Starts a Model Context Protocol (MCP) server on stdin/stdout so any MCP-aware client (Claude Desktop, Cursor, ChatGPT Desktop, Windsurf, VS Code MCP) can query this repository’s read-only state: agent coordination intents/conflicts, governance docs, specs and tasks, session status and learnings, agent skills, git context, source browsing (list_files, read_file, search_code over the local working tree), and the repository’s own README and documentation.

The write surface is bounded: four publish tools for an agent’s boot events, publishing as the calling session only; and, for a server started in the repository’s main working tree only, the session lifecycle tools (start_session, pause_session, stop_session, purge_session) and the verdict tools (publish_verified, publish_feedback). A server in an agent’s worktree neither lists nor runs those. See the MCP Server guide.

The server is client-spawned and one-shot: the MCP client owns the process lifecycle and the server exits when stdin is closed. It runs standalone — no tmux session, broker, or supervisor is required. When a data source is unavailable (no broker, no session, no governance config) tools return well-formed empty/null results rather than errors.

Usage: git-paw mcp [OPTIONS]

Options:
      --repo <PATH>      Repository to serve (overrides current-directory discovery; required for Claude Desktop)
      --log-file <PATH>  Also write tracing output to this file (stderr is always used)
  -h, --help             Print help

Repository resolution: --repo wins; otherwise the nearest ancestor of the current directory containing .git is used (worktrees resolve to their own root). Clients that spawn servers from a fixed directory — notably Claude Desktop, which launches from its app-support directory — must pass --repo with an absolute path.

--log-file tees tracing diagnostics to a file in addition to stderr; stdout always stays reserved for the JSON-RPC stream.

Claude Desktop config (claude_desktop_config.json):

{
  "mcpServers": {
    "git-paw": {
      "command": "git",
      "args": ["paw", "mcp", "--repo", "/absolute/path/to/your/repo"]
    }
  }
}

Examples:

git paw mcp
git paw mcp --repo /path/to/repo
git paw mcp --repo /path/to/repo --log-file /tmp/git-paw-mcp.log

git paw doctor

Runs read-only preflight checks over the environment, configuration, and repository state and prints a grouped report answering “why won’t it launch?”.

Usage: git-paw doctor [OPTIONS]

Options:
      --json  Emit machine-readable JSON
      --live  Also run the live session-lifecycle smoke check (slower; needs tmux)
  -h, --help  Print help

Checks are grouped under Environment, CLIs, Config, Spec system, Bundled scripts, Broker, Supervisor, and Hygiene. Each reports ✓ (pass), ⚠ (warning), or ✗ (failure), and every non-✓ check prints an actionable remedy on its own ↳ line.

Doctor diagnoses but never repairs — no file, config, session, or other persistent state is created, modified, or deleted. There is deliberately no --fix flag.

Exit behaviour:

  • Exits non-zero when any check is ✗.
  • Exits 0 when the worst status is ⚠ or better — a warning alone never blocks, so doctor works as a pre-launch or CI gate that fails only on true blockers.
  • --json shares the identical exit-code contract and suppresses the human rendering; stdout is a single JSON document with a top-level status and a checks array whose entries carry group, name, status, detail, and remedy.

--live appends a Live smoke group carrying the verdict of the full session lifecycle (start → add → remove → stop) run against an isolated throwaway repository and a dummy CLI — the same harness git paw selftest drives. It is the only arm that writes anything, and only inside its own .git-paw/tmp/ sandbox, which it removes afterwards.

Examples:

git paw doctor
git paw doctor --json
git paw doctor --live

See the Doctor chapter for the full check catalogue.

git paw completions

Generates a shell-completion script from the clap command definition and prints it to stdout, for installation through the shell’s standard completion mechanism.

Usage: git-paw completions <SHELL>

Arguments:
  <SHELL>  Shell to generate completions for [possible values: bash, elvish, fish, powershell, zsh]

Options:
  -h, --help  Print help

An unsupported shell argument is rejected by clap with an actionable error naming the supported values.

Examples:

git paw completions bash > /etc/bash_completion.d/git-paw
git paw completions zsh > "${fpath[1]}/_git-paw"
git paw completions fish > ~/.config/fish/completions/git-paw.fish

See Installation for shell-specific setup.

git paw selftest

Internal diagnostic (hidden from the command list). selftest is the live arm of git paw doctor — reachable directly for dogfooding and CI, or through git paw doctor --live, which folds its verdict into the grouped report.

Runs an isolated, end-to-end session-lifecycle smoke check — the shipped form of the dogfood isolation recipe — so the orchestration plumbing can be verified anywhere, including CI, without a real LLM backend or an interactive terminal.

Usage: git-paw selftest

Options:
  -h, --help  Print help

The harness drives start → add → remove → stop against a throwaway repository and a dummy CLI (cat), observing that the agent roster grows on add and shrinks on remove. It takes no arguments.

Every external resource is isolated so the run never disturbs live work:

  • a private tmux socket (a per-run TMUX_TMPDIR, with TMUX/TMUX_PANE stripped from every child) — your default tmux socket is untouched;
  • an OS-assigned ephemeral broker port (bind 127.0.0.1:0), so concurrent runs never collide on the broker port;
  • an isolated HOME, so your real sessions directory is never written;
  • a throwaway git repository under .git-paw/tmp/, swept clean before the run and removed on both the success and failure paths.

Exit behaviour:

  • Exits 0 and prints selftest passed when the full lifecycle completes.
  • Skips with selftest skipped: tmux not available and exits 0 when tmux is not on PATH (CI runners have tmux, so a skip never silently passes there).
  • Exits non-zero and names the failing step (e.g. selftest failed at step 'add': …) only on an actual lifecycle failure.

Example:

git paw selftest

See the Selftest chapter for the full isolation recipe and how the harness closes the deferred live-verification of add/remove roster transitions.

Exit Codes

CodeMeaning
0Success
1Error (git, tmux, config, or other failure)
2User cancelled (Ctrl+C or empty selection)

Internal subcommands

git-paw also has hidden subcommands that it runs itself; they are not listed in git paw --help and are not meant to be invoked by hand:

  • __broker --session <name> --log-path <path> — hosts a session’s broker in its detached broker tmux window. start, recovery and resume launch it; pause closes its window and stop ends it with the session.
  • __dashboard — renders the dashboard in pane 0 as a thin client of that broker.
  • __prompt-profile — prints one resolved prompt-shape profile field for the bundled sweep.sh helper.

Architecture

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

It describes the code as it is today. For the structure the code is moving toward — and the rules new code follows — see Target Architecture.

Module Diagram

┌───────────────────────────────────────────────────────────────────────┐
│        main.rs  (entry layer: parse, resolve, prompt, attach)         │
│       commands/  (library use cases: context + options → outcome)     │
├──────────┬──────────────┬──────────────┬─────────────┬────────────────┤
│  cli.rs  │ interactive/ │   config/    │   error.rs  │    dirs.rs     │
│  (clap)  │  (prompts,   │    (TOML)    │  (PawError) │     (XDG)      │
│          │   pickers)   │              │             │                │
├──────────┴──────────────┴──────────────┴─────────────┴────────────────┤
│                                                                       │
│  detect.rs    git.rs       session.rs   logging.rs   agents.rs        │
│  (PATH scan)  (worktrees)  (JSON state) (pane logs)  (AGENTS.md)      │
│                                                                       │
│  surface.rs                                                           │
│  (SessionLayout + SurfaceRole: the reserved-index record)             │
│                                                                       │
│  skills.rs    init.rs      replay.rs    selftest.rs  doctor.rs        │
│  (skill       (project     (log         (smoke       (preflight       │
│   templates)   bootstrap)   playback)    check)       diagnostics)    │
│                                                                       │
│  domain.rs    lock.rs      random.rs    command_runner.rs             │
│  (newtypes)   (add/remove  (UUID v4)    (process-execution seam)      │
│                lock)                                                  │
│                                                                       │
│  static_regex.rs                                                      │
│  (one audited compiler for build-time regex patterns)                 │
│                                                                       │
│  worktree_provision.rs       dashboard.rs + dashboard/                │
│  (env copies, ports, hooks)  (TUI: model, view, log panel)            │
├───────────────────────────────────────────────────────────────────────┤
│  broker/               supervisor/               specs/               │
│  ├── mod.rs            ├── mod.rs                ├── mod.rs           │
│  ├── server.rs         ├── approval_gate.rs      ├── openspec.rs      │
│  ├── messages.rs       ├── branch_refresh.rs     ├── markdown.rs      │
│  ├── delivery.rs       ├── claim.rs              ├── speckit.rs       │
│  ├── conflict.rs       ├── drive.rs              ├── superpowers.rs   │
│  ├── learnings.rs      ├── keys.rs               └── resolve.rs       │
│  ├── watcher.rs        ├── loop_binding.rs                            │
│  └── publish.rs        ├── manual_approvals.rs   opsx/                │
│                        └── permission_prompt.rs  ├── mod.rs           │
│                                                  └── role_guard.rs    │
│                                                                       │
│                                                  coordination/        │
│                                                  ├── mod.rs           │
│                                                  ├── inventory.rs     │
│                                                  └── tell.rs          │
├───────────────────────────────────────────────────────────────────────┤
│  terminal/             tmux/                 mcp/                     │
│  ├── mod.rs  (port)    ├── mod.rs            ├── mod.rs               │
│  ├── tmux.rs (adapter) ├── command.rs        ├── server.rs            │
│  ├── fake.rs           ├── layout.rs         ├── logging.rs           │
│  └── fixtures/         ├── readiness.rs      ├── query/               │
│      (argv goldens)    └── session.rs        └── tools/               │
└───────────────────────────────────────────────────────────────────────┘

Module Responsibilities

ModuleFilePurpose
Entry pointsrc/main.rs, src/lib.rsmain.rs is the entry layer only: it parses arguments, resolves the inputs (current directory, config, and the interactive prompts — the “Start in supervisor mode?” question, the purge confirmation, the setup-cli confirmation, and the branch/spec pickers handed to the commands as an injected interactive::Prompter), dispatches into git_paw::commands, renders the outcome, and performs the terminal steps a command’s LaunchOutcome asks for: attach (or print the detached hint), reattach-or-refuse an already-running session, or take the drive-loop binding and run the unattended loop. The only other functions it holds are allowed by name: cmd_prompt_profile (hidden __prompt-profile), cmd_setup_cli / confirm_setup (plan print, terminal check, confirmation) and cmd_dashboard (the thin __dashboard client). lib.rs is the library target the binary, the MCP server and the integration tests share — internal, not a stable API.
Commandssrc/commands/The command use cases, in the library as git_paw::commands (internal API, not covered by semver). Each family takes a CommandContext (the repository root and its config — handed in loaded, or loaded on first use so error ordering is unchanged), an options struct and an injected Output sink, and returns a typed outcome; it never reads the current directory or stdin, prompts, prints, attaches, or runs the drive loop. output.rs (the Output port — line / warn — and CaptureOutput, which collects lines for in-process callers; the terminal adapter that writes stdout/stderr lives in the entry layer, main.rs), start.rs (fresh launch, restart-from-pause, spec-driven launch), supervisor.rs (supervisor, plus the drive-loop binding and run wiring the entry layer drives), recover.rs, add.rs, remove.rs, pause.rs, stop.rs, status.rs, attach.rs, approvals.rs, replay.rs, clis.rs (list-clis / add-cli / remove-cli), setup_cli.rs (setup-cli: prepare resolves the CLI identity and plans an apply or an undo, writing nothing; only apply_setup / apply_undo write, and main.rs calls them after printing the plan and getting the confirmation or --yes), purge.rs (purge, stale-receipt purge and invalidation, and the shared per-worktree teardown detach_worktree with its branch-retirement rules), launch.rs (attach_agent / AttachContext, prompt composition, LaunchOutcome, the per-repo discovery file), entry_points.rs (submitting a spec format’s workflow entry point once a pane is idle), spec_mode.rs (SpecMode and apply_spec_mode), plus shared helpers.rs (including start_broker_host, which spawns the host’s service surface and waits for /status). Two modules are entry-layer or host code and exempt by name: completions.rs (writes the generated completion script to stdout) and broker_host.rs (the hidden __broker host: builds the broker state, starts the broker, and blocks until SIGHUP / orphaned / terminal hang-up).
CLIsrc/cli.rsArgument parsing with clap v4 derive macros. Defines all subcommands, flags (--from-all-specs, --specs, --specs-format, --supervisor, --no-supervisor, --force, …), and help text.
Detectionsrc/detect.rsScans PATH for known AI CLI binaries (KNOWN_CLIS). 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.
Terminalsrc/terminal/The terminal port. mod.rs defines TerminalBackend — every orchestration-path terminal operation (open/close a session from a SessionPlan, spawn/kill/label a surface, list surfaces with their owning worktree, send text and named keys as separate operations, read a screen tail and a surface’s foreground process, relaunch a surface’s CLI or answer its first-run dialog, stream output to a log, grid layout, attach/detach, and run / stop / query a detached service outside the agent grid — the broker host) in git-paw’s terms, keyed by an opaque SurfaceId. tmux.rs is the tmux adapter (TmuxBackend) over the src/tmux/ builders and the CommandRunner seam; it alone converts a layout position to a tmux target, implements a service as a detached named window (new-window -d -n <name>), and fixtures/ pins its argv to the pre-port behaviour. fake.rs (FakeBackend) records operations and answers scripted screens, owners and processes for logic tests.
Tmuxsrc/tmux/Tmux orchestration. command.rs holds the TmuxSessionBuilder plus the supervisor-session, add-agent, and remove-retile command builders (pure argv assembly, pane titles, the agent grid every session uses); layout.rs the equal-width agent-row rebalance; readiness.rs the launch-readiness gate; session.rs runtime operations on live sessions (liveness probes, session-name resolution, attach/detach, pane and session teardown, pane-to-worktree reconciliation).
Sessionsrc/session.rsPersists session state to JSON files under ~/.local/share/git-paw/sessions/. Atomic writes, crash recovery. Also writes the per-repo discovery file (see below).
Surfacesrc/surface.rsThe session’s pane-layout record and the role each surface plays. SessionLayout — derived from a saved session or from a launch plan, never persisted — owns every reserved-index fact: which index holds the dashboard, where coding agents start, and what each launch records. No surface runs the supervisor: the orchestrator drives the session from outside it. SurfaceRole (Dashboard / Coding(agent id) / Unknown) is produced by the single resolver SessionLayout::resolve, which matches a surface’s owning worktree path through the core owner rule resolve_agent (exact canonical path) before consulting the dashboard’s reserved index; a pre-relocation session’s old supervisor pane resolves as Unknown. Decision code matches on the role instead of comparing a pane index to a literal or an agent id to "supervisor"; the supervisor’s broker wire identity is defined here once. The supervisor-window grid geometry (SupervisorLayout, supervisor_layout, layout_for, SUPERVISOR_PANE_OFFSET) lives here too so src/tmux/ depends on core rather than on src/supervisor/.
Configsrc/config/Parses TOML from global (~/.config/git-paw/config.toml) and per-repo (.git-paw/config.toml). Merges with repo-wins semantics. mod.rs holds PawConfig and loading; one file per section: broker.rs, cli.rs (custom CLIs and presets), dashboard.rs, layout.rs, prompt_profile.rs (per-CLI prompt-shape profiles), specs.rs, supervisor.rs.
Interactivesrc/interactive/Terminal prompts, split by altitude behind the Prompter trait seam: resolver.rs (terminal-free selection logic, PickerState, spec grouping), prompter.rs (the dialoguer TerminalPrompter), picker.rs (the ratatui/crossterm fuzzy multi-select and its terminal lifecycle). The branch and spec multi-selects are fuzzy-filter pickers (type to filter, space to toggle); the mode and CLI single-selects use dialoguer. Skips prompts when flags are provided.
Errorsrc/error.rsPawError enum with thiserror. Actionable error messages and distinct exit codes.
Dirssrc/dirs.rsIn-tree platform XDG path helper. Replaces the upstream dirs crate (removed in v0.5.0 for license reasons); see AGENTS.md § Dependencies.
Agentssrc/agents.rsGenerates the gitignored .git-paw/AGENTS.local.md sidecar (the combined view) per worktree; manages the <!-- git-paw:start … end --> marker region; leaves the tracked AGENTS.md committable.
Skillssrc/skills.rsResolves agent skills following the agentskills.io specification — .agents/skills/<name>/SKILL.md first, then a user override in the config directory, then the default embedded in the binary — and renders their placeholders. Injects coordination + supervisor instructions into the worktree sidecar.
Initsrc/init.rsgit paw init bootstrap. Creates .git-paw/ (with logs/, tmp/, scripts/), installs the bundled helper scripts into .git-paw/scripts/, writes the default config, and adds gitignore entries. Prompts for the spec system and records [specs] (no filesystem auto-detection).
Replaysrc/replay.rsgit paw replay. Reads pane logs from .git-paw/logs/ and either strips ANSI or pipes through less -R.
Per-CLI setupsrc/cli_setup/git paw setup-cli, the only thing in git-paw that writes an agent CLI’s own settings. facts.rs is the compiled per-CLI fact table and the fixed grant table; plan.rs and undo.rs are the pure planners over file snapshots; render.rs prints the plan; record.rs is the setup record’s shape; shell.rs is the imperative shell (git queries through CommandRunner, snapshot collection, atomic writes); doctor.rs holds the two read-only doctor checks.
Doctorsrc/doctor.rsgit paw doctor. Read-only preflight diagnostics: a probe layer gathers environment/CLI/config/spec/script/broker/supervisor/hygiene facts, and pure check functions map them to grouped ✓/⚠/✗ results with remedies. Renders human or --json; exits non-zero only when a check fails (warnings exit 0). --live folds in the selftest verdict.
Selftestsrc/selftest.rsgit paw selftest. Isolated end-to-end lifecycle smoke check (start → add → remove → stop) against a throwaway repo and a dummy CLI (cat) — private tmux socket, ephemeral broker port, isolated HOME, no LLM backend. The shipped form of the dogfood isolation recipe.
Loggingsrc/logging.rsPer-pane log capture via tmux pipe-pane. Files at .git-paw/logs/<session>/<branch>.log.
Worktree provisioningsrc/worktree_provision.rsPer-worktree runtime isolation driven by [worktree] config: copies declared env files into a new worktree, allocates its port block (base + slot * stride) into a managed .env.local block, and runs the consumer’s on_create / on_remove hooks.
Process seamsrc/command_runner.rsThe CommandRunner trait every git / tmux invocation goes through: production wires RealCommandRunner, tests inject a fake that records argv and returns scripted output.
Domainsrc/domain.rsNewtypes for injection-prone constructed strings — SessionName, BranchSlug, WorktreePath — plus shell_quote for paths interpolated into shell commands.
Locksrc/lock.rsAdvisory <repo>/.git-paw/.add-remove.lock taken by git paw add and git paw remove so two concurrent pane splices cannot interleave.
Randomsrc/random.rsVersion-4 UUIDs built from OS randomness (no uuid dependency), used for CLI session ids.
Static regexsrc/static_regex.rsThe single audited compiler for regex patterns fixed at build time. static_regex(pattern: &'static str) carries the only #[allow(clippy::expect_used)] in non-test code; every pattern routed through it is compiled by a unit test, so an invalid static pattern fails cargo test instead of panicking at run time. Patterns that arrive at run time are compiled by their caller with the error propagated.
Dashboardsrc/dashboard.rs, src/dashboard/The git paw __dashboard TUI (status table + Broker log panel), a thin HTTP client of the broker. Detail below.
Brokersrc/broker/HTTP coordination server (axum) with watcher + conflict detector + learnings subsystems. Detail below.
Supervisorsrc/supervisor/Supervisor-mode subsystems (unattended drive loop and its one-loop-per-session binding, permission-prompt detection, the live-prompt gate and per-pane claim, keystroke dispatch, manual-decision log, branch refresh). Detail below.
Specssrc/specs/Spec scanning. Four backends (openspec, markdown, speckit, superpowers); mod.rs holds the dispatch entry point and resolve.rs resolves --specs names.
Task promptssrc/task_prompts.rsThe bundled per-backend task-prompt templates and the one exhaustive SpecBackendKind selector over them. Each format names a prose template and, optionally, a workflow entry point template — the single command line a launch submits as its own composer message once the agent’s pane goes idle. The launch’s build_task_prompt and the doctor’s spec-workflow probe both read this selector, so no workflow command is written in Rust and the two cannot drift.
opsxsrc/opsx/OpenSpec role-gating guard (role_guard.rs): run by the broker on every committed agent.artifact, it flags archive activity by a non-supervisor agent per [opsx] role_gating. Inert unless the spec engine is OpenSpec.
MCPsrc/mcp/git paw mcp. Model Context Protocol server over stdio. Read-only tools expose coordination, governance, docs, specs, session, learnings, skills, git, and source-browsing state to MCP-aware clients; the bounded write surface is four session-scoped publish tools (a worktree publishes as its own agent, the main working tree as supervisor) plus, for a root session only, the session lifecycle tools and the two verdict tools, and the spec-authoring write tools for a root session of a repository that set [mcp] allow_repo_writes. Runs standalone (no session/broker/supervisor required; broker-backed reads degrade to empty results). Detail below.
Coordinationsrc/coordination/User→agent coordination helpers (inventory + target validation) backing the /agents and /tell supervisor commands. Distinct from src/broker/ peer-to-peer (agent↔agent) coordination. Detail below.

src/broker/ modules

FilePurpose
src/broker/mod.rsPublic surface (start/stop entry points, shared state types).
src/broker/server.rsaxum HTTP server: /publish, /watch, /messages/:agent_id, /status, /log. Validates each incoming /publish body (message schema, agent_id pattern, unfilled-placeholder rejection) before handing it to delivery.
src/broker/messages.rsBrokerMessage enum + payload types + slug validation. Source of truth for the wire format used in user-facing examples.
src/broker/client.rsBrokerClient: the one HTTP client every process other than the broker host uses — bounded-timeout GET /status, GET /log?since=&wait=, POST /publish, POST /watch (with an optional spec id), and the repository-scoped stale-broker probe. The dashboard, the launcher, add, status, the drive loop and the MCP server all go through it.
src/broker/publish.rsClient-side helpers for callers outside the broker process (thin wrappers over BrokerClient): build BrokerMessage values and publish them over HTTP, register a live /watch target, fetch /log (by cursor, optionally long-polling), and fetch the wave-progress object from /status. Shared by commands::supervisor::supervisor, git paw status, and the MCP query and publish tools.
src/broker/delivery.rsDelivery layer: assigns each message its sequence number, updates the sender’s record, appends it to the message log, and routes it into inboxes (broadcast, supervisor inbox, targeted delivery). Also cursor-based polling, the dashboard snapshot, and the broker-log flush loop.
src/broker/watcher.rsGit-status polling watcher: one task per worktree runs git status --porcelain every 2 s and publishes agent.status (with modified_files) when the reported path set changes; reads git rev-parse HEAD on the same tick and marks wave progress when the branch tip moves (the first observation is a baseline); prunes a worktree that has vanished.
src/broker/conflict.rsForward / in-flight / ownership conflict detection. Auto-emits [conflict-detector]-tagged agent.feedback and escalates via agent.question.
src/broker/learnings.rsOpt-in learnings subsystem. Aggregates the deterministic signals (stuck durations, recovery cycles, conflict events, permission patterns) plus published qualitative agent.learning records, and flushes them to .git-paw/session-learnings.md.

src/dashboard.rs (top-level, not inside src/broker/) renders the dashboard pane — the live status table and the optional message-log panel — by polling the broker over HTTP (GET /status, GET /log?since=<seq>). It holds no broker state. The dashboard pane sits at pane index 0 whenever the session has one — in supervisor mode (see the layout diagram below) and in non-supervisor broker mode alike.

The dashboard is structured as Model-View-Update:

FilePurpose
src/dashboard.rsThe Model (Model — the redraw state: derived agent rows, footer status line, Broker log buffer, quit flag) and the Update (Msg + a pure update(&mut Model, Msg)), plus what MVU cannot own: terminal setup/teardown, the poll_tty FFI, the getppid() == 1 orphan-exit gate, the broker poll, and the event loop. A failed poll sets the Model’s broker-unreachable flag (rendered in the status line, last snapshot kept); a lower /status uptime_seconds than last seen is a restarted host, which rewinds the log cursor.
src/dashboard/view.rsThe View — a pure function of the Model producing the rendered frame (render, render_dashboard, draw_frame, and the status_symbol / format_age / format_agent_rows / arrange_with_supervisor_pinned / format_status_line helpers). No I/O, so frames are asserted against a TestBackend without a live pty.
src/dashboard/broker_log.rsThe Broker log panel: ring buffer, filter chips, selection/overlay state, key handling, and its own rendering.

The event loop drains crossterm key presses, the per-tick status snapshot, and new broker-log entries into Msg values, routes each through update, and renders the resulting Model via the View — rather than mutating local state inline. The View re-exports keep crate::dashboard::{render_dashboard, format_agent_rows, …} resolving from the parent module.

src/supervisor/ modules

FilePurpose
src/supervisor/mod.rsModule root declaring the submodules below. The supervisor launch itself (commands::supervisor::supervisor) lives in src/commands/supervisor.rs.
src/supervisor/approval_gate.rsThe shared live-prompt gate (live_prompt_in_tail) and the escalation dedup key.
src/supervisor/branch_refresh.rsPure precondition check for the opt-in post-merge branch refresh ([supervisor] branch_refresh): a branch is rebased only when every gate passes. The drive loop resolves the inputs.
src/supervisor/claim.rsExclusive per-pane keystroke claim. Every sender — the drive loop’s nudge and buffered-input recovery, and the bundled sweep.sh approve in its own process — atomically creates <repo>/.git-paw/tmp/approve-pane-<N>.claim before sending a keystroke, so two can never both type into one pane. Non-blocking (a held pane is skipped, not waited on), released by RAII, and stealable once untouched past a 10s TTL so a hard-killed sender cannot wedge a pane.
src/supervisor/drive.rsThe --unattended drive loop, run in-process by the binary’s entry layer once commands::supervisor::supervisor returns (or reports an existing session) and the drive-loop binding is taken: ~15 s sweeps of every coding-agent pane, non-blocking escalation of every live permission prompt as a broker review item (nothing is typed for it), buffered-input recovery, the optional correction loop and branch refresh, completion detection, and the exit summary. It sends no approval keystroke.
src/supervisor/drive/decide.rsThe drive loop’s functional core. The loop runs observe → decide → apply: the shell in drive.rs observes (surfaces and their owners through the terminal port, screen captures, /status, new broker messages by cursor, merge states from git, the clock) and passes those facts to the pure decisions here — decide_prompt, decide_message, decide_nudge with decide_reengaged and decide_recovery_step, and summarize. Each returns the next state and an ordered list of DriveEffects (record a manual decision, publish an escalation, record a learning, submit buffered input, re-engage a worker, refresh branches, record an exhausted branch, note a judgment call; there is no approval effect). The shell applies each effect in order through the existing ports and appends it, with its EffectOutcome, to an in-memory effect log: drive_loop_logged returns DriveRun { summary, effect_log } and drive_loop returns only the summary. No decision reads a port, the clock, git or the broker.
src/supervisor/keys.rsThe KeyDispatcher seam, implemented by every terminal backend (src/terminal/), used by the drive loop’s nudge and input-recovery paths.
src/supervisor/loop_binding.rsThe drive-loop binding: a refreshed lease file (.git-paw/tmp/drive-loop.binding) that keeps at most one drive loop on a session instance, so a re-attach never runs a second loop beside a live one.
src/supervisor/manual_approvals.rsRecords each prompt the drive loop escalates for a human decision to .git-paw/sessions/<session>.manual-approvals.jsonl, aggregates the log, and suggests which of the agent CLI’s own allowlists could absorb the pattern for git paw approvals.
src/supervisor/permission_prompt.rsAll permission-prompt detection over a screen read through the terminal port: the approval-marker scan, the live-prompt gate and its anchoring windows, input-box and rule-line parsing, and extraction of the prompted command slice or file-operation target path. Reports what is on screen; decides nothing about the command.

src/specs/ modules

FilePurpose
src/specs/mod.rsPublic surface (SpecBackend trait, SpecEntry) and the dispatch entry point (scan_specs / scan_specs_with_override). Picks the backend from the --specs-format CLI override or the [specs] type config — the only two sources (no filesystem auto-detection).
src/specs/resolve.rsResolves --specs NAME[,NAME...] against the scanned entries: exact id match, then Spec Kit feature-name and numeric-prefix matches; an unknown or ambiguous name fails the whole resolution.
src/specs/openspec.rsOpenSpec backend: scans <dir>/<change>/tasks.md directories, skips <dir>/archive/.
src/specs/markdown.rsMarkdown backend: scans flat .md files with YAML frontmatter; only paw_status: pending is picked up.
src/specs/speckit.rsSpec Kit backend: scans .specify/specs/<feature>/, decomposes the current phase into [P]-task worktrees plus one consolidated phase/… worktree; probes <dir>/../memory/constitution.md for the governance auto-wire.
src/specs/superpowers.rsSuperpowers backend: scans docs/superpowers/plans/*.md plan files; each plan with incomplete steps becomes one entry (one worktree), never fanned out per task.

src/mcp/ modules

The MCP subsystem follows a strict one-way dependency direction — query knows nothing about MCP, tools knows about MCP and query, and server only wires tools onto a transport — so a future HTTP transport stays additive.

FilePurpose
src/mcp/mod.rsEntry point: cmd_mcp(), RepoContext, and repository resolution (--repo wins, else nearest .git ancestor). RepoContext also records whether the server runs in the main working tree (a root session, decided once with RepoLayout::is_linked_worktree) and, for a root session only, the session’s authority credential (redacted from Debug), plus the startup-resolved allow_repo_writes consent.
src/mcp/server.rsstdio transport setup, tool-registry wiring, and process lifecycle (exits when stdin closes). The lifecycle and verdict routers are merged only for a root session; call_tool answers a worktree’s by-name call to a root-only tool with an error naming the restriction. The spec-authoring write router is merged only when repo_write_access passes, and a by-name call otherwise gets the refusal (the switch, or the worktree restriction).
src/mcp/repo_write.rsThe repository-write gate (repo_write_access, a pure decision over root session × allow_repo_writes) and write confinement: lexical checks on change/path, canonical checks that the spec directory lies inside the repository (not the root, .git or .git-paw), per-component symlink checks against the change directory, temp file + rename.
src/mcp/logging.rsMinimal in-tree logger (no tracing dependency) — diagnostics to stderr at a RUST_LOG-controlled level (default warn) and, with --log-file, tee’d to a file; stdout stays reserved for the JSON-RPC stream.
src/mcp/query/*Data-layer reads (no MCP types): conflicts.rs, docs.rs, events.rs, git.rs, governance.rs, intents.rs, learnings.rs, session.rs, source.rs, specs.rs, plus mod.rs. Built from broker HTTP state, files on disk, and git output. events.rs is the cursor read behind wait_for_events: it long-polls /log and returns the new entries, the new cursor, the broker epoch, and the wave.
src/mcp/tools/*MCP tool surfaces (one file per category): coordination.rs, docs.rs, git.rs, governance.rs, project.rs, publish.rs, session.rs, source.rs, spec_authoring.rs, plus mod.rs. Each read tool maps a query reader onto an MCP tool definition. publish.rs holds the publish tools: publish_status, publish_artifact, publish_blocked, and publish_question, built with the src/broker/publish.rs helpers, with the publishing id derived from the session (a worktree’s branch, or supervisor in the main working tree; no tool accepts one), plus the root-only publish_verified / publish_feedback, which attach the session credential read from the receipt at call time. session.rs adds the root-only lifecycle tools (start_session, pause_session, stop_session, purge_session), each an in-process call into git_paw::commands with a capturing output sink — never prompting, never attaching, never running the drive loop. spec_authoring.rs adds the gated create_spec / write_spec_artifact and the read-only validate_spec, thin adapters over the pure scaffold and structural check in src/specs/openspec.rs.

src/coordination/ modules

FilePurpose
src/coordination/mod.rsPublic surface for the user→agent coordination helpers.
src/coordination/inventory.rsAgent inventory + target-validation helpers (unknown-target rejection) shared by the supervisor routing commands.
src/coordination/tell.rsPure logic behind the /tell supervisor command — argument parsing and delivery-mode selection. The send itself (tmux send-keys or a broker publish) is driven by the supervisor skill.

Start Flow

The start command is the primary flow. main.rs resolves supervisor mode and dispatches it to one of three use cases in git_paw::commands: supervisor::supervisor when supervisor mode resolves on (--supervisor or --unattended, else [supervisor] enabled, else an interactive prompt when no [supervisor] section exists; --no-supervisor always wins), start::start_with_specs for a spec-driven launch (--from-all-specs / --specs) without a supervisor, and start::start otherwise. All three run the same existing-session check first, and all three return a LaunchOutcome instead of touching the terminal — main.rs attaches, reattaches or refuses, or runs the unattended drive loop from it. Here’s what start::start does step by step:

git paw start
     │
     ▼
┌─ Validate git repo ──────────────────────────────────────┐
│  git::validate_repo() → repo root path                   │
└──────────────────────────────────────────────────────────┘
     │
     ▼
┌─ Check for existing session ─────────────────────────────┐
│                                                          │
│  Active, tmux session gone?  ──yes──► Invalidate stale   │
│         │ no                           receipt, then     │
│         │                              fresh start       │
│  Paused + tmux alive?        ──yes──► Restart broker,    │
│         │ no                           reattach          │
│  Active + tmux alive?        ──yes──► Reattach           │
│         │ no                                             │
│  Stopped session?            ──yes──► Recover            │
│         │ no                                             │
│  No session                  ──────► Fresh start         │
└──────────────────────────────────────────────────────────┘
     │
     ▼ (fresh start)
┌─ Load config ────────────────────────────────────────────┐
│  config::load_config() → merged PawConfig                │
│  ([supervisor] enabled → hand off to supervisor())       │
└──────────────────────────────────────────────────────────┘
     │
     ▼
┌─ 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                  │
│  → .git-paw/worktrees/<slug>/   (child placement)        │
│    or ../<project>-<slug>/       (sibling placement)     │
│  worktree_provision::provision_worktree()                │
│  → env-file copies, port slot, on_create hook            │
└──────────────────────────────────────────────────────────┘
     │
     ▼
┌─ Build tmux session ─────────────────────────────────────┐
│  TmuxSessionBuilder::new(project)                        │
│    .session_name("paw-project")                          │
│    .mouse_mode(true)                                     │
│    .border_affordances(true)                             │
│    .add_pane(PaneSpec { branch, worktree,                │
│                         cli_command }) × N               │
│    .build() → TmuxSession with command sequence          │
└──────────────────────────────────────────────────────────┘
     │
     ▼
┌─ Pane layout by mode ────────────────────────────────────┐
│  supervisor   → Pane 0 = `git paw __dashboard`           │
│                 Pane 1..N = per-agent CLIs               │
│                 (supervisor() builds it with             │
│                  tmux::build_supervisor_session; the     │
│                  orchestrator runs outside the session)  │
│  broker-only  → Pane 0 = `git paw __dashboard`           │
│                 Pane 1..N = per-branch agent CLIs        │
│  no broker    → Pane 0..N = per-branch agent CLIs        │
│                                                          │
│  With the broker on, the tmux session environment        │
│  carries GIT_PAW_BROKER_URL into every pane, and the     │
│  dashboard pane renders the status table as a client     │
│  of the broker host started next.                        │
└──────────────────────────────────────────────────────────┘
     │
     ▼
┌─ Start the broker host (broker on) ──────────────────────┐
│  helpers::start_broker_host()                            │
│  → detached window `broker` (outside window 0's grid)    │
│    running `git paw __broker --session <name>            │
│                              --log-path <log>`           │
│  → wait ≤10s for GET /status (git_paw + repo_root);      │
│    a host that exits or never answers fails the launch   │
│    naming the port, before any receipt is saved          │
│  helpers::register_agents_with_host()                    │
│  → POST /watch per agent (agent id, worktree, CLI,       │
│    spec id)                                              │
└──────────────────────────────────────────────────────────┘
     │
     ▼
┌─ Save session state ─────────────────────────────────────┐
│  session::save_session() → atomic JSON write             │
│  + per-repo discovery file (.git-paw/sessions/)          │
└──────────────────────────────────────────────────────────┘
     │
     ▼
┌─ Attach (entry layer, from LaunchOutcome::Attach) ───────┐
│  attach_or_print_hint() → user enters tmux session       │
│  (prints an attach hint when stdin is not a TTY)         │
└──────────────────────────────────────────────────────────┘

Per-repo session discovery file

Alongside the global receipt under ~/.local/share/git-paw/sessions/, git paw start writes a per-repo discovery file at <repo>/.git-paw/sessions/<session>.json. This is the surface the bundled sweep.sh supervisor helper reads to find the active session and its agent roster from inside the repo (without reaching into the XDG state dir). git paw add / remove rewrite it with the updated roster; purge removes it.

Every path that creates or rebuilds the session’s panes (re)writes it, not just the first launch — the bare launch, the spec-driven launch with or without supervisor mode, the supervisor launch, recovery of a stopped session, and restart of a paused session. The last two matter because they change the layout: recovery rebuilds every pane from the saved receipt, and a resume splits a fresh dashboard pane into the live session at whatever index tmux assigns. A file left by the original launch would then record pane indices that no longer describe the session. Recovery rewrites it from the offsets of the layout it just rebuilt; the resume path reads each agent’s pane index back from the running session (list-panes matched on pane_current_path) rather than assuming the launch-time offsets.

Its shape — stable for sweep.sh and forward-compatible (consumers ignore unknown keys):

{
  "session_name": "paw-myproject",
  "agents": [
    {
      "branch_id": "feat-add-auth",
      "worktree_path": "/abs/path/to/myproject/.git-paw/worktrees/feat-add-auth",
      "cli": "claude",
      "pane_index": 1
    }
  ]
}

branch_id is the broker agent id (slugified branch); pane_index is the agent’s tmux pane within the session window. When the file is absent (e.g. a supervisor attached to a pre-existing paw-* session), sweep.sh falls back to resolving the session name from $TMUX / tmux display-message -p '#S', so the file never needs to be hand-authored.

The agents roster lists coding agents only. No orchestrator entry is written: the orchestrator runs outside the session, so no pane is its own. A file written by an earlier release may still carry one; it deserializes with #[serde(default)], so such a file loads unchanged, and sweep.sh — which reads agents and session_name by name — ignores it.

Broker Architecture

When [broker] enabled = true, the broker runs in its own process, git paw __broker, in a detached service surface of the session — on tmux, a window named broker, outside window 0’s pane grid, so pane indices are untouched. The launcher hands it the session name and the per-session log path on argv (a fresh launch saves its receipt only after the panes exist); when a saved receipt with that name exists (recovery, restart from pause) the host seeds its watch targets, the agent → spec mapping, and the broker port and bind from it. On a fresh launch the agents arrive through POST /watch instead, each with its spec id. The dashboard pane runs git paw __dashboard, a thin client that polls the host over HTTP; closing it never affects the broker.

Broker host process (git paw __broker, window `broker`):
├── tokio runtime (background threads)
│   ├── axum HTTP server on 127.0.0.1:9119 (default [broker] bind / port)
│   │   ├── POST /publish
│   │   ├── POST /watch   (register a worktree as a live watch target,
│   │   │                  optionally with the agent's spec id)
│   │   ├── GET /messages/:agent_id?since=N
│   │   ├── GET /status
│   │   └── GET /log?since=N[&wait=S]   (full message log, cursor-paged and
│   │       long-pollable; read by the dashboard, the drive loop and the MCP server)
│   ├── Worktree watchers (src/broker/watcher.rs), one task per worktree
│   │   └── `git status --porcelain` + `git rev-parse HEAD` every 2s; publishes
│   │       agent.status on change; marks wave progress on a moved branch tip;
│   │       prunes a vanished worktree
│   └── Conflict detector (src/broker/conflict.rs), supervisor mode only
│       └── Forward / in-flight / ownership shapes
├── Log flush thread (std::thread, 5s interval)
│   └── Appends new messages to <session_name>.broker.log
├── Learnings flush thread (std::thread; opt-in via [supervisor] learnings = true)
│   └── Flushes the aggregator (src/broker/learnings.rs), fed on every publish,
│       to .git-paw/session-learnings.md (default every 60s, plus once at shutdown)
└── Main thread
    └── waits for SIGHUP, reparent-to-init, or its terminal hanging up

Dashboard pane process (git paw __dashboard, pane 0):
└── ratatui dashboard (800ms idle tick; a keystroke wakes it immediately)
    └── each tick: GET /status + GET /log?since=<cursor> through BrokerClient

Verdict authority. POST /publish refuses a verdict — any agent.verified, and any agent.feedback whose from is supervisor — unless the request’s X-Paw-Authority header matches the session’s authority credential (constant-time compare; 403 {"error":"authority required"}, no state change). The launcher mints the credential, persists it in the user-data Session (0600) with the receipt, and hands it to the host through a one-shot 0600 file in the sessions directory (never argv or env); the host reads it from there, else from the saved Session, and back-fills one only for a session saved before the credential existed. BrokerState holds it as an Option — None accepts no verdict. In-process publishers (conflict detector, role guard) are not gated; root-side HTTP publishers (the drive loop, sweep.sh verdict verbs via git paw __authority) attach the header. See Security Posture.

The host renders nothing and needs no $TMUX. It exits — dropping BrokerHandle, so the final broker-log flush and the learnings flush run — on SIGHUP (tmux kills its window on pause and every window on stop), on reparent-to-init (getppid() == 1, Unix only), or when a terminal it was given hangs up. It ignores SIGINT, sends no keystrokes to any surface, and a bind or probe failure exits non-zero with a diagnostic naming the port rather than retrying.

The dashboard’s render loop is bound to the session’s lifecycle the same way: a single exit gate checked every iteration (on all paths, including the degraded branch) terminates it on reparent-to-init or when the controlling terminal is gone (a poll error or a failed terminal write — catching reparent-to-a-lingering-shell). An unreachable broker is never an exit condition: the dashboard keeps the last snapshot, shows “broker unreachable” in its status line, and reconnects on its own. See the Dashboard chapter for the user-facing description.

Broker state

The broker state is an Arc<BrokerState> defined in src/broker/mod.rs: an RwLock around the agent records, inboxes, and message log, plus a lock-free AtomicU64 sequence counter. Lock guards are never held across an .await (the clippy::await_holding_lock lint is set to deny). The state is shared between the axum server handlers, the watchers, the conflict detector, and the learnings aggregator, all inside the broker host. The server validates incoming messages (src/broker/server.rs) and hands them to src/broker/delivery.rs, which sequences, logs, and routes them; the dashboard reads the latest snapshot over GET /status each tick.

The log flush thread periodically appends new messages to the session’s broker log in the session-state directory (~/.local/share/git-paw/sessions/<session_name>.broker.log, recorded as broker_log_path in the session file — one log per session, so two repositories never write to or purge the same file), one human-readable line per message ([seq] <unix-seconds> [agent_id] <summary>). This runs on a plain std::thread to avoid contention with the tokio runtime.

Environment injection

When the broker is enabled, git-paw sets GIT_PAW_BROKER_URL=http://<bind>:<port> (default http://127.0.0.1:9119) in the tmux environment for the session when it builds it. Each agent pane inherits this variable and can use it to communicate with the broker.

Supervisor Mode Layout

When --supervisor is active (or [supervisor] enabled = true), the tmux session is laid out as the dashboard in a full-width top row plus a row-major agent grid below. No pane runs an orchestrator CLI.

┌─────────────────────────────────────────────────────┐
│  pane 0: dashboard                                  │
├──┬──┬──┬──┬──┬──────────────────────────────────────┤
│ 1│ 2│ 3│ 4│ 5│  agent grid (row 1)                  │
├──┴──┴──┴──┴──┤                                      │
│ 6│..│..│..│ N│  agent grid (row 2..M)               │
└──┴──┴──┴──┴──┴──────────────────────────────────────┘

Pane 0 always hosts the dashboard; pane indices 1 onwards host one CLI per agent. The orchestrator runs outside the tmux session — the operator’s own agent session, pointed at the repo-root sidecar .git-paw/AGENTS.local.md that the launch writes and names. It drives the run through the broker, the bundled helpers (.git-paw/scripts/sweep.sh, broker.sh) or the MCP server; the dashboard reads the same broker state for its status table.

A live session saved by an earlier release (orchestrator CLI at pane 0, dashboard at pane 1, agents from 2) keeps resolving until it ends: every consumer derives the first agent pane from the recorded dashboard_pane (n → agents from n + 1), and neither of that session’s first two panes is ever treated as a coding agent. Recovering such a session rebuilds the layout above.

One layout for every session

There is a single pane layout. Bare start, spec-driven start and supervisor-mode start — and the recovery of any of them — all build the grid above through one routine, so the geometry never depends on how the session was launched. With the broker disabled there is no dashboard: the agent rows start at pane 0 and share the full window height instead of the 50% the dashboard row leaves.

This is why git paw add and git paw remove work on every session. The grid is laid out by explicit split-window + resize-pane calls with predictable, stable pane indices, which is what lets it be re-tiled incrementally for a new agent count. Bare sessions were previously laid out with tmux’s whole-window select-layout tiled / main-horizontal layouts, which cannot be extended in place; those layouts are no longer used for coding agents anywhere.

Each session records the layout it was built with (Session.layout), so a live session started by an earlier binary — which recorded none, and for a bare session therefore was tiled — is refused by add / remove rather than re-tiled under the user. Recovery rebuilds such a session as a grid and records it.

Row-height proportions

The top row is always half the session height — the dashboard spans its full width — and the agent rows share the remaining 50% evenly, each taking 50 / agent_rows percent. This is a fixed rule rather than a per-agent-count table: adding agents shrinks the agent rows, never the top row.

Agent rowsTop rowEach agent row
150%50%
250%25%
350%16.7%
450%12.5%
550%10%

Pane order

Every agent’s pane index is its row-major position: agent i is pane first_agent + i. That holds because every split targets the session’s last pane, and tmux places a split’s new pane right after its target in the pane list: a sibling in the current row is split-window -h off the previous pane, and a new row is a full-width split-window -v -f off the previous pane, so the row spans the window below the rows above it. Splitting a new row off the previous row’s first pane instead would put the new pane mid-list and shift every later pane by one, so each launch command and prompt typed by index would land in the wrong agent’s pane. git paw add uses the same two splits, so an added agent takes the next index without renumbering any existing pane.

Equal-width agent columns

Agent panes are spliced into a row by successive tmux split-window -h, and each -h split halves the current pane — so a row populated by raw splits renders unequal widths (a 3-agent row would render 50/25/25, not equal thirds). select-layout tiled is deliberately not used for the whole window because it would scramble the predictable pane-index ordering the rest of the system relies on. Instead, after the panes for a row exist, git-paw rebalances that row to equal width: tmux::rebalance_agent_rows queries the live window width and issues tmux resize-pane -x <cols> so each pane in the row gets an equal column share (the last pane absorbs the rounding remainder), leaving the row equal-width within a one-column tolerance. The rebalance runs on every path that changes the grid — git paw start (bare, spec-driven and supervised, and the recovery of each), git paw add, and git paw remove — so an incrementally-built grid matches a start-time grid of the same agent count. It never touches the full-width dashboard row nor the per-row vertical heights. No agent row exceeds five panes (SUPERVISOR_AGENTS_PER_ROW), bounding the smallest equal-width target to ~20% of the window. src/surface.rs and the rebalance in src/tmux/layout.rs are the source of truth.

Launch-readiness gate

Before injecting an agent’s boot block (the initial prompt, paste-handling notes, and the task prose; the spec format’s workflow entry point follows later, as its own message once the pane is idle) into a pane, git-paw verifies the pane’s CLI has actually reached an interactive state rather than relying on a fixed wall-clock sleep. tmux::gate_pane_for_injection polls the pane with tmux capture-pane for a CLI-readiness marker; only once the marker appears is the boot block injected. If the readiness budget elapses while the pane is still a bare shell (the CLI never started), git-paw relaunches the CLI command into that pane and polls again, up to a small relaunch budget, before falling back to injection. The gate is conservative: an unrecognised CLI whose UI matches no known marker simply falls back to injecting after the budget, so launch behaviour is never worse than the previous fixed-sleep launch. This prevents the multi-line boot block from being typed into a bare shell, where it would be interpreted line-by-line as failing commands. The same gate also recognises a first-run trust/bypass acceptance dialog and fails the launch loudly rather than risk a blind Enter accepting it. The gate guards every pane that receives an injected prompt: bare git paw start (with the broker enabled), a spec-driven launch (--specs / --from-all-specs, with or without --supervisor), and git paw add. Session recovery and a broker-less bare launch spawn panes without ever calling the gate — harmless, since neither path injects anything into them.

Non-Supervisor Layout

When supervisor mode is OFF and the broker is on, the dashboard occupies pane 0 and the agent CLIs occupy panes 1 onwards in a single row-major grid (no top row):

┌───────────────────────────────────────────────────────┐
│  pane 0: dashboard                                    │
├──┬──┬──┬──┬──┬────────────────────────────────────────┤
│ 1│ 2│ 3│ 4│ 5│  agent grid (row 1)                   │
├──┴──┴──┴──┴──┤                                       │
│ 6│..│..│..│ N│  agent grid (row 2..M)                │
└──┴──┴──┴──┴──┴───────────────────────────────────────┘

When the broker is disabled too, every pane (0..N) is an agent CLI and there is no dashboard pane.

Worktree Lifecycle

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

Creation

Where a worktree goes is set by worktree_placement in the config (git::worktree_path_for). git paw init writes worktree_placement = "child", which nests worktrees inside the repo. For a project named my-app and branches feature/auth-flow and feat/api:

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

A child worktree is named by the branch slug: slashes become hyphens and characters outside [A-Za-z0-9._-] are dropped.

A config with no worktree_placement key resolves to "sibling", the earlier layout, which creates worktrees beside the main repo directory:

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)

The sibling naming convention is <project>-<sanitized-branch>, where every non-alphanumeric character becomes a hyphen.

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 — per worktree it runs the configured [worktree.hooks] on_remove command, then git worktree remove --force, then decides the branch’s fate. There is no prune step. The branch is deleted only when git-paw created it and git merge-base --is-ancestor refs/heads/<branch> refs/heads/<target> succeeds for the default branch or for the branch recorded as the worktree’s base; otherwise it is kept and printed with its tip SHA and a resume/discard hint. Each step is best-effort: a failure is reported as a warning: and never aborts the teardown.
  • Start prunes stale worktree registrations (git worktree prune) before creating new worktrees

Session State

Session state is persisted as JSON under ~/.local/share/git-paw/sessions/ (~/Library/Application Support/git-paw/sessions/ on macOS):

{
  "session_name": "paw-my-app",
  "repo_path": "/home/you/projects/my-app",
  "project_name": "my-app",
  "created_at": "2025-01-15T10:30:00Z",
  "status": "active",
  "worktrees": [
    {
      "branch": "feat/auth",
      "worktree_path": "/home/you/projects/my-app/.git-paw/worktrees/feat-auth",
      "cli": "claude",
      "branch_created": true
    },
    {
      "branch": "feat/api",
      "worktree_path": "/home/you/projects/my-app/.git-paw/worktrees/feat-api",
      "cli": "codex",
      "branch_created": false
    }
  ],
  "broker_port": 9119,
  "broker_bind": "127.0.0.1",
  "broker_log_path": "/home/you/.local/share/git-paw/sessions/paw-myproject.broker.log",
  "mode": "bare",
  "dashboard_pane": 0
}

status is active, paused, or stopped. broker_port, broker_bind, broker_log_path, and dashboard_pane are present only when the broker is enabled; git paw status uses the bind and port to probe the broker, and git paw purge uses broker_log_path to clean up that session’s broker log. mode (bare or supervisor) records the pane layout so recovery rebuilds the same shape. branch_created records whether git-paw created the branch (rather than reusing an existing one). A worktree entry can also carry base_branch (the branch a git-paw-created branch started from, so teardown can tell an already-merged branch from an unmerged one), pending_boot_prompt, runtime_slot, cli_session_id, and spec_id (the id of the spec the agent was launched from, absent for a bare git paw start or a git paw add without --from-spec); each is omitted when unset.

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)
activeNo (session gone)Stale — start invalidates the receipt and launches fresh
pausedYesPaused (restart the broker, reattach)
pausedNoStopped (recover)
stoppedN/AStopped (recover)
No fileN/ANo session

For an active receipt, “alive” also requires at least one agent pane running something other than a bare shell once the session is past a ~30-second launch grace period; a session whose every agent CLI has exited is treated as Stopped and recovered.

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 — render the plan (command_strings()) without side effects
  • Atomicity — validate the full plan before running anything
#![allow(unused)]
fn main() {
TmuxSessionBuilder::new("my-app")
    .session_name(resolved_name)   // e.g. "paw-my-app"
    .mouse_mode(true)
    .border_affordances(true)
    .set_environment("GIT_PAW_BROKER_URL", &broker_url)
    .add_pane(PaneSpec { branch, worktree, cli_command })
    .add_pane(PaneSpec { ... })
    .build()?
    // → TmuxSession { name, commands: Vec<TmuxCommand> }
}

The built TmuxSession can be inspected (command_strings()) or executed (execute()). Supervisor-mode sessions are assembled the same way by tmux::build_supervisor_session; git paw add splices a pane in with build_add_agent_commands and git paw remove retiles with build_remove_retile_commands — all in src/tmux/command.rs.

CLI-launch robustness

Supervisor-mode sessions and panes hot-added by git paw add are created with shell auto-update prompts suppressed (new-session/session env set DISABLE_AUTO_UPDATE=true and DISABLE_UPDATE_PROMPT=true) so an interactive framework prompt (e.g. oh-my-zsh’s Would you like to update? [Y/n]) cannot fire as the pane’s shell reads its rc and swallow the first keystroke of the CLI-launch command. As a second layer, those builders send a C-u line-clear immediately before each CLI-launch command, so any stray pending input cannot corrupt it. Every headless new-session canvas is sized (480×140) to tile a supervisor session with several agents when no client is attached; a real terminal resizes the session on attach.

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

Target Architecture

The Architecture chapter describes the code as it is today. This page describes the structure the code is moving toward, and the rules new code follows so that it moves in that direction rather than away from it.

The move is incremental. Modules are restructured when they are next changed, not in one large rewrite, and every step keeps git-paw’s behavior exactly the same.

Why

git-paw coordinates several outside systems: a terminal multiplexer, git, agent CLIs, an HTTP broker, and MCP clients. Today some of them leak into code that should not need to know about them — tmux pane numbers appear in session state and in the supervisor’s decision logic, and the drive loop assumes one particular CLI’s screen layout in places. That makes each of them hard to replace (for example, tmux by another terminal backend or a headless mode), hard to test without faking processes, and risky to freeze as a stable contract.

The pattern

Two well-known patterns, used together:

  • Ports and adapters (also called hexagonal architecture) for the overall structure. Everything git-paw talks to sits behind a port — a Rust trait named in git-paw’s own terms — and is reached only through an adapter that implements that trait.
  • Functional core, imperative shell (in Rust also called sans-IO) for the logic inside. Code that decides — whether to approve a prompt, whether to nudge an agent, whether a branch was merged — is a pure function: data in, data out. Separate code observes the world and carries out the decisions.

Two smaller patterns fill specific slots: Strategy for behavior that differs per agent CLI or per spec format, and data-transfer objects (DTOs) wherever data is written to a file or sent over the network.

Layers

 entry      who calls git-paw      CLI (clap), MCP tools, dashboard, broker HTTP server
   │          thin: parse input → call a use case → show the result
   ▼
 app        use cases              start, stop, purge, add, remove, … and the drive-loop shell
   │          orchestrates: observes and acts through ports, asks core what to do
   ▼
 core       the domain             agents, waves, surfaces, phases, escalations + pure decisions
              no I/O · no tmux · no HTTP or MCP libraries · no file or wire formats

 ports      traits the app needs   terminal backend, version control, broker client, clock
 adapters   implementations        tmux, the git command line, the broker over HTTP, test fakes
 wire       serialized formats     session file, config file, broker messages, MCP payloads

These are layers, not directories that exist today.

The dependency rule

Dependencies point inward: entry → app → core. Adapters depend on the ports they implement, and nothing depends on an adapter except the startup code that wires the program together. core depends on nothing else in git-paw. If code in core would need tmux, an HTTP framework, the MCP library, or a file format, it belongs in another layer.

Ports

PortWhat it means in git-paw’s termsAdapters
Terminal backendCreate and remove an agent’s surface; send it text or a key; read its recent output; list the surfaces and the worktree each one is in; stream its output to a log; check it is alive; let a person attachtmux today; a native terminal backend or a headless mode could be added later
Version controlCreate and remove worktrees, read branches and refs, ask whether a branch has been mergedthe git command line
Broker clientPublish a message; read agent status and the message logHTTP to the broker
Process runnerRun an external commandCommandRunner (already exists)
ClockThe current timethe system clock; a fixed clock in tests

Ports use git-paw’s vocabulary, never a tool’s. A terminal method named after tmux’s pane_current_path only fits tmux. The question git-paw actually needs answered is “which agent owns this surface?” — tmux answers it with pane_current_path, a native terminal backend knows it because it created the surface, and a headless mode never needs to ask. Surfaces are identified by an opaque id rather than a tmux pane number.

Adapters are compiled into the binary and chosen at startup, not loaded as runtime plugins. Each one runs its tool the way git-paw already does, so no new dependencies are needed.

Example: escalating a permission prompt

git-paw answers no permission prompt. When a coding agent’s screen shows one, the drive loop’s job is to put it in front of a human without stalling the rest of the wave. The decision lives in src/supervisor/drive/decide.rs; the shell is src/supervisor/drive.rs.

#![allow(unused)]
fn main() {
// core: pure — plain data in, the next state and effects out
pub enum DriveEffect { RecordManualDecision { .. }, PublishEscalation(Escalation), RecordLearning { .. },
                       SubmitBufferedInput { .. }, /* …no approval variant */ }

pub fn decide_prompt(state: &DedupWindow, obs: &SurfaceObservation, now: Instant)
    -> (DedupWindow, Vec<DriveEffect>) { … }
// CLI-unavailable banner      → one deduped PublishEscalation (cli-unavailable)
// not live, text buffered     → SubmitBufferedInput
// live prompt, new dedup key  → RecordManualDecision, PublishEscalation (permission-prompt), RecordLearning
// anything else, or a repeat inside the dedup window → nothing

// app: the shell — the only part that does I/O, and only through ports
let capture = terminal.capture(surface);
let (next, effects) = decide::decide_prompt(&dedup, &observation(&capture, profile_for(agent)), clock.now());
dedup = next;
for effect in effects {
    shell.apply(deps, effect); // through the ports, in order; logged with its outcome
}
}

The decision is tested by passing in a captured screen and a time and checking the effects — no fake terminal and no fake clock. No input makes it return a keystroke for a live prompt. Supporting a different terminal means writing a new adapter; core and app do not change.

Behavior that differs per CLI or per spec format

Agent CLIs differ in how their screens look and which flags they accept; spec formats differ in where specs live and how tasks are tracked. Each is a Strategy: one implementation per CLI or format behind a single trait, with a generic implementation as the fallback for anything unrecognized. The profile for an agent’s CLI is worked out once for that agent and passed to the decisions that need it, instead of each decision assuming one particular CLI.

Use a Rust enum when the set is small and fixed and callers really do need to handle each case differently — the compiler then checks that every case is handled. Use a trait when the set can grow and callers should not need to know which implementation they hold; terminal backends are the clearest example.

Serialized formats

The session file, the config file, broker messages and MCP responses are read by other processes and by the bundled helper scripts, so they are contracts that must stay stable. The target keeps each one as a separate wire type (a DTO) with a conversion to and from the core types, so the internal model can change without changing the format. Each format carries a version field and stays backward-compatible: new fields are optional, and older files keep loading unchanged.

This split applies only where a format is a contract, not to every struct in the program.

Rules for new code

  • Put each decision in a pure function that takes data and returns data. Keep I/O in the shell.
  • Reach tmux, git, the broker and the filesystem only through their port. Do not add direct tmux calls outside the terminal adapter.
  • Name ports and core types in git-paw’s terms (agent, surface, wave), never a tool’s.
  • Identify an agent’s terminal by its surface, not by a pane number. Do not add logic that depends on “pane 0”.
  • Do not hardcode one agent CLI’s behavior where a per-CLI profile exists.
  • Keep serialized formats additive and backward-compatible.
  • Restructure in small, safe steps: first add the port or type and route the existing code through it with no change in behavior, then let the compiler find every call site, and only then add a second implementation or remove the old code.

Where the code is today

AreaStatus
Live-prompt gate (supervisor::permission_prompt)Already a pure decision
DashboardAlready Model-View-Update — a functional core for a terminal UI
Process execution (CommandRunner)Already a port with a test fake; not yet used everywhere
MCP serverAlready layered one way (query → tools → server) and talks to the broker over HTTP
TerminalA port: terminal::TerminalBackend, in git-paw’s terms and keyed by an opaque SurfaceId, with tmux (terminal::tmux::TmuxBackend) as its first adapter and a recording fake (terminal::fake::FakeBackend) for logic tests. The drive loop, the launch paths’ session opening, logging and layout, add’s and remove’s surface changes, prompt and entry-point submission, the readiness gate and the agent inventory go through it; golden argv fixtures pin the adapter to the pre-port tmux behaviour, and a source audit keeps tmux spawns inside src/tmux/ and the adapter. One owner rule (surface::resolve_agent, exact canonical path) resolves surfaces to agents. The lifecycle commands (pause, stop, attach, status, doctor) and a few launch-path helpers (session-name resolution, the attach hand-off, add’s pane reconciliation) still call src/tmux/ helpers directly. Pane numbers remain the tmux adapter’s encoding in session state
BrokerRuns in its own host process (git paw __broker) in a detached service surface of the session; the dashboard is a thin HTTP client of it, like the MCP server and the drive loop, through the shared BrokerClient
Drive loopObserve → decide → apply. The shell (supervisor/drive.rs) observes through the ports and calls pure decisions in supervisor/drive/decide.rs (decide_prompt, decide_message, decide_nudge / decide_reengaged / decide_recovery_step, summarize), which return the next state and a list of DriveEffects. The shell applies each effect in order and records it, with its outcome, in an in-memory effect log (drive_loop_logged returns DriveRun { summary, effect_log }). The heartbeat helpers heartbeat_due / tick_made_progress are pure functions the shell calls. The effect log is not persisted
Commands (start, stop, purge, …)A callable library surface (git_paw::commands): each family takes a CommandContext (repository root and config), an options struct and an injected Output sink, and returns a typed outcome. main.rs is the thin entry layer — it resolves the current directory and config, prompts, renders, and attaches or runs the drive loop from the outcome. A source audit keeps the command bodies free of cwd, stdin, prompts, prints and attach
Serialized formatsThe session file, config file and broker messages use the same types as the internal model

Further reading

  • Hexagonal Architecture (ports and adapters), Alistair Cockburn.
  • Functional Core, Imperative Shell, Gary Bernhardt.

Specifications

git-paw uses OpenSpec for formal, testable specifications: each capability has a dedicated spec.md under openspec/specs/ using RFC 2119 keywords (SHALL, MUST, SHOULD) and GIVEN/WHEN/THEN scenarios, and every scenario maps to at least one test.

The eight foundational capabilities (the original v0.1–v0.2 surface) are reproduced in full below. The project has since grown to many more capabilities (broker, supervisor, dashboard, MCP, learnings, conflict detection, unattended operation, agent-friendly docs, …); the complete index at the end of this page links to every capability spec in the repository.

Foundational capabilities

CapabilityDescription
CLI ParsingCommand-line argument parsing and subcommands
CLI DetectionAuto-detect AI CLIs on PATH, load custom CLIs
Git OperationsValidate repos, list branches, manage worktrees
Tmux OrchestrationCreate sessions, manage panes, apply layout
Session StatePersist and recover session state
ConfigurationParse and merge TOML config files
Interactive SelectionUser prompts for mode, branch, and CLI selection
Error HandlingUnified error types with exit codes

CLI Parsing

Purpose

Define the command-line interface for git-paw using clap v4. Declares all subcommands (start, stop, purge, status, list-clis, add-cli, remove-cli), their flags, and argument validation. When no subcommand is given, defaults to start. This includes the git paw start --force flag that suppresses the uncommitted-spec validation warning (logging force usage to stderr for audit and propagating git status failures as PawError::GitError), and the guarantee that git paw start --supervisor --specs <names> passes the resolved --specs value (not just its presence) to the supervisor launch path so it launches worktrees only for the named subset — pinned by a regression matrix across every selection-flag/supervisor-flag combination and required to behave identically across all spec backends (OpenSpec, Markdown, Spec Kit).

Requirements

Requirement: Default to start when no subcommand is given

The system SHALL treat no arguments as equivalent to start with no flags.

The system SHALL also accept the hidden internal subcommands __broker, which hosts the session’s broker in its detached service surface; __dashboard, which renders the dashboard as a thin client of that broker in pane 0; and __prompt-profile, which prints one resolved CLI prompt-shape profile field for the bundled helper. None of them SHALL appear in --help output.

Scenario: No arguments yields None command

  • GIVEN no arguments are passed
  • WHEN the CLI is parsed
  • THEN command SHALL be None (handled as Start in main)

Scenario: __dashboard subcommand parses

  • GIVEN __dashboard is passed
  • WHEN the CLI is parsed
  • THEN the command SHALL be Command::Dashboard

Scenario: __broker subcommand parses

  • GIVEN __broker is passed
  • WHEN the CLI is parsed
  • THEN the command SHALL be Command::Broker

Scenario: Hidden subcommands do not appear in help

  • GIVEN --help is passed
  • WHEN the help text is rendered
  • THEN the output SHALL NOT contain __dashboard, __broker or __prompt-profile

Requirement: Start subcommand with optional flags

The start subcommand SHALL be extended to accept a --supervisor flag (boolean, defaults to false). The flag MAY be combined with any other start flags.

When --supervisor is passed, the parsed StartArgs struct SHALL have supervisor: bool set to true.

The start subcommand SHALL also accept a --no-rebase flag (boolean, defaults to false). When --no-rebase is passed, the parsed StartArgs struct SHALL have no_rebase: bool set to true. The dispatch SHALL invoke create_worktree with rebase_onto_main = !args.no_rebase for every worktree creation in the launch. When --no-rebase is omitted (i.e. no_rebase == false), agent branches SHALL be rebased onto the repository’s default branch before their worktrees are opened. When --no-rebase is present, agent branches SHALL NOT be rebased, matching the post-worktree-resume-fix v0.5.0 behaviour.

The --no-rebase flag MAY be combined with any other start flags including --supervisor, --from-specs, --cli, and --branches.

Scenario: Start with –supervisor flag

  • GIVEN start --supervisor
  • WHEN the CLI is parsed
  • THEN supervisor SHALL be true

Scenario: Start with –supervisor combined with other flags

  • GIVEN start --supervisor --cli claude --branches feat/a,feat/b
  • WHEN the CLI is parsed
  • THEN supervisor SHALL be true
  • AND cli SHALL be Some("claude")
  • AND branches SHALL be ["feat/a", "feat/b"]

Scenario: Start without –supervisor defaults to false

  • GIVEN start --cli claude
  • WHEN the CLI is parsed
  • THEN supervisor SHALL be false

Scenario: Start with –no-rebase flag

  • GIVEN start --no-rebase
  • WHEN the CLI is parsed
  • THEN no_rebase SHALL be true

Scenario: Start without –no-rebase defaults to false

  • GIVEN start --cli claude
  • WHEN the CLI is parsed
  • THEN no_rebase SHALL be false

Scenario: Start with –no-rebase combined with other flags

  • GIVEN start --no-rebase --supervisor --from-specs
  • WHEN the CLI is parsed
  • THEN no_rebase SHALL be true
  • AND supervisor SHALL be true
  • AND from_specs SHALL be true

Scenario: –no-rebase propagates to create_worktree as rebase_onto_main = false

  • GIVEN start --branches feat/a --no-rebase is invoked
  • WHEN the dispatch reaches the worktree-creation loop
  • THEN create_worktree(repo_root, "feat/a", rebase_onto_main) SHALL be called with rebase_onto_main = false

Scenario: Default start propagates rebase_onto_main = true

  • GIVEN start --branches feat/a is invoked without --no-rebase
  • WHEN the dispatch reaches the worktree-creation loop
  • THEN create_worktree(repo_root, "feat/a", rebase_onto_main) SHALL be called with rebase_onto_main = true

Requirement: Stop subcommand

The stop subcommand SHALL take no confirmation flag and SHALL NOT prompt for confirmation. Stopping is recoverable — the worktrees, branches and session state survive, and git paw start recovers the session — so stop SHALL proceed immediately whether or not stdin is a TTY. The stop subcommand SHALL NOT accept a --force flag; passing one SHALL be rejected by argument parsing as an unexpected argument.

The long_about help text for stop SHALL name all three teardown verbs (pause, stop, purge) with a one-line summary of each, so users can choose the right verb at --help time, and SHALL NOT describe a confirmation prompt or a --force flag.

Scenario: Stop parses without flags

  • GIVEN stop is passed
  • WHEN the CLI is parsed
  • THEN the command SHALL be Command::Stop

Scenario: Stop rejects –force

  • GIVEN stop --force is passed
  • WHEN the CLI is parsed
  • THEN parsing SHALL fail with an unexpected-argument error naming --force

Scenario: Stop help names all three teardown verbs

  • WHEN git paw stop --help is run
  • THEN the output SHALL mention pause as the soft alternative
  • AND the output SHALL mention purge as the full reset
  • AND the output SHALL describe what stop itself does (kills CLI processes, preserves worktrees)
  • AND the output SHALL NOT mention a confirmation prompt or a --force flag

Scenario: Stop from a TTY does not prompt

  • GIVEN an active session and stdin attached to a TTY
  • WHEN git paw stop is run
  • THEN no interactive prompt SHALL be rendered
  • AND the session SHALL be stopped immediately

Requirement: Purge subcommand with optional –force flag

The purge subcommand SHALL accept an optional --force flag (defaults to false).

Scenario: Purge without –force

  • GIVEN purge is passed without flags
  • WHEN the CLI is parsed
  • THEN force SHALL be false

Test: cli::tests::purge_without_force

Scenario: Purge with –force

  • GIVEN purge --force is passed
  • WHEN the CLI is parsed
  • THEN force SHALL be true

Test: cli::tests::purge_with_force

Requirement: Status subcommand

The status subcommand SHALL parse with no additional arguments.

Scenario: Status parses

  • GIVEN status is passed
  • WHEN the CLI is parsed
  • THEN the command SHALL be Command::Status

Test: cli::tests::status_parses

Requirement: List-CLIs subcommand

The list-clis subcommand SHALL parse with no additional arguments.

Scenario: List-CLIs parses

  • GIVEN list-clis is passed
  • WHEN the CLI is parsed
  • THEN the command SHALL be Command::ListClis

Test: cli::tests::list_clis_parses

Requirement: Add-CLI subcommand with required and optional arguments

The add-cli subcommand SHALL require name and command positional arguments and accept an optional --display-name flag.

Scenario: Add-CLI with required arguments only

  • GIVEN add-cli my-agent /usr/local/bin/my-agent
  • WHEN the CLI is parsed
  • THEN name SHALL be "my-agent", command SHALL be the path, and display_name SHALL be None

Test: cli::tests::add_cli_with_required_args

Scenario: Add-CLI with –display-name

  • GIVEN add-cli my-agent my-agent --display-name "My Agent"
  • WHEN the CLI is parsed
  • THEN display_name SHALL be Some("My Agent")

Test: cli::tests::add_cli_with_display_name

Scenario: Add-CLI missing required arguments is rejected

  • GIVEN add-cli with no positional arguments
  • WHEN the CLI is parsed
  • THEN parsing SHALL fail

Test: cli::tests::add_cli_missing_required_args_is_rejected

Requirement: Remove-CLI subcommand with required argument

The remove-cli subcommand SHALL require a name positional argument.

Scenario: Remove-CLI parses

  • GIVEN remove-cli my-agent
  • WHEN the CLI is parsed
  • THEN name SHALL be "my-agent"

Test: cli::tests::remove_cli_parses

Requirement: Standard flags –version and –help

The CLI SHALL accept --version and --help flags.

Scenario: –version flag is accepted

  • GIVEN --version is passed
  • WHEN the CLI is parsed
  • THEN clap SHALL emit a DisplayVersion response

Test: cli::tests::version_flag_is_accepted

Scenario: –help flag is accepted

  • GIVEN --help is passed
  • WHEN the CLI is parsed
  • THEN clap SHALL emit a DisplayHelp response

Test: cli::tests::help_flag_is_accepted

Requirement: Unknown subcommands are rejected

The CLI SHALL reject unrecognized subcommands with a parse error.

Scenario: Unknown subcommand fails

  • GIVEN an unrecognized subcommand is passed
  • WHEN the CLI is parsed
  • THEN parsing SHALL fail

Test: cli::tests::unknown_subcommand_is_rejected

Requirement: Help output contains all subcommands and quick start

The start --help output SHALL list the --supervisor flag with a description.

Scenario: Start help shows –supervisor flag

  • GIVEN start --help is passed
  • WHEN the binary runs
  • THEN stdout SHALL contain --supervisor

Requirement: Version output includes binary name

The --version output SHALL include the binary name.

Scenario: Version output

  • GIVEN --version is passed
  • WHEN the binary runs
  • THEN stdout SHALL contain “git-paw”

Test: cli_tests::version_output

Requirement: No arguments behaves like start

When no subcommand is provided, the binary SHALL behave identically to start.

Scenario: No args produces same error as start

  • GIVEN the binary is run with no arguments outside a git repo
  • WHEN both git-paw and git-paw start are run
  • THEN they SHALL produce identical stderr output

Test: cli_tests::no_args_behaves_like_start

Requirement: Subcommands run without error when applicable

Subcommands that don’t require a session SHALL succeed in a valid git repo.

Scenario: Stop runs without error

  • GIVEN the binary is run in a git repo
  • WHEN stop is passed
  • THEN it SHALL succeed

Test: cli_tests::stop_runs_without_error

Scenario: Status runs without error

  • GIVEN the binary is run in a git repo
  • WHEN status is passed
  • THEN it SHALL succeed

Test: cli_tests::status_runs_without_error

Scenario: List-CLIs runs without error

  • GIVEN the binary is run in a git repo
  • WHEN list-clis is passed
  • THEN it SHALL succeed

Test: cli_tests::list_clis_runs_without_error

Requirement: Binary rejects missing required arguments

Subcommands with required arguments SHALL fail when they are missing.

Scenario: Add-CLI requires arguments

  • GIVEN add-cli is passed with no arguments
  • WHEN the binary runs
  • THEN it SHALL fail with stderr mentioning “required”

Test: cli_tests::add_cli_requires_arguments

Scenario: Remove-CLI requires argument

  • GIVEN remove-cli is passed with no arguments
  • WHEN the binary runs
  • THEN it SHALL fail with stderr mentioning “required”

Test: cli_tests::remove_cli_requires_argument

Requirement: Not-a-repo error from binary

Commands requiring a git repo SHALL fail with an actionable error when run outside one.

Scenario: Start from non-git directory

  • GIVEN the binary is run outside a git repository
  • WHEN start is passed
  • THEN it SHALL fail with stderr containing “Not a git repository”

Test: cli_tests::start_from_non_git_dir

Scenario: Unknown subcommand from binary

  • GIVEN the binary is passed an unrecognized subcommand
  • WHEN it runs
  • THEN it SHALL fail with stderr containing “error”

Test: cli_tests::unknown_subcommand_fails

Requirement: Replay subcommand

The replay subcommand SHALL accept an optional <branch> positional argument, a --list flag, a --color flag, and an optional --session flag.

Scenario: Replay with branch

  • WHEN replay feat/add-auth is passed
  • THEN branch SHALL be Some("feat/add-auth"), list SHALL be false, color SHALL be false

Scenario: Replay with –list

  • WHEN replay --list is passed
  • THEN list SHALL be true and branch SHALL be None

Scenario: Replay with –color

  • WHEN replay feat/add-auth --color is passed
  • THEN color SHALL be true

Scenario: Replay with –session

  • WHEN replay feat/add-auth --session paw-myproject is passed
  • THEN session SHALL be Some("paw-myproject")

Scenario: Replay with no arguments and no –list

  • WHEN replay is passed with no arguments and no --list
  • THEN parsing SHALL fail with an error indicating either a branch or --list is required

Scenario: Replay help text

  • WHEN replay --help is passed
  • THEN stdout SHALL contain descriptions of --list, --color, and --session flags with examples

Requirement: Init subcommand

The init subcommand SHALL parse with no required arguments.

Scenario: Init parses

  • WHEN init is passed
  • THEN the command SHALL be Command::Init

Scenario: Init help text

  • WHEN init --help is passed
  • THEN stdout SHALL contain a description of project initialization and examples

Requirement: Spec mode + supervisor mode dispatch

The start subcommand dispatch SHALL evaluate the supervisor-mode resolution chain (per supervisor-cli) BEFORE branching on --from-specs. Specifically:

  1. Resolve supervisor-mode-enabled state from the --supervisor flag, --no-supervisor flag, [supervisor] config, and the prompt fallback.
  2. If supervisor mode is enabled, route to cmd_supervisor. When --from-specs is also set, pass branches_flag = None so cmd_supervisor’s existing scan_specs(...) fallback runs to determine branches from configured specs.
  3. Otherwise, if --from-specs is set, route to cmd_start_from_specs.
  4. Otherwise, route to bare cmd_start.

This ordering ensures --from-specs --supervisor (or --from-specs with [supervisor] enabled = true in config) actually engages supervisor mode end-to-end, rather than silently degrading to spec-mode-without-supervisor.

--from-specs combined with --branches continues to follow v0.4’s existing behaviour (the spec-mode flow ignores explicit branches when from-specs is set); this change does not introduce a new mutual-exclusion error for that combination.

Scenario: –from-specs –supervisor engages supervisor mode

  • GIVEN git paw start --from-specs --supervisor is invoked
  • WHEN the dispatch resolves
  • THEN the supervisor-mode resolution chain SHALL evaluate supervisor = true
  • AND the dispatch SHALL route to cmd_supervisor
  • AND cmd_supervisor SHALL receive branches_flag = None, triggering its scan_specs(...) fallback

Scenario: –from-specs without supervisor uses spec mode

  • GIVEN git paw start --from-specs is invoked, no --supervisor flag, and [supervisor] config indicates supervisor mode is not enabled (either explicitly false or absent + non-interactive)
  • WHEN the dispatch resolves
  • THEN the dispatch SHALL route to cmd_start_from_specs

Scenario: –from-specs with [supervisor] enabled = true config engages supervisor mode

  • GIVEN git paw start --from-specs is invoked with no --supervisor flag
  • AND .git-paw/config.toml contains [supervisor] enabled = true
  • WHEN the dispatch resolves
  • THEN supervisor mode SHALL be active per the resolution chain
  • AND the dispatch SHALL route to cmd_supervisor (not cmd_start_from_specs)

Scenario: –no-supervisor –from-specs uses spec mode

  • GIVEN git paw start --from-specs --no-supervisor is invoked
  • AND [supervisor] enabled = true is set in config
  • WHEN the dispatch resolves
  • THEN supervisor mode SHALL be disabled per the resolution chain
  • AND the dispatch SHALL route to cmd_start_from_specs

Scenario: Bare start (no –from-specs, no supervisor) uses cmd_start

  • GIVEN git paw start is invoked with no --from-specs, no --supervisor, and supervisor mode is not enabled in config
  • WHEN the dispatch resolves
  • THEN the dispatch SHALL route to cmd_start

Requirement: Non-TTY launch handling

When a git paw start invocation reaches its session-launch step (after worktrees are created, panes added, and tmux_session.execute() succeeds), the system SHALL detect whether stdin is connected to a terminal via std::io::IsTerminal::is_terminal(&std::io::stdin()).

When stdin is not a terminal:

  • The system SHALL skip the tmux::attach(...) call.
  • The system SHALL print an informational message to stdout naming the launched session and the manual-attach command (tmux attach -t <session>).
  • The system SHALL exit with status 0.
  • For supervisor mode specifically, the system SHALL also skip the foreground supervisor-CLI launch (Command::new(supervisor_cli).status()) with an additional hint that supervisor mode requires an interactive terminal.

When stdin is a terminal, the launch flow proceeds as before (call tmux::attach, run the supervisor CLI in foreground for supervisor mode).

This SHALL apply to all three start paths: cmd_start, cmd_start_from_specs, and cmd_supervisor.

Scenario: Non-TTY bare start exits cleanly with attach hint

  • GIVEN git paw start --branches feat/x,feat/y is invoked with stdin redirected from /dev/null (or otherwise non-TTY)
  • WHEN the launch flow completes its session-build steps
  • THEN the command SHALL exit with status 0
  • AND stdout SHALL contain “Session ‘’ started in detached mode.”
  • AND stdout SHALL contain “Attach with: tmux attach -t ”
  • AND the tmux session SHALL exist and be alive after exit

Scenario: Non-TTY –from-specs exits cleanly

  • GIVEN git paw start --from-specs is invoked from a non-TTY context
  • WHEN the launch flow completes
  • THEN the command SHALL exit with status 0
  • AND the attach-hint message SHALL be printed
  • AND the tmux session SHALL exist and be alive

Scenario: Non-TTY –supervisor skips supervisor CLI launch

  • GIVEN git paw start --supervisor --from-specs is invoked from a non-TTY context
  • WHEN the launch flow completes
  • THEN the command SHALL exit with status 0
  • AND the foreground supervisor-CLI launch SHALL be skipped
  • AND stdout SHALL contain a hint indicating supervisor mode requires an interactive terminal
  • AND stdout SHALL contain the manual-attach command for the launched session

Scenario: TTY launch attaches as before

  • GIVEN git paw start --branches feat/x,feat/y is invoked from a real TTY
  • WHEN the launch flow completes its session-build steps
  • THEN the system SHALL call tmux::attach(...) for the launched session
  • AND SHALL NOT print the “started in detached mode” hint

Requirement: –from-all-specs flag

The start subcommand SHALL accept a --from-all-specs flag (boolean, default false). When passed, the resulting StartArgs SHALL indicate the “launch every discovered spec” mode — the v0.4 behaviour previously gated by --from-specs.

The flag SHALL appear in git paw start --help output with a description naming it as the canonical name for this behaviour.

Scenario: –from-all-specs sets the launch-all mode

  • GIVEN the user invokes git paw start --from-all-specs
  • WHEN the CLI is parsed
  • THEN the parsed StartArgs SHALL indicate the launch-all-discovered-specs mode

Scenario: –from-all-specs combined with –supervisor

  • GIVEN start --from-all-specs --supervisor
  • WHEN the CLI is parsed
  • THEN both from_all_specs and supervisor SHALL be true

Scenario: –from-all-specs appears in help output

  • WHEN git paw start --help is run
  • THEN the output contains --from-all-specs
  • AND the output describes the flag as launching every discovered spec

Requirement: –from-specs is a hidden alias of –from-all-specs

The start subcommand SHALL accept --from-specs as a hidden alias of --from-all-specs. When the user passes --from-specs, the parsed StartArgs SHALL be byte-for-byte identical to the parse result for --from-all-specs. No stderr warning SHALL be emitted at runtime; the alias is silent.

The alias SHALL NOT appear in git paw start --help output. The alias SHALL be removed in v1.0.0; v0.5.0 keeps it for backward compatibility with v0.4 scripts.

Scenario: –from-specs parses identically to –from-all-specs

  • GIVEN two CLI invocations: start --from-specs and start --from-all-specs
  • WHEN both are parsed
  • THEN the resulting StartArgs values SHALL be equal

Scenario: –from-specs does not appear in help

  • WHEN git paw start --help is run
  • THEN the output SHALL NOT contain the substring --from-specs

Scenario: –from-specs emits no stderr warning

  • GIVEN the user runs a command containing --from-specs
  • WHEN the CLI parses
  • THEN no stderr warning SHALL be emitted regarding the flag’s deprecation
  • AND the command proceeds exactly as if --from-all-specs had been passed

Requirement: –specs flag with comma-separated values

The start subcommand SHALL accept a --specs flag whose value is a comma-separated list of spec names (mirroring the existing --branches feat/a,feat/b syntax). The flag SHALL accept zero or more values:

  • --specs (no values) — indicates the picker mode.
  • --specs NAME — narrows to a single named spec.
  • --specs NAME1,NAME2,NAME3 — narrows to the listed specs.
  • --specs NAME1,NAME2 --specs NAME3 — equivalent to --specs NAME1,NAME2,NAME3 if clap’s value-accumulation across repetitions is enabled (implementation choice; tests assert behaviour for the comma-separated form).

The parsed value distinguishes three states:

  • Flag absent → no spec mode requested.
  • Flag present with zero values → picker mode.
  • Flag present with one or more values → narrow mode with the listed names.

The flag SHALL appear in git paw start --help output.

Scenario: –specs with single value parses as narrow

  • GIVEN start --specs add-auth
  • WHEN the CLI is parsed
  • THEN StartArgs SHALL indicate narrow mode with ["add-auth"]

Scenario: –specs with comma-separated values parses as narrow with multiple names

  • GIVEN start --specs add-auth,fix-session,add-logging
  • WHEN the CLI is parsed
  • THEN StartArgs SHALL indicate narrow mode with ["add-auth", "fix-session", "add-logging"]

Scenario: –specs with no values parses as picker

  • GIVEN start --specs
  • WHEN the CLI is parsed
  • THEN StartArgs SHALL indicate picker mode

Scenario: –specs absent leaves spec mode unset

  • GIVEN start --supervisor (no --specs, no --from-all-specs)
  • WHEN the CLI is parsed
  • THEN StartArgs SHALL indicate no spec mode (falls through to standard branch selection)

Requirement: –from-all-specs and –specs are mutually exclusive

The system SHALL reject any invocation that combines --from-all-specs (or its alias --from-specs) with --specs. clap’s parse step SHALL produce an error before the command runs. The error message SHALL clearly state that the two flags express opposing intents and SHALL list both flags.

Scenario: –from-all-specs and –specs together are rejected

  • GIVEN start --from-all-specs --specs add-auth
  • WHEN the CLI is parsed
  • THEN parsing SHALL fail with an error mentioning both --from-all-specs and --specs

Scenario: –from-specs alias and –specs together are also rejected

  • GIVEN start --from-specs --specs add-auth
  • WHEN the CLI is parsed
  • THEN parsing SHALL fail with an error mentioning both flags
  • AND the alias SHALL enforce the same mutual-exclusion rule as the canonical name

Requirement: –no-supervisor flag

The start subcommand SHALL accept a --no-supervisor flag (boolean, default false). When passed, the parsed StartArgs SHALL have no_supervisor: bool set to true. The flag SHALL appear in git paw start --help output with a description that names the use case (overriding [supervisor] enabled = true for a single session).

Scenario: –no-supervisor sets the flag

  • GIVEN the user invokes git paw start --no-supervisor
  • WHEN the CLI is parsed
  • THEN the parsed StartArgs.no_supervisor SHALL be true
  • AND StartArgs.supervisor SHALL be false

Scenario: –no-supervisor absent leaves flag false

  • GIVEN the user invokes git paw start with neither --supervisor nor --no-supervisor
  • WHEN the CLI is parsed
  • THEN StartArgs.no_supervisor SHALL be false
  • AND StartArgs.supervisor SHALL be false

Scenario: –no-supervisor appears in help output

  • WHEN git paw start --help is run
  • THEN the output contains --no-supervisor
  • AND the output describes the flag as disabling supervisor for the session and overriding any [supervisor] enabled = true config setting

Requirement: –supervisor and –no-supervisor are mutually exclusive

The system SHALL reject any invocation that combines --supervisor and --no-supervisor on the same start command. clap’s parse step SHALL produce an error before the command runs. The error message SHALL clearly state that the two flags express opposing intents and SHALL list both.

Scenario: Both flags together are rejected

  • GIVEN start --supervisor --no-supervisor
  • WHEN the CLI is parsed
  • THEN parsing SHALL fail with an error mentioning both --supervisor and --no-supervisor

Scenario: –no-supervisor combines with other flags

  • GIVEN start --no-supervisor --cli claude --branches feat/a,feat/b
  • WHEN the CLI is parsed
  • THEN no_supervisor SHALL be true
  • AND cli SHALL be Some("claude")
  • AND branches SHALL contain feat/a and feat/b
  • AND parsing SHALL succeed

Requirement: Pause subcommand

The pause subcommand SHALL parse with no additional arguments and SHALL be visible in git paw --help output. The subcommand SHALL include an about string (“Pause the session (detaches client, stops broker, leaves CLIs running)”) and a long_about string that names the RAM trade-off and points the reader at stop and (forthcoming v1.0.0) hibernate for the destructive and RAM-free alternatives respectively.

The pause subcommand SHALL appear in the root after_help quick-start guide alongside start, stop, and purge.

Scenario: Pause parses

  • GIVEN pause is passed
  • WHEN the CLI is parsed
  • THEN the command SHALL be Command::Pause

Scenario: Pause accepts no flags

  • GIVEN pause --anything is passed (any flag)
  • WHEN the CLI is parsed
  • THEN parsing SHALL fail with an unknown-argument error

Scenario: Pause appears in help

  • WHEN git paw --help is run
  • THEN the output SHALL list a pause subcommand
  • AND the output SHALL include the pause line in the quick-start after_help block

Scenario: Pause help text names the RAM trade-off

  • WHEN git paw pause --help is run
  • THEN the output SHALL mention that CLI processes remain running
  • AND the output SHALL mention the RAM-allocation trade-off (or words conveying “RAM stays held”)
  • AND the output SHALL suggest git paw stop for the RAM-releasing alternative

Requirement: git paw purge interactive confirmation SHALL honour y+Enter under all conditions

The cmd_purge interactive confirmation prompt SHALL be reliably readable by the dialoguer Confirm widget regardless of preceding stderr output. When the unmerged-commits warning has been written to stderr immediately before the prompt, the warning writer SHALL flush stderr before the prompt’s interact() call begins, so the user’s y+Enter input is not racing the warning’s buffered bytes.

Scenario: Purge with unmerged commits and y+Enter proceeds

  • GIVEN a session with at least one branch carrying commits not in main
  • AND git paw purge is invoked from a TTY
  • WHEN the prompt “Purge is irreversible. Continue?” appears and the user types y followed by Enter
  • THEN the purge SHALL proceed (kill tmux session + remove worktrees + delete session JSON)
  • AND the exit code SHALL be 0
  • AND stdout SHALL contain Purged session 'paw-...'

Scenario: Purge with unmerged commits and n+Enter cancels

  • GIVEN same setup as above
  • WHEN the user types n followed by Enter
  • THEN the purge SHALL NOT proceed
  • AND the exit code SHALL be 0
  • AND stdout SHALL contain Purge cancelled.
  • AND the session worktrees SHALL still be on disk

Scenario: Purge with bare Enter (no y/n) defaults to no

  • GIVEN same setup
  • WHEN the user types Enter without first typing y or n
  • THEN the prompt SHALL default to false (No)
  • AND the purge SHALL NOT proceed
  • AND stdout SHALL contain Purge cancelled.

Requirement: git paw purge --force SHALL propagate --force to git worktree remove and emit per-worktree progress

When git paw purge is invoked with --force, the underlying git worktree remove invocations SHALL pass --force so the removal succeeds on worktrees with uncommitted changes. The command SHALL also emit per-worktree progress messages to stderr (e.g. Removing worktree <path>... before each removal and done (<elapsed>s) after) so the user can distinguish a slow-but-progressing removal from an actual hang.

Scenario: purge --force removes dirty worktrees

  • GIVEN a session with one worktree containing uncommitted edits
  • WHEN git paw purge --force is invoked
  • THEN the dirty worktree SHALL be removed successfully
  • AND the exit code SHALL be 0
  • AND the underlying git worktree remove invocation SHALL include the --force flag

Scenario: purge --force emits per-worktree progress to stderr

  • GIVEN a session with two or more worktrees
  • WHEN git paw purge --force is invoked
  • THEN stderr SHALL contain a Removing worktree <path>... line for each worktree being removed
  • AND stderr SHALL contain a done or completion marker after each removal
  • AND the order SHALL match the worktree iteration order

Scenario: purge without --force does NOT pass --force to git worktree remove

  • GIVEN a session with one worktree containing uncommitted edits
  • WHEN git paw purge (no --force) is invoked and the user confirms with y
  • THEN the underlying git worktree remove SHALL NOT include the --force flag
  • AND if git worktree remove fails because of the dirty state, the failure SHALL be reported to stderr as warning: failed to remove worktree '<path>': <git error> per the existing error-handling path
  • AND purge SHALL continue with the remaining worktrees

Requirement: git paw stop and git paw purge SHALL strip the supervisor boot-block injection from AGENTS.md

cmd_stop and cmd_purge (src/main.rs) SHALL invoke a helper that removes the supervisor-pane boot-block injection block from <repo>/AGENTS.md. The block is bounded by HTML comment markers <!-- git-paw:start --> … <!-- git-paw:end --> (or similar — the actual marker strings are owned by the injection code path and SHALL match exactly). The helper SHALL be idempotent and SHALL preserve all surrounding content byte-for-byte.

Scenario: Stop strips the boot-block injection

  • GIVEN a session in which cmd_supervisor or cmd_start injected a <!-- git-paw:start -->…<!-- git-paw:end --> block into AGENTS.md
  • WHEN git paw stop (with or without --force) is invoked
  • AND the teardown completes successfully
  • THEN the resulting AGENTS.md SHALL contain no <!-- git-paw:start --> marker
  • AND no <!-- git-paw:end --> marker

Scenario: Purge strips the boot-block injection

  • GIVEN the same setup
  • WHEN git paw purge (with or without --force) is invoked
  • THEN AGENTS.md SHALL contain neither marker after the purge completes

Scenario: Stop/purge on AGENTS.md without markers is a no-op

  • GIVEN an AGENTS.md with no <!-- git-paw:start --> marker
  • WHEN git paw stop or git paw purge runs the cleanup helper
  • THEN AGENTS.md SHALL be byte-identical to its pre-cleanup state
  • AND the helper SHALL return success

Requirement: git paw init SHALL be idempotent and additive on existing config files

src/init.rs::run_init SHALL parse the existing .git-paw/config.toml (if any) and compare its top-level keys/tables against the bundled-default schema. The init flow SHALL append commented stanzas ONLY for keys/tables missing from the user’s config. It SHALL NEVER:

  1. Modify the value of an existing key.
  2. Add a second occurrence of any top-level table (e.g. a second [supervisor]) when the user already has that section commented OR uncommented.
  3. Re-order or reformat existing keys/sections.
  4. Strip existing user comments or blank lines.

When every bundled-default key is already present in the user’s config, init SHALL print config.toml already has all default keys; no changes and return Ok without writing.

Init invocations SHALL be idempotent: running git paw init a second time on a config that the first run produced SHALL leave the file byte-identical.

Scenario: First init writes a complete commented default config

  • GIVEN a fresh repo with no .git-paw/config.toml
  • WHEN git paw init is invoked
  • THEN the file SHALL be created
  • AND SHALL parse as valid TOML
  • AND SHALL contain commented stanzas for every bundled-default top-level key/section

Scenario: Second init on the just-written file is a no-op

  • GIVEN the same repo after the first init
  • WHEN git paw init is invoked again
  • THEN the file SHALL be byte-identical to the first-run output
  • AND the exit SHALL be 0

Scenario: Init preserves a user-authored [supervisor] block

  • GIVEN a .git-paw/config.toml containing only:
    [supervisor]
    enabled = true
    cli = "claude-oss"
    
  • WHEN git paw init is invoked
  • THEN the resulting file SHALL contain enabled = true and cli = "claude-oss" byte-identical to the input
  • AND SHALL NOT contain a second [supervisor] section header (commented or uncommented)
  • AND SHALL parse as valid TOML (no duplicate key errors)

Scenario: Init appends missing top-level sections

  • GIVEN a .git-paw/config.toml containing only branch_prefix = "feat/"
  • WHEN git paw init is invoked
  • THEN the resulting file SHALL preserve branch_prefix = "feat/" byte-identical
  • AND SHALL gain commented stanzas for every bundled-default section the user is missing ([broker], [dashboard], [supervisor], etc.)
  • AND SHALL parse as valid TOML

Scenario: Init never modifies existing user values

  • GIVEN a .git-paw/config.toml with [broker] port = 9200 (non-default port)
  • WHEN git paw init is invoked
  • THEN the resulting file SHALL still have port = 9200
  • AND SHALL NOT introduce a second port key or a commented # port = 9119 stanza inside [broker]

Requirement: –unattended flag

The start subcommand SHALL accept an --unattended flag (boolean, default false). When passed, the parsed StartArgs SHALL have unattended: bool set to true. When omitted, unattended SHALL be false.

The flag SHALL appear in git paw start --help output with a description naming the use case: drive a supervisor wave to completion in-process (escalate every permission prompt for a human decision without blocking the rest of the wave, detect completion, exit with a summary). The help text SHALL NOT claim that the flag approves prompts, and its long help SHALL state that prompt-free unattended runs need the agent CLI’s own allow rules or the full-auto approval level inside a sandbox.

--unattended engages supervisor mode: when --unattended is passed, the supervisor-mode resolution chain SHALL resolve supervisor mode active (equivalent to --supervisor) so the dispatch routes to the supervisor launch path. --unattended MAY be combined with --supervisor, --from-all-specs (and its alias --from-specs), --specs, --cli, and --branches.

--unattended SHALL NOT be combined with --no-supervisor; the system SHALL reject any invocation that combines --unattended with --no-supervisor with a parse error naming both flags, because they express opposing intents.

Scenario: –unattended sets the flag

  • GIVEN the user invokes git paw start --unattended
  • WHEN the CLI is parsed
  • THEN the parsed StartArgs.unattended SHALL be true

Scenario: –unattended absent leaves flag false

  • GIVEN the user invokes git paw start without --unattended
  • WHEN the CLI is parsed
  • THEN StartArgs.unattended SHALL be false

Scenario: –unattended combines with other flags

  • GIVEN start --unattended --from-specs --cli claude
  • WHEN the CLI is parsed
  • THEN unattended SHALL be true
  • AND the spec-mode launch-all state SHALL be set
  • AND cli SHALL be Some("claude")
  • AND parsing SHALL succeed

Scenario: –unattended appears in help output

  • WHEN git paw start --help is run
  • THEN the output SHALL contain --unattended
  • AND the output SHALL describe the flag as driving the wave to completion and escalating permission prompts
  • AND the output SHALL NOT describe the flag as auto-approving prompts

Scenario: –unattended engages supervisor mode

  • GIVEN git paw start --unattended --branches feat/a,feat/b is invoked
  • WHEN the dispatch resolves the supervisor-mode resolution chain
  • THEN supervisor mode SHALL resolve active
  • AND the dispatch SHALL route to the supervisor launch path (cmd_supervisor)

Scenario: –unattended with –no-supervisor is rejected

  • GIVEN start --unattended --no-supervisor
  • WHEN the CLI is parsed
  • THEN parsing SHALL fail with an error mentioning both --unattended and --no-supervisor

Requirement: Force flag definition

The git paw start command SHALL accept an optional --force boolean flag that defaults to false. The flag SHALL appear in git paw start --help output with descriptive text explaining that it bypasses the uncommitted-spec validation warning. The flag SHALL parse according to Clap’s boolean flag rules.

Scenario: Force flag is parsed by clap

  • GIVEN the git paw start command
  • WHEN the user invokes git paw start --from-specs --force
  • THEN the parsed Start arguments expose force = true
  • AND when invoked without --force, force = false

Scenario: Force flag appears in help text

  • WHEN the user runs git paw start --help
  • THEN the output contains a description for --force mentioning uncommitted-spec validation

Requirement: Validation bypass with force flag

When --force is provided, the command SHALL proceed with execution even if uncommitted spec changes are detected. When --force is NOT provided and uncommitted specs exist, the command SHALL display a warning that lists the IDs of specs with uncommitted changes and SHALL continue execution. Uncommitted-spec detection SHALL iterate through pending specs from scan_specs(), check each spec’s directory using git status --porcelain, and collect IDs of specs with non-empty git status output.

Scenario: No uncommitted specs

  • GIVEN all pending specs are committed in git
  • WHEN the user runs git paw start --from-specs
  • THEN the command proceeds normally without any uncommitted-spec warning

Scenario: Uncommitted specs without force

  • GIVEN at least one pending spec has uncommitted changes per git status --porcelain
  • WHEN the user runs git paw start --from-specs
  • THEN stderr contains a warning listing the IDs of specs with uncommitted changes
  • AND the command continues execution

Scenario: Uncommitted specs with force

  • GIVEN at least one pending spec has uncommitted changes
  • WHEN the user runs git paw start --from-specs --force
  • THEN the uncommitted-spec warning is suppressed
  • AND the command logs force usage to stderr for audit purposes
  • AND the command continues execution

Scenario: Force combined with dry run

  • GIVEN at least one pending spec has uncommitted changes
  • WHEN the user runs git paw start --from-specs --force --dry-run
  • THEN dry-run output is shown without the uncommitted-spec warning

Requirement: Force flag error handling

When checking spec git status, the command SHALL propagate git status failures as PawError::GitError. Invalid spec paths SHALL be skipped without aborting the overall validation.

Scenario: Git status failure is reported as PawError

  • GIVEN a spec directory where git status --porcelain exits non-zero
  • WHEN uncommitted-spec validation runs
  • THEN the command returns a PawError::GitError

Requirement: –specs list honoured under –supervisor

The dispatcher SHALL pass the --specs flag’s resolved value (not just its presence) to the supervisor launch path. When the user invokes git paw start --supervisor --specs <names>, the system SHALL launch worktrees ONLY for the named subset of discovered specs, matching the non-supervisor --specs behaviour exactly.

Scenario: –supervisor –specs subset filters correctly

  • GIVEN an openspec/changes/ directory containing N discovered specs (N > 2)
  • WHEN the user runs git paw start --supervisor --specs a,b where a and b are exactly two of the N specs
  • THEN the launch SHALL create exactly two worktrees (for a and b) and SHALL NOT create worktrees for any other spec in the directory

Scenario: –supervisor –from-all-specs still launches all

  • GIVEN the same N discovered specs
  • WHEN the user runs git paw start --supervisor --from-all-specs
  • THEN the launch SHALL create worktrees for all N discovered specs (current behaviour preserved)

Scenario: –supervisor with no spec flag uses branch picker

  • WHEN the user runs git paw start --supervisor with neither --specs nor --from-all-specs
  • THEN the launch SHALL behave identically to git paw start (branch picker) but with supervisor mode enabled — no automatic discovery of every spec

Scenario: –supervisor –specs (picker, no values) opens interactive picker

  • GIVEN an interactive TTY
  • WHEN the user runs git paw start --supervisor --specs with no values
  • THEN the system SHALL open the multi-select picker; selected specs alone SHALL determine the launched worktrees

Scenario: –supervisor + –specs without –from-all-specs

  • GIVEN N discovered specs in openspec/changes/
  • WHEN the user runs git paw start --supervisor --specs cold-start-ci-parity (a single named spec)
  • THEN exactly one worktree SHALL be created (for cold-start-ci-parity); no worktree SHALL exist for any other spec

Requirement: Regression matrix for selection-flag combinations

The test suite SHALL include a matrix of integration tests covering every combination of selection flag and supervisor flag. The matrix SHALL include at minimum the 15 combinations of {none, --from-all-specs, --specs <names>, --specs (picker), --branches} × {no supervisor, --supervisor, --no-supervisor}.

Scenario: Matrix passes for every combination

  • WHEN the integration matrix runs
  • THEN every cell SHALL pass — each combination produces the documented worktree set, and no combination produces worktrees for specs outside the filter

Scenario: Matrix catches a regression in any cell

  • GIVEN a hypothetical regression in any cell (e.g. someone re-introduces the v0.6.0 bug where --supervisor --specs <list> launches all discovered specs)
  • WHEN the matrix runs
  • THEN the failing cell’s test SHALL fail with a message identifying the offending flag combination

Requirement: Stack-agnostic and backend-agnostic

The filter behaviour SHALL be identical across all configured spec backends (OpenSpec, Markdown, Spec Kit). The bug fix SHALL NOT introduce new backend-specific code paths.

Scenario: Same filter behaviour on Markdown backend

  • GIVEN a Markdown-backend repo with N specs
  • WHEN the user runs git paw start --supervisor --specs a
  • THEN the launch SHALL create exactly one worktree for spec a, matching the OpenSpec-backend behaviour

Scenario: Same filter behaviour on Spec Kit backend

  • GIVEN a Spec Kit .specify/specs/ repo with N features
  • WHEN the user runs git paw start --supervisor --specs 003-feature-name
  • THEN the launch SHALL create exactly one worktree for that feature

Requirement: Spec-driven launch chapter uses the canonical flag name

docs/src/user-guide/spec-driven-launch.md SHALL use --from-all-specs as the canonical flag name in every example. The hidden v0.4 alias --from-specs SHALL NOT appear in example command lines in the chapter (per the cross-format-spec-selection archive’s documentation policy, the alias is intentionally undocumented to nudge migration).

Scenario: Spec-driven launch chapter uses –from-all-specs

  • WHEN docs/src/user-guide/spec-driven-launch.md is inspected
  • THEN every example command launching all discovered specs uses --from-all-specs
  • AND no example command line uses --from-specs

Requirement: CLI reference lists every top-level subcommand

docs/src/cli-reference.md’s top-level commands list SHALL include every public subcommand. Currently the list SHALL include (at minimum) start, stop, purge, status, list-clis, add-cli, remove-cli, init, and replay. The v0.4 omission of init and replay SHALL be repaired.

Scenario: CLI reference lists init

  • WHEN the top-level commands section of docs/src/cli-reference.md is inspected
  • THEN it contains the substring init

Scenario: CLI reference lists replay

  • WHEN the top-level commands section of docs/src/cli-reference.md is inspected
  • THEN it contains the substring replay

Requirement: CLI reference start-flag table lists every flag

docs/src/cli-reference.md’s start-flag table SHALL include every flag defined in src/cli.rs::StartArgs. Currently the table SHALL include (at minimum) --cli, --branches, --dry-run, --preset, --from-all-specs, --specs, --specs-format, --supervisor, --no-supervisor, and --force.

Scenario: CLI reference includes –specs-format

  • WHEN the start-flag table in docs/src/cli-reference.md is inspected
  • THEN it contains the substring --specs-format

Scenario: CLI reference includes –no-supervisor

  • WHEN the start-flag table in docs/src/cli-reference.md is inspected
  • THEN it contains the substring --no-supervisor

Scenario: CLI reference includes –from-all-specs and –specs

  • WHEN the start-flag table in docs/src/cli-reference.md is inspected
  • THEN it contains the substring --from-all-specs
  • AND it contains the substring --specs (as a distinct flag, not as a prefix of --specs-format)

Scenario: CLI reference includes –force

  • WHEN the start-flag table in docs/src/cli-reference.md is inspected
  • THEN it contains the substring --force

Requirement: Attach subcommand

The attach subcommand SHALL reattach the current terminal to the running tmux session for the current repository, resolving the session by the repository path (as status does) and invoking the tmux attach path. When no session exists for the repository, or the session is not alive, it SHALL exit with an actionable error naming that no session is running and how to start one. This provides a first-class git paw attach in place of the raw tmux attach -t paw-<project>.

Scenario: Attach reattaches to the repository’s running session

  • GIVEN a running session for the current repository
  • WHEN the user runs git paw attach
  • THEN the system SHALL attach the terminal to that session

Scenario: Attach with no running session errors actionably

  • GIVEN no running session exists for the current repository
  • WHEN the user runs git paw attach
  • THEN the system SHALL exit with an error stating no session is running and how to start one

Requirement: Completions subcommand

The completions subcommand SHALL print a shell-completion script for a requested shell (bash, zsh, fish) to stdout, generated from the clap command definition, so a user can install completions through their shell’s standard mechanism. A missing or unsupported shell argument SHALL produce an actionable error.

Scenario: Completions prints a script for a supported shell

  • WHEN the user runs git paw completions bash
  • THEN the system SHALL print a bash completion script to stdout and exit successfully

Scenario: Completions rejects an unsupported shell

  • WHEN the user runs git paw completions <unsupported>
  • THEN the system SHALL exit with an actionable error naming the supported shells

Requirement: No resume subcommand; reattach is attach, revival is start

The CLI SHALL NOT provide a resume subcommand. Reattaching the current terminal to a running session SHALL be git paw attach; reviving a paused or stopped session SHALL remain git paw start. The documentation (CLI reference, README, user guide) SHALL NOT reference a git paw resume command.

Scenario: git paw resume is not a valid subcommand

  • WHEN the user runs git paw resume
  • THEN the system SHALL reject it as an unknown subcommand

Scenario: Docs do not reference a resume command

  • WHEN the CLI reference and user guide are inspected
  • THEN they SHALL NOT reference a git paw resume command; reattach is documented as git paw attach and session revival as git paw start

Requirement: Purge –all-repos flag for a machine-wide stale sweep

git paw purge SHALL accept an --all-repos flag that broadens the --stale sweep from the current repository (the default) to every repository with a receipt on the machine. --all-repos SHALL be meaningful only in combination with --stale; used without --stale it SHALL be rejected with an actionable error (or treated as inert) per the CLI’s flag rules. The machine-wide sweep SHALL still only ever purge stale receipts and SHALL never purge a live session.

Scenario: –all-repos requires –stale

  • WHEN the user runs git paw purge --all-repos without --stale
  • THEN the CLI SHALL reject the combination with an actionable error (or treat --all-repos as inert), per the flag rules

Scenario: –stale –all-repos broadens the sweep

  • GIVEN stale receipts in multiple repositories
  • WHEN the user runs git paw purge --stale --all-repos
  • THEN the sweep SHALL cover every repository’s stale receipts
  • AND live sessions SHALL remain intact

CLI Detection

cli-resolution Specification

Purpose

Detect available AI coding CLI binaries by scanning PATH for known names and merging with user-defined custom CLIs from configuration, providing a unified, deduplicated, sorted list, and resolve which CLI to use for each spec-driven branch using a multi-level priority chain that considers command-line flags, per-spec overrides, config defaults, and interactive selection, prompting the user at most once. It also covers reliable boot-block submission and CLI-pane launch hardening: injecting the prompt text literally and then sending Enter as a separate keystroke after a settle delay resolved per CLI from [clis.<name>].submit_delay_ms (with a single CLI-agnostic default and no hardcoded CLI names), clearing the shell input line before the launch command, suppressing known auto-update/confirmation prompts in the launched pane’s environment, and verifying the CLI started within a bounded window and retrying the launch once on failure — so a fresh supervisor session boots and all agents self-register unattended.

Requirements

Requirement: Auto-detect known AI CLIs on PATH

The system SHALL scan PATH for the known CLI binaries: claude, codex, agy, aider, vibe, qwen, amp, opencode, cline, droid, pi, junie, cursor-agent, copilot, cn, kilo, and kimi.

agy is the Antigravity CLI (display name “Antigravity”), which replaces the retired Gemini CLI (gemini) — Gemini CLI stops serving free / AI Pro / AI Ultra users on 2026-06-18. Removing gemini affects auto-detection only; an explicit default_cli = "gemini", a [clis.gemini] custom entry, or a saved-session cli string still resolves as a pass-through.

cursor-agent is Cursor’s agent CLI (display name “Cursor Agent”). The bare cursor binary is the Cursor editor launcher on most installations and SHALL NOT be auto-detected as an AI CLI. Removing cursor affects auto-detection only: an explicit git paw start --cli cursor, a [clis.cursor] custom entry, or a saved-session cli = "cursor" still resolves as a pass-through. The paths that validate a CLI against the detected roster — spec-driven selection, git paw add, and the interactive picker (which default_cli only pre-selects within) — require a [clis.cursor] entry.

Scenario: All known CLIs are present on PATH

  • GIVEN all 17 known CLI binaries exist on PATH
  • WHEN detect_known_clis() is called
  • THEN it SHALL return a CliInfo for each binary with source = Detected, a non-empty display_name, and a valid path

Test: detect::tests::all_known_clis_detected_when_present

Scenario: No known CLIs are present on PATH

  • GIVEN PATH contains no known CLI binaries
  • WHEN detect_known_clis() is called
  • THEN it SHALL return an empty list

Test: detect::tests::returns_empty_when_no_known_clis_on_path

Scenario: Partial set of CLIs on PATH

  • GIVEN only a subset of known CLIs exist on PATH
  • WHEN detect_known_clis() is called
  • THEN it SHALL return only the CLIs that are found

Test: detect::tests::detects_subset_of_known_clis

Scenario: Antigravity CLI is detected under its agy binary name

  • GIVEN the agy binary exists on PATH
  • WHEN detect_known_clis() is called
  • THEN the result SHALL include a CliInfo with binary_name = "agy" and source = Detected
  • AND the result SHALL NOT include a CLI with binary_name = "gemini" unless gemini is supplied as a custom [clis.*] entry

Test: detect::tests::agy_detected_and_gemini_not_known

Scenario: Cursor’s agent CLI is detected under cursor-agent, not the editor launcher

  • GIVEN both a cursor and a cursor-agent binary exist on PATH
  • WHEN detect_known_clis() is called
  • THEN the result SHALL include a CliInfo with binary_name = "cursor-agent", display_name = "Cursor Agent", and source = Detected
  • AND the result SHALL NOT include a CLI with binary_name = "cursor"

Test: detect::tests::cursor_agent_detected_and_editor_launcher_not_known

Scenario: An explicit cursor CLI still passes through

  • GIVEN no cursor entry is auto-detected and no [clis.cursor] entry is configured
  • WHEN a bare session is started with --cli cursor
  • THEN the branch-to-CLI mapping SHALL carry cursor unchanged
  • AND the pane SHALL be launched with cursor

Test: interactive::resolver::tests::explicit_cursor_cli_passes_through_without_detection, config::tests::launch_command_falls_back_to_name_without_entry

Requirement: Resolve and merge custom CLIs from configuration

The system SHALL resolve custom CLI definitions by looking up commands as absolute paths or via PATH, and merge them with auto-detected CLIs.

Scenario: Custom CLIs merged with detected CLIs

  • GIVEN auto-detected CLIs exist and custom CLI definitions are provided
  • WHEN detect_clis() is called
  • THEN the result SHALL contain both detected and custom CLIs

Test: detect::tests::custom_clis_merged_with_detected

Scenario: Custom CLI binary not found

  • GIVEN a custom CLI definition references a non-existent binary
  • WHEN detect_clis() is called
  • THEN the missing CLI SHALL be excluded and a warning printed to stderr

Test: detect::tests::custom_cli_excluded_when_binary_missing

Scenario: Custom CLI resolved by absolute path

  • GIVEN a custom CLI definition uses an absolute path to an existing binary
  • WHEN resolve_custom_clis() is called
  • THEN the resolved path SHALL match the absolute path provided

Test: detect::tests::custom_cli_resolved_by_absolute_path

Requirement: Custom CLIs override detected CLIs with the same name

When a custom CLI has the same binary_name as a detected CLI, the custom definition SHALL take precedence.

Scenario: Custom CLI overrides auto-detected CLI

  • GIVEN a custom CLI shares a binary_name with an auto-detected CLI
  • WHEN detect_clis() is called
  • THEN the result SHALL contain only the custom version with source = Custom

Test: detect::tests::custom_cli_overrides_detected_with_same_binary_name

Requirement: Each CLI result includes all required fields

Every CliInfo SHALL have a non-empty display_name, binary_name, a valid path, and a source indicator.

Scenario: Detected CLI has all fields populated

  • GIVEN a known CLI binary exists on PATH
  • WHEN it is detected
  • THEN all fields (display_name, binary_name, path, source) SHALL be populated

Test: detect::tests::detected_cli_has_all_fields

Scenario: Custom CLI has all fields populated

  • GIVEN a custom CLI definition is resolved
  • WHEN it is included in results
  • THEN all fields SHALL be populated

Test: detect::tests::custom_cli_has_all_fields

Requirement: Display name derivation

When no explicit display name is provided, the system SHALL derive one by capitalizing the first letter of the binary name.

Scenario: Custom CLI defaults to capitalized name

  • GIVEN a custom CLI definition has no display_name
  • WHEN it is resolved
  • THEN the display_name SHALL be the binary name with the first letter capitalized

Test: detect::tests::custom_cli_display_name_defaults_to_capitalised_name

Requirement: Results sorted by display name

The combined CLI list SHALL be sorted alphabetically by display_name (case-insensitive).

Scenario: Results are sorted

  • GIVEN multiple CLIs are detected and/or custom
  • WHEN detect_clis() is called
  • THEN the results SHALL be sorted by display name

Test: detect::tests::results_sorted_by_display_name

Requirement: CliSource display format

The CliSource enum SHALL display as "detected" or "custom".

Scenario: CliSource display strings

  • GIVEN CliSource::Detected and CliSource::Custom
  • WHEN formatted with Display
  • THEN they SHALL render as "detected" and "custom" respectively

Test: detect::tests::cli_source_display_format

Requirement: CLI resolution chain for spec-driven launches

The system SHALL resolve which CLI to use for each spec-driven branch using a 5-level priority chain, from highest to lowest priority. This chain SHALL apply identically to non-supervisor and supervisor-mode spec-driven launches — the --supervisor flag SHALL NOT bypass or alter it. In particular, default_spec_cli SHALL be honoured for spec branches lacking a paw_cli override even when --supervisor is combined with --specs, so supervisor-mode workers are never silently launched with a different (e.g. unsandboxed) CLI than configured.

Scenario: –cli flag overrides everything

  • WHEN --cli claude is passed and specs have various paw_cli values
  • THEN all branches SHALL use "claude" regardless of spec or config values

Scenario: paw_cli in spec overrides config

  • WHEN no --cli flag is passed and a spec has paw_cli: gemini
  • THEN that branch SHALL use "gemini" regardless of default_spec_cli or default_cli

Scenario: default_spec_cli fills remaining without prompt

  • WHEN no --cli flag, some specs have no paw_cli, and default_spec_cli = "claude" in config
  • THEN specs without paw_cli SHALL use "claude" with no interactive prompt

Scenario: default_cli pre-selects in picker

  • WHEN no --cli flag, no paw_cli, no default_spec_cli, and default_cli = "claude" in config
  • THEN the CLI picker SHALL be shown with "claude" pre-selected

Scenario: No defaults — full picker

  • WHEN no --cli flag, no paw_cli, no default_spec_cli, and no default_cli
  • THEN the CLI picker SHALL be shown with no pre-selection

Scenario: default_spec_cli is honoured under –supervisor –specs

  • WHEN git paw start --supervisor --specs a,b runs with no --cli, specs a and b having no paw_cli, and default_spec_cli = "claude-oss" in config
  • THEN the a and b worktree panes SHALL launch the "claude-oss" CLI (not a fallback or default CLI), identical to the non-supervisor --specs resolution

Requirement: Mixed resolution across specs

The system SHALL handle specs where some have paw_cli and others don’t in the same launch.

Scenario: Mix of paw_cli and default_spec_cli

  • WHEN 3 specs are launched, 1 has paw_cli: gemini, and default_spec_cli = "claude"
  • THEN the gemini spec SHALL use "gemini" and the other 2 SHALL use "claude"

Scenario: Mix of paw_cli and interactive

  • WHEN 3 specs are launched, 1 has paw_cli: gemini, no default_spec_cli, and user picks "claude" in the prompt
  • THEN the gemini spec SHALL use "gemini" and the other 2 SHALL use "claude"

Requirement: Prompt at most once

The system SHALL prompt the user for CLI selection at most once during a --from-specs launch, applying the choice to all branches without a paw_cli or default_spec_cli.

Scenario: Single prompt for remaining branches

  • WHEN 5 specs are launched, 2 have paw_cli, and the picker fires for the remaining 3
  • THEN the picker SHALL fire once and the chosen CLI SHALL be applied to all 3

Requirement: Validate resolved CLI names

The system SHALL validate that each resolved CLI name matches an available CLI.

Scenario: paw_cli references unknown CLI

  • WHEN a spec has paw_cli: nonexistent and no CLI named "nonexistent" is available
  • THEN the system SHALL return PawError::CliNotFound("nonexistent")

Scenario: default_spec_cli references unknown CLI

  • WHEN default_spec_cli = "nonexistent" and no such CLI is available
  • THEN the system SHALL return PawError::CliNotFound("nonexistent")

Scenario: –cli flag references unknown CLI

  • GIVEN specs exist
  • WHEN --cli nonexistent is passed and “nonexistent” is not in available CLIs
  • THEN the system SHALL return PawError::CliNotFound("nonexistent")

Requirement: No prompt when fully resolved

The system SHALL not show any interactive prompt when all branches are resolved via --cli, paw_cli, or default_spec_cli.

Scenario: All resolved without prompt

  • WHEN --cli claude is passed
  • THEN no interactive prompt SHALL be shown

Scenario: All resolved via paw_cli and default_spec_cli

  • WHEN every spec has paw_cli or default_spec_cli covers the rest
  • THEN no interactive prompt SHALL be shown

Requirement: Boot prompt submitted via split-send + settle delay

The boot-injection path SHALL inject the boot block into a pane literally and then submit it with a SEPARATE Enter sent after a settle delay, rather than a same-call trailing Enter. This split is what reliably submits a large paste across CLIs (W15-1: a same-call trailing Enter left the boot block unsubmitted on a custom CLI). The mechanism SHALL contain no hardcoded CLI names.

Scenario: Boot block is injected then submitted separately

  • WHEN a boot block is injected into a pane
  • THEN the system SHALL first send the prompt text (literally, no Enter), then after the settle delay send Enter as a separate keystroke

Scenario: Mechanism is CLI-name-free

  • WHEN the submit path is inspected
  • THEN it SHALL NOT branch on any specific CLI name — the same split-send applies to every CLI

Requirement: Settle delay is config-driven with an agnostic default

The settle delay SHALL be resolved per CLI from [clis.<name>].submit_delay_ms, falling back to a single CLI-agnostic default (DEFAULT_SUBMIT_DELAY_MS) for any CLI without an override. The resolver SHALL key on the leading binary token of the CLI command (so a CLI string carrying flags still matches its config entry).

Scenario: Unconfigured CLI uses the agnostic default

  • GIVEN a CLI with no [clis.<name>].submit_delay_ms configured (or no [clis.<name>] entry at all)
  • WHEN the settle delay is resolved
  • THEN it SHALL equal DEFAULT_SUBMIT_DELAY_MS

Scenario: Per-CLI override is honoured

  • GIVEN [clis.mycli].submit_delay_ms = 2500
  • WHEN the settle delay for mycli is resolved
  • THEN it SHALL be 2500

Scenario: Resolver keys on the binary, not the flags

  • GIVEN [clis.mycli].submit_delay_ms = 2500
  • WHEN the delay is resolved for the CLI command "mycli --some-flag"
  • THEN it SHALL be 2500 (the leading token mycli matched the config entry)

Scenario: No CLI name resolves to a hardcoded value

  • GIVEN an empty [clis] config
  • WHEN the delay is resolved for any CLI id (including names that might otherwise be special-cased)
  • THEN every CLI SHALL resolve to the same DEFAULT_SUBMIT_DELAY_MS — there is no built-in per-name table

Requirement: Profile applies to supervisor and agent panes

The split-send + resolved delay SHALL apply to every launched coding-agent pane, each resolved from that agent’s own CLI. No orchestrator pane exists in the session, so no boot block is submitted for an orchestrator CLI; the orchestrator’s CLI is resolved only for git-paw-initiated headless invocations, never for a pane.

Scenario: Coding-agent boot blocks are submitted with their own CLI’s delay

  • GIVEN a supervised session with two coding agents running different CLIs
  • WHEN each agent’s boot block is injected
  • THEN each SHALL be submitted via the split-send using that agent’s CLI’s resolved delay, so the agent begins work without a manual Enter

Scenario: No pane receives an orchestrator boot block

  • GIVEN a supervised session with [supervisor] cli set
  • WHEN the launch injects boot blocks
  • THEN no split-send SHALL target a pane with the orchestrator CLI’s resolved delay

Requirement: End-to-end boot registration

The system SHALL boot a fresh supervisor session such that all coding agents register with the broker without a human Enter, for any CLI given an adequate settle delay (default or configured). The agents’ boot publish goes through the bundled broker.sh helper at its stable path. Whether that call runs without a permission prompt is decided by the agent CLI’s own permission system: its resolved permission mode, or an allow rule for the helper’s exact path in that CLI’s settings. Registration SHALL NOT depend on a git-paw command classifier. When the CLI prompts for the helper, git-paw SHALL NOT answer the prompt; under --unattended the drive loop escalates it (supervisor-unattended-operation).

Scenario: All agents register unattended

  • GIVEN a fresh supervisor session with N agents and broker enabled, using a CLI whose settle delay is adequate and whose permission system lets the broker.sh helper run
  • WHEN the session launches
  • THEN within a bounded window the broker /status SHALL list all N coding agents (plus the supervisor) with no human Enter or permission approval required

Scenario: A prompted boot publish is escalated, not answered

  • GIVEN an --unattended session whose agent CLI raises a permission prompt for its broker.sh boot publish
  • WHEN the drive loop sweeps that pane
  • THEN the prompt SHALL be escalated
  • AND no keystroke SHALL be sent to that pane

Requirement: Clean the shell input line before the CLI-launch command

The system SHALL ensure a pane’s shell input line is clean before sending the CLI-launch command — by sending a clearing keystroke (e.g. C-u/C-c) and/or a leading newline — so a pending shell startup prompt (auto-update confirmation, MOTD, etc.) cannot swallow the leading character of the launch command and strand the pane at a bare shell.

Scenario: Launch keystroke is not corrupted by a startup prompt

  • GIVEN a pane whose interactive shell shows a startup prompt (e.g. [oh-my-zsh] Would you like to update? [Y/n]) at launch time
  • WHEN git-paw sends the CLI-launch command
  • THEN the pane SHALL clear the pending prompt first so the full launch command (not a keystroke-truncated variant like laude-oss) reaches the shell and the CLI starts

Requirement: Suppress shell startup prompts in the launched pane

The system SHALL suppress known shell auto-update / confirmation prompts in the pane it launches where it controls the pane environment (e.g. exporting DISABLE_AUTO_UPDATE=true or the equivalent), so such a prompt cannot fire mid-launch. The system SHALL NOT modify the user’s global shell configuration.

Scenario: Auto-update prompt suppressed for the launched pane

  • WHEN git-paw launches a CLI pane
  • THEN it SHALL set the pane environment so the shell’s auto-update prompt does not fire during launch, without editing the user’s ~/.zshrc or global oh-my-zsh settings

Requirement: Verify the CLI started and retry once

The system SHALL verify, within a bounded window after the launch keystroke, that the pane’s CLI actually started (the shell prompt was replaced by the CLI), and SHALL retry the launch once if the first attempt did not take.

Scenario: Failed launch is retried

  • GIVEN a pane where the first CLI-launch attempt did not start the CLI (the shell prompt is still present after the bounded window)
  • THEN git-paw SHALL send the launch command once more before giving up, so a single swallowed attempt does not permanently strand the pane

Requirement: README Supported AI CLIs table matches src/detect.rs

The README’s Supported AI CLIs table SHALL list every CLI binary in src/detect.rs’s known-CLI roster, and SHALL NOT list a binary that the roster does not auto-detect. The mdBook docs/src/supported-clis.md table SHALL carry the same binary set.

Scenario: README CLI table mentions opencode, cline, and droid

  • WHEN the README’s Supported AI CLIs table is inspected
  • THEN it contains the substring opencode
  • AND it contains the substring cline
  • AND it contains the substring droid

Test: tests/supported_clis_docs_sync.rs::readme_and_book_tables_match_known_cli_roster (opencode, cline, and droid are roster members, so the per-binary missing-cell assertion covers them)

Scenario: README and mdBook CLI tables list every roster binary and only those

  • WHEN the README’s Supported AI CLIs table and docs/src/supported-clis.md’s table are inspected
  • THEN each SHALL contain a `<binary>` cell for every binary in the known-CLI roster, including `cursor-agent`
  • AND neither SHALL contain a `cursor` binary cell

Test: tests/supported_clis_docs_sync.rs::readme_and_book_tables_match_known_cli_roster

Requirement: Per-CLI session-id and resume argument templates

The [clis.<name>] table SHALL accept two optional string templates, session_id_args (arguments that assign a session identifier at launch) and resume_args (arguments that resume an existing session), each containing the placeholder {{SESSION_ID}}. Resolution for a CLI SHALL consult, in order: (1) the [clis.<name>] override when present; (2) the built-in table; (3) no template. The built-in table SHALL contain, at minimum, claude → session_id_args = "--session-id {{SESSION_ID}}" and resume_args = "--resume {{SESSION_ID}}"; every CLI without a built-in row and without an override SHALL resolve to no template. A template that omits {{SESSION_ID}} SHALL be rejected at config load with an error naming the key. The built-in rows SHALL be verified against the CLI’s upstream documentation at implementation time.

Scenario: Claude resolves the built-in templates

  • WHEN templates are resolved for claude with no config override
  • THEN session_id_args SHALL render to --session-id <id> and resume_args to --resume <id> for the given id

Scenario: Override wins over the built-in row

  • GIVEN [clis.claude] defining resume_args = "--continue-session {{SESSION_ID}}"
  • WHEN the resume template is resolved for claude
  • THEN it SHALL render to --continue-session <id>

Scenario: Unknown CLI resolves no template

  • WHEN templates are resolved for my-agent with no [clis.my-agent] templates
  • THEN both session_id_args and resume_args SHALL resolve to none

Scenario: Template without the placeholder is rejected

  • GIVEN [clis.my-agent] defining session_id_args = "--session" (no {{SESSION_ID}})
  • WHEN the config is loaded
  • THEN loading SHALL fail with an error naming session_id_args

Requirement: Worker session id is assigned at launch

The system SHALL assign each worker a session identifier at launch: when the launch flow attaches a worker (initial start, spec-driven start, git paw add) and the worker’s CLI resolves a session_id_args template, it SHALL generate a fresh version-4-formatted UUID from OS-provided randomness, append the rendered session_id_args to the CLI launch command after any approval flags, and record the id in the worktree’s cli_session_id. When the CLI resolves no session_id_args template, the launch command SHALL be unchanged and cli_session_id SHALL remain None. The identifier SHALL be generated without adding a dependency.

Scenario: Claude worker is launched with an assigned id

  • GIVEN a worker on feat/x with cli = "claude" and no override
  • WHEN the launch command is built
  • THEN it SHALL contain --session-id <uuid> where <uuid> matches the v4 UUID shape
  • AND the saved WorktreeEntry for feat/x SHALL have cli_session_id = Some(<uuid>)

Scenario: Each worker gets a distinct id

  • GIVEN three Claude workers launched in one session
  • WHEN their cli_session_id values are compared
  • THEN all three SHALL differ

Scenario: CLI without an assignment template launches unchanged

  • GIVEN a worker with cli = "codex" and no [clis.codex].session_id_args
  • WHEN the launch command is built
  • THEN it SHALL be identical to the pre-change command
  • AND the saved entry’s cli_session_id SHALL be None

Scenario: Assignment follows approval flags

  • GIVEN a Claude worker at agent_approval = "full-auto"
  • WHEN the launch command is built
  • THEN --dangerously-skip-permissions SHALL precede --session-id <uuid> in the command

Requirement: Panes launch the configured command of a CLI

Every launch path that spawns an AI CLI into a tmux pane — the bare start, the spec-driven start, the supervisor pane, git paw add, and session recovery (worker and supervisor panes) — SHALL build the pane’s launch command from the CLI’s resolved [clis.<name>].command, not from the [clis.<name>] key. The leading whitespace-delimited token of the CLI identity is looked up in the merged [clis] table; when it names an entry, that token SHALL be replaced by the entry’s command and any trailing text SHALL be preserved verbatim. When it names no entry (a built-in detected CLI, or an explicit pass-through name), the identity SHALL be used unchanged.

The substituted command SHALL be emitted as exactly one shell word that the pane’s shell resolves to the configured string: it SHALL be single-quoted (with embedded single quotes escaped) whenever it contains any character outside the portable safe set A-Z a-z 0-9 _ - . / + , : @ %, so a path containing spaces or shell metacharacters is neither word-split nor expanded. Approval flags and session-id / resume arguments SHALL be appended after the substituted command exactly as they are appended after the bare CLI today.

Scenario: A custom CLI whose key differs from its command launches the command

  • GIVEN [clis.claude-variant] command = "claude"
  • WHEN a session is started with a worker on feat/x using CLI claude-variant
  • THEN the pane launch command SHALL begin with claude
  • AND it SHALL NOT contain claude-variant

Test: tests/cli_launch_identity_e2e.rs::worker_pane_launches_configured_command_not_key

Scenario: A CLI without a [clis] entry launches by name

  • GIVEN no [clis.codex] entry
  • WHEN a worker is attached with CLI codex
  • THEN the pane launch command SHALL be codex exactly as before this change

Test: config::tests::launch_command_falls_back_to_name_without_entry

Scenario: A command path containing a space is one shell word

  • GIVEN [clis.my-agent] command = "/opt/My Tools/my-agent" and an executable stub at that path
  • WHEN the launch command is built and run through a real POSIX shell
  • THEN the launch command SHALL begin with '/opt/My Tools/my-agent'
  • AND the shell SHALL execute the stub at that exact path (not /opt/My with an argument)

Test: config::tests::launch_command_quotes_path_with_space_as_one_shell_word

Scenario: Trailing text after the identity is preserved

  • GIVEN [clis.claude-oss] command = "/usr/local/bin/claude"
  • WHEN the launch command is built for the identity claude-oss --model opus
  • THEN it SHALL be /usr/local/bin/claude --model opus

Test: config::tests::launch_command_preserves_trailing_text

Scenario: Supervisor pane launches the configured command

  • GIVEN [supervisor] cli = "claude-oss" and [clis.claude-oss] command = "claude" with approval_args resolving a non-empty flag for the supervisor’s approval level
  • WHEN the supervisor session launches
  • THEN pane 0’s launch command SHALL be claude followed by that flag

Test: tests/cli_launch_identity_e2e.rs::supervisor_pane_launches_configured_command

Scenario: Recovery relaunches the configured command

  • GIVEN a saved session whose worktree entry has cli = "claude-oss" and cli_session_id = Some(<id>), and [clis.claude-oss] command = "claude" with a resolvable resume_args
  • WHEN the session is recovered
  • THEN the worker pane’s launch command SHALL begin with claude followed by the rendered resume arguments

Test: tests/cli_launch_identity_e2e.rs::recovery_launches_configured_command_with_resume_args

Requirement: The CLI identity, not the launch command, keys per-CLI resolution

The [clis.<name>] key (or the built-in / pass-through CLI name) SHALL remain the CLI’s identity. Approval-flag resolution, session-id and resume template resolution, prompt-profile resolution (including the readiness gate’s choice of readiness markers), settle-delay resolution, MCP-capability resolution, and the cli recorded in each saved WorktreeEntry SHALL all be keyed by that identity, never by the substituted launch command. The readiness gate SHALL relaunch the substituted launch command when a pane is still a bare shell.

Scenario: Saved worktree entry records the key

  • GIVEN [clis.claude-oss] command = "claude"
  • WHEN a session is started with a worker using CLI claude-oss
  • THEN the saved session’s WorktreeEntry.cli for that worker SHALL be claude-oss

Test: tests/cli_launch_identity_e2e.rs::saved_session_records_cli_key

Scenario: Per-CLI overrides resolve against the key

  • GIVEN [clis.claude-oss] command = "claude" with approval_args = { auto = "--paw-oss-sentinel" }
  • WHEN a session is started with a worker using CLI claude-oss at agent_approval = "auto"
  • THEN the pane launch command SHALL be claude --paw-oss-sentinel

Test: tests/cli_launch_identity_e2e.rs::worker_overrides_resolve_by_key_and_launch_command

Scenario: Readiness gate uses the identity’s prompt profile

  • GIVEN [clis.claude-oss] command = "claude" with a prompt_profile.readiness_markers override
  • WHEN the readiness gate polls the pane for CLI identity claude-oss
  • THEN it SHALL classify readiness using the override’s markers
  • AND a bare-shell relaunch SHALL send the launch command claude

Test: tmux::tests::gate_pane_resolves_profile_by_identity_and_relaunches_command

Requirement: Per-CLI headless invocation template

The [clis.<name>] table SHALL accept an optional string headless_args giving the arguments that make the CLI run one non-interactive turn reading its prompt from stdin. Resolution for a CLI SHALL consult, in order: (1) the [clis.<name>] override; (2) the built-in table, which SHALL contain at minimum claude → -p and codex → exec; (3) none. The built-in rows SHALL be verified against each CLI’s upstream documentation at implementation time. An empty override string SHALL be rejected at config load with an error naming the key.

Scenario: Claude and Codex resolve through the built-in table

  • WHEN headless_args is resolved for claude and for codex with no overrides
  • THEN the results SHALL be -p and exec respectively

Scenario: Override wins over the built-in row

  • GIVEN [clis.claude] defining headless_args = "-p --output-format text"
  • WHEN headless_args is resolved for claude
  • THEN the result SHALL be -p --output-format text

Scenario: Unknown CLI resolves none

  • WHEN headless_args is resolved for my-agent with no [clis.my-agent].headless_args
  • THEN the result SHALL be none

Scenario: Empty override is rejected

  • GIVEN [clis.my-agent] headless_args = ""
  • WHEN the config is loaded
  • THEN loading SHALL fail with an error naming headless_args

Git Operations

Purpose

Validate git repositories, list branches, create and remove worktrees, and derive worktree directory names. Provides the git plumbing that underpins parallel branch sessions.

Requirements

Requirement: Validate that a path is inside a git repository

The system SHALL confirm a path is inside a git repository and return the repository root.

Scenario: Path is inside a git repository

  • GIVEN a path inside an initialized git repository
  • WHEN validate_repo() is called
  • THEN it SHALL return Ok with the absolute path to the repository root

Test: git::tests::validate_repo_returns_root_inside_repo

Scenario: Path is not inside a git repository

  • GIVEN a path that is not inside any git repository
  • WHEN validate_repo() is called
  • THEN it SHALL return Err(PawError::NotAGitRepo)

Test: git::tests::validate_repo_returns_not_a_git_repo_outside

Requirement: List branches sorted and deduplicated

The system SHALL list all local and remote branches, deduplicated, sorted, with remote prefixes stripped and HEAD pointers excluded.

Scenario: Branches are returned sorted

  • GIVEN a repository with multiple branches
  • WHEN list_branches() is called
  • THEN it SHALL return branches sorted alphabetically

Test: git::tests::list_branches_returns_sorted_branches

Scenario: Local and remote branches are deduplicated with prefix stripping

  • GIVEN a repository cloned from a remote, with branches existing both locally and as remote-tracking refs
  • WHEN list_branches() is called
  • THEN each branch SHALL appear exactly once, with origin/ prefixes stripped

Test: git_integration::list_branches_strips_remote_prefix_and_deduplicates

Requirement: Derive project name from repository path

The system SHALL extract the project name from the final component of the repository root path, falling back to "project" for root paths.

Scenario: Normal repository path

  • GIVEN a repository at /Users/jie/code/git-paw
  • WHEN project_name() is called
  • THEN it SHALL return "git-paw"

Test: git::tests::project_name_from_path

Scenario: Root path fallback

  • GIVEN a repository at /
  • WHEN project_name() is called
  • THEN it SHALL return "project"

Test: git::tests::project_name_fallback_for_root

Requirement: Build worktree directory names

The system SHALL generate worktree directory names as <project>-<sanitized-branch>, replacing / with - and stripping unsafe characters.

Scenario: Branch with single slash

  • GIVEN project "git-paw" and branch "feature/auth-flow"
  • WHEN worktree_dir_name() is called
  • THEN it SHALL return "git-paw-feature-auth-flow"

Test: git::tests::worktree_dir_name_replaces_slash_with_dash

Scenario: Branch with multiple slashes

  • GIVEN project "git-paw" and branch "feat/auth/v2"
  • WHEN worktree_dir_name() is called
  • THEN it SHALL return "git-paw-feat-auth-v2"

Test: git::tests::worktree_dir_name_handles_multiple_slashes

Scenario: Branch with special characters

  • GIVEN project "my-proj" and branch "fix/issue#42"
  • WHEN worktree_dir_name() is called
  • THEN unsafe characters SHALL be stripped, returning "my-proj-fix-issue42"

Test: git::tests::worktree_dir_name_strips_special_chars

Scenario: Simple branch name

  • GIVEN project "git-paw" and branch "main"
  • WHEN worktree_dir_name() is called
  • THEN it SHALL return "git-paw-main"

Test: git::tests::worktree_dir_name_simple_branch

Requirement: Create worktrees as siblings of the repository

The system SHALL create git worktrees at a path resolved from the configured worktree_placement setting. When worktree_placement is "child", the target path SHALL be <repo_root>/.git-paw/worktrees/<branch-slug> and the .git-paw/worktrees/ directory SHALL be created if absent. When worktree_placement is "sibling" OR the setting is absent, the target path SHALL be <repo_parent>/<project>-<branch-slug> using the derived directory name convention, matching the v0.7.0 sibling layout. Only the resolved target path varies with placement; all other behaviour described below is unchanged.

The create_worktree function SHALL accept a rebase_onto_main: bool parameter. When rebase_onto_main is true AND the target branch already exists in the local repository, the function SHALL rebase the target branch onto the repository’s default branch (as returned by default_branch()) BEFORE performing the existence check for the worktree directory. The rebase SHALL be performed by invoking git rebase <default-branch> from the repository root. When the branch is already at or ahead of the default branch, git rebase exits zero with no rewrite; the function SHALL treat that as success.

If git rebase exits non-zero (rebase conflict or any other failure), the function SHALL invoke git rebase --abort in the repository root and return Err(PawError::WorktreeError("rebase onto main failed: <stderr>")). The branch SHALL be left at its pre-rebase HEAD after the abort; the function SHALL NOT proceed to the existence check or git worktree add when the rebase failed.

If rebase_onto_main is false, the function SHALL skip the rebase block entirely and behave identically to the post-worktree-resume-fix v0.5.0 contract (idempotent existence check followed by git worktree add).

If the target branch does NOT exist in the local repository at the time create_worktree is invoked, the function SHALL skip the rebase block regardless of the rebase_onto_main value and proceed to the existing git worktree add -b <branch> fallback, which creates the branch from current HEAD (already at the default branch tip by construction).

The system SHALL be idempotent in the resume case: when create_worktree() is invoked for a branch whose worktree already exists at the expected path AND is registered with git for that branch, the function SHALL return Ok(WorktreeCreation { path, branch_created: false }) without re-running git worktree add. Idempotency is verified by parsing git worktree list --porcelain output and matching both the worktree path and the refs/heads/<branch> line. When rebase_onto_main is true, the rebase block runs BEFORE this idempotency check, so a surviving worktree’s branch ref SHALL be updated to the rebased SHA before the function returns.

If the expected path exists on disk but is NOT a git worktree registered for the specified branch (e.g. an unrelated directory, or a worktree for a different branch), the function SHALL fall through to the existing git worktree add call so the user sees the actionable fatal: '<path>' already exists error from git directly.

Scenario: Worktree created at correct path

  • GIVEN a repository with a branch feature/test
  • WHEN create_worktree() is called with rebase_onto_main = false
  • THEN a worktree SHALL be created at ../<project>-feature-test containing the repository files

Scenario: Worktree created under child placement

  • GIVEN a repository whose effective config has worktree_placement = "child" and a branch feature/test
  • WHEN create_worktree() is called with rebase_onto_main = false
  • THEN a worktree SHALL be created at <repo_root>/.git-paw/worktrees/feature-test containing the repository files

Scenario: Worktree created under sibling placement

  • GIVEN a repository whose effective config has worktree_placement = "sibling" and a branch feature/test
  • WHEN create_worktree() is called with rebase_onto_main = false
  • THEN a worktree SHALL be created at ../<project>-feature-test

Scenario: Worktree created under absent placement defaults to sibling

  • GIVEN a repository whose effective config has no worktree_placement field and a branch feature/test
  • WHEN create_worktree() is called with rebase_onto_main = false
  • THEN a worktree SHALL be created at ../<project>-feature-test

Scenario: Creating worktree for currently checked-out branch fails

  • GIVEN the current branch is checked out in the main repo
  • WHEN create_worktree() is called for that branch
  • THEN it SHALL return Err(PawError::WorktreeError)

Scenario: Resume of an existing worktree returns success without re-running git worktree add

  • GIVEN a worktree already exists at ../<project>-feature-test for branch feature/test from a prior session
  • AND rebase_onto_main = false is passed
  • WHEN create_worktree() is called for feature/test
  • THEN the function SHALL return Ok(WorktreeCreation { path: <expected>, branch_created: false })
  • AND the existing worktree SHALL remain unchanged (HEAD SHA, working tree files, and uncommitted changes preserved)
  • AND no second git worktree add SHALL be executed

Scenario: Path exists but is not a git worktree

  • GIVEN the expected worktree path ../<project>-feature-test exists as a regular directory (not registered with git)
  • WHEN create_worktree() is called for branch feature/test
  • THEN the function SHALL return Err(PawError::WorktreeError) whose message contains the substring already exists

Scenario: Path exists as a worktree but for a different branch

  • GIVEN a worktree already exists at ../<project>-feature-test but registered for branch feature/other
  • WHEN create_worktree() is called for branch feature/test
  • THEN the function SHALL fall through to git worktree add and return Err(PawError::WorktreeError) (preserving the v0.4 contract for unrelated path collisions)

Scenario: Rebase-on-resume happy path advances branch onto current main

  • GIVEN the default branch main has advanced by N commits since branch feat/example was created
  • AND feat/example exists locally and is behind main by exactly N commits with no diverging commits of its own
  • WHEN create_worktree() is called for feat/example with rebase_onto_main = true
  • THEN the function SHALL invoke git rebase <main> against feat/example in the repository root
  • AND the rebase SHALL succeed
  • AND feat/example’s HEAD SHA after the call SHALL be reachable from main (i.e. include the N new commits)
  • AND the function SHALL return Ok(WorktreeCreation { path: <expected>, branch_created: false })

Scenario: Rebase skipped when branch is already up-to-date

  • GIVEN branch feat/example exists locally and is at the same SHA as main (no divergence)
  • WHEN create_worktree() is called for feat/example with rebase_onto_main = true
  • THEN git rebase <main> SHALL be invoked and SHALL exit zero with no rewrite
  • AND feat/example’s HEAD SHA SHALL be unchanged
  • AND the function SHALL return Ok(WorktreeCreation { path: <expected>, branch_created: false })
  • AND no error SHALL be returned

Scenario: Rebase conflict aborts cleanly and surfaces error

  • GIVEN branch feat/example and main both modify the same line of the same file with different content
  • WHEN create_worktree() is called for feat/example with rebase_onto_main = true
  • THEN git rebase <main> SHALL be invoked and SHALL exit non-zero with conflict markers
  • AND the function SHALL invoke git rebase --abort
  • AND feat/example’s HEAD SHA after the call SHALL equal its pre-call HEAD SHA
  • AND no .git/rebase-merge or .git/rebase-apply directory SHALL remain in the repository
  • AND the function SHALL return Err(PawError::WorktreeError(msg)) where msg contains the substring rebase onto main failed
  • AND the worktree directory at ../<project>-feat-example SHALL NOT have been created (or, if it existed from a prior session, SHALL be unchanged)

Scenario: rebase_onto_main = false preserves v0.5 no-rebase behaviour

  • GIVEN branch feat/example exists locally and is behind main by 3 commits
  • WHEN create_worktree() is called for feat/example with rebase_onto_main = false
  • THEN no git rebase invocation SHALL occur
  • AND feat/example’s HEAD SHA after the call SHALL equal its pre-call HEAD SHA
  • AND the function SHALL proceed to the existence check and (if applicable) git worktree add, matching the post-worktree-resume-fix v0.5.0 contract exactly

Scenario: New branch creation skips rebase regardless of flag

  • GIVEN branch feat/new does NOT exist in the local repository
  • AND rebase_onto_main = true is passed
  • WHEN create_worktree() is called for feat/new
  • THEN no git rebase invocation SHALL occur (there is nothing to rebase)
  • AND the function SHALL invoke git worktree add -b feat/new <path> to create the branch from current HEAD
  • AND the function SHALL return Ok(WorktreeCreation { path, branch_created: true })

Requirement: Remove worktrees and prune stale entries

The system SHALL force-remove a worktree and prune stale git worktree metadata. remove_worktree SHALL pass --force to git worktree remove so a worktree containing uncommitted modifications, untracked files, or both is still deleted; the function is only called from the destructive purge path, where leaving worktree directories on disk after the user already opted into a destructive operation is the wrong behaviour.

Scenario: Worktree fully cleaned up after removal

  • GIVEN an existing worktree
  • WHEN remove_worktree() is called
  • THEN the directory SHALL be deleted and git SHALL no longer track it

Test: git::tests::remove_worktree_cleans_up_fully

Scenario: Dirty worktree is force-removed

  • GIVEN an existing worktree containing both a modified tracked file and an untracked file
  • WHEN remove_worktree() is called
  • THEN the call SHALL succeed
  • AND the worktree directory SHALL be deleted from disk
  • AND git SHALL no longer track the worktree

Test: git_integration::remove_worktree_force_removes_dirty_worktree

Requirement: Repository validation SHALL work against real git repos

Scenario: Succeeds inside a real git repo

  • GIVEN a temporary git repository with an initial commit
  • WHEN validate_repo() is called
  • THEN it SHALL return the canonicalized repo root

Test: git_integration::validate_repo_succeeds_inside_git_repo

Scenario: Fails outside a git repo

  • GIVEN a temporary directory that is not a git repo
  • WHEN validate_repo() is called
  • THEN it SHALL return an error

Test: git_integration::validate_repo_fails_outside_git_repo

Requirement: Branch listing SHALL work against real git repos

Scenario: Lists created branches

  • GIVEN a repo with branches feature/auth and fix/db
  • WHEN list_branches() is called
  • THEN both branches SHALL appear in the result

Test: git_integration::list_branches_includes_created_branches

Scenario: Branches are sorted

  • GIVEN branches created in non-alphabetical order
  • WHEN list_branches() is called
  • THEN results SHALL be alphabetically sorted

Test: git_integration::list_branches_returns_sorted

Scenario: Deduplicates local and remote

  • GIVEN a repository with a default branch
  • WHEN list_branches() is called
  • THEN each branch SHALL appear exactly once

Test: git_integration::list_branches_deduplicates_local_and_remote

Requirement: Worktree lifecycle SHALL work against real git repos

Scenario: Create and remove worktree

  • GIVEN a branch in a temporary repo
  • WHEN create_worktree() then remove_worktree() are called
  • THEN the worktree SHALL exist after creation and be gone after removal

Test: git_integration::create_and_remove_worktree

Scenario: Worktree placed as sibling of repo

  • GIVEN a repo at <sandbox>/test-repo/
  • WHEN create_worktree() is called
  • THEN the worktree SHALL be in the same parent directory

Test: git_integration::worktree_placed_as_sibling_of_repo

Scenario: Fails for checked-out branch

  • GIVEN the currently checked-out branch
  • WHEN create_worktree() is called for it
  • THEN it SHALL fail

Test: git_integration::create_worktree_fails_for_checked_out_branch

Requirement: Directory naming SHALL be correct in integration tests

Scenario: Project name from real repo path

  • GIVEN a repo at .../test-repo/
  • WHEN project_name() is called
  • THEN it SHALL return "test-repo"

Test: git_integration::project_name_from_repo_path

Scenario: Worktree dir name replaces slashes

  • WHEN worktree_dir_name("my-project", "feature/auth-flow") is called
  • THEN it SHALL return "my-project-feature-auth-flow"

Test: git_integration::worktree_dir_name_replaces_slashes

Scenario: Worktree dir name strips unsafe chars

  • WHEN worktree_dir_name("proj", "feat/special@chars!") is called
  • THEN @ and ! SHALL be stripped

Test: git_integration::worktree_dir_name_strips_unsafe_chars

Scenario: Worktree dir name handles nested slashes

  • WHEN worktree_dir_name("proj", "feature/deep/nested/branch") is called
  • THEN it SHALL return "proj-feature-deep-nested-branch"

Test: git_integration::worktree_dir_name_handles_nested_slashes

Requirement: Worktree creation produces a usable worktree path

The create_worktree function SHALL create a git worktree for the given branch and return its path. Callers MAY perform post-creation setup (such as AGENTS.md generation) using the returned path.

Scenario: Worktree created at correct path

  • GIVEN a git repo and a branch name
  • WHEN create_worktree() is called
  • THEN it SHALL return the path to the new worktree as a sibling of the repo directory

Test: git::tests::create_worktree_at_correct_path

Scenario: Worktree creation fails for checked-out branch

  • GIVEN a branch that is currently checked out
  • WHEN create_worktree() is called
  • THEN it SHALL return a PawError::WorktreeError

Test: git::tests::create_worktree_errors_on_checked_out_branch

Requirement: Default-branch resolution is single-source and tolerant of a missing origin/HEAD

The system SHALL resolve the repository’s default branch through a single resolver used by every launch path (initial start, resume, add, and the rebase step), so no two paths can disagree. When refs/remotes/origin/HEAD is not set — e.g. a repository with local commits but no configured remote HEAD — the resolver SHALL fall back deterministically (a local main, else a local master, else the checked-out branch via git symbolic-ref --short HEAD) rather than aborting. A launch that succeeded initially SHALL therefore also succeed on resume in the same repository state.

Scenario: Resolution succeeds without origin/HEAD

  • GIVEN a repository with local commits and no refs/remotes/origin/HEAD set
  • WHEN the default branch is resolved
  • THEN it SHALL return a valid local default branch (via the fallback) rather than aborting with an error

Scenario: First launch and resume resolve identically

  • GIVEN a repository whose first git paw start resolved a default branch
  • WHEN the session is later resumed (a second git paw start) in the same repository state
  • THEN the resume SHALL resolve the same default branch through the same resolver and SHALL NOT fail where the first launch succeeded

Scenario: origin/HEAD is still honoured when present

  • GIVEN a repository with refs/remotes/origin/HEAD pointing at origin/main
  • WHEN the default branch is resolved
  • THEN it SHALL return main (the configured remote HEAD), unchanged from prior behaviour

Requirement: Mid-session rebase of a live worker branch

The system SHALL provide a rebase entry point that rebases a named branch onto the repository’s default branch while that branch is checked out in a live worktree, separate from create_worktree’s creation-time rebase_onto_main path. The entry point SHALL run the rebase inside the worktree where the branch is currently checked out. On a non-zero git rebase exit it SHALL invoke git rebase --abort and return an error, leaving the branch at its pre-rebase HEAD. When the branch is already at or ahead of the default branch, git rebase exits zero with no rewrite and the entry point SHALL treat that as success.

The creation-time contract of create_worktree (including its rebase_onto_main parameter, its ordering relative to the existence check, and its idempotent resume behaviour) SHALL be unchanged by this addition.

Scenario: Rebase runs in the worktree holding the branch

  • GIVEN a branch checked out in a live worktree
  • WHEN the mid-session rebase entry point is invoked for that branch
  • THEN the rebase SHALL be run inside that worktree

Scenario: Failed mid-session rebase aborts and restores

  • GIVEN a mid-session rebase that exits non-zero
  • WHEN the failure is detected
  • THEN git rebase --abort SHALL be invoked
  • AND the branch SHALL be left at its pre-rebase HEAD
  • AND an error SHALL be returned

Scenario: Branch already current is a successful no-op

  • GIVEN a branch already at or ahead of the default branch
  • WHEN the mid-session rebase entry point is invoked for that branch
  • THEN it SHALL return success
  • AND the branch SHALL NOT be rewritten

Scenario: Creation-time rebase contract is unaffected

  • WHEN create_worktree() is called with rebase_onto_main = true for an existing branch
  • THEN its behaviour SHALL be identical to the behaviour before the mid-session entry point was added

Tmux Orchestration

Purpose

Orchestrate tmux sessions with multiple panes, each running an AI CLI in a git worktree. Uses a builder pattern for testability and dry-run support, with configurable mouse mode and automatic tiled layout.

Requirements

Requirement: Check tmux availability with actionable error

The system SHALL verify tmux is installed on PATH and provide install instructions if missing.

Scenario: tmux is present on PATH

  • GIVEN tmux is installed
  • WHEN ensure_tmux_installed() is called
  • THEN it SHALL return Ok(())

Test: tmux::tests::ensure_tmux_installed_succeeds_when_present

Requirement: Create named sessions derived from project name

The system SHALL name tmux sessions as paw-<project_name>.

Scenario: Session named after project

  • GIVEN project name "my-project"
  • WHEN a session is built
  • THEN the session name SHALL be "paw-my-project"

Test: tmux::tests::session_is_named_after_project

Scenario: Session creation command uses correct name

  • GIVEN project name "app"
  • WHEN a session is built
  • THEN the commands SHALL include new-session with paw-app

Test: tmux::tests::session_creation_command_uses_session_name

Requirement: Session creation passes explicit dimensions for headless environments

Both session builders (the basic TmuxSessionBuilder and build_supervisor_session) SHALL emit tmux new-session with -x 200 -y 50 so the session has explicit window dimensions when created without an attached client. The user’s real terminal resizes the session on tmux attach.

Without explicit dimensions, tmux on Linux (apt-shipped tmux 3.4+) errors with size missing on subsequent split-window operations because the layout engine can’t resolve percentages without a known window size.

Additionally, immediately after the new-session command, both builders SHALL emit tmux set-option -g default-size 200x50. This pins the global default-size so subsequent split-window / resize-pane operations have a fallback size context even when no client is attached. macOS tmux honours per-session -x/-y for splits; Linux tmux 3.4+ requires the server-level fallback.

Scenario: Basic session passes -x/-y to new-session

  • GIVEN any TmuxSessionBuilder with one or more panes
  • WHEN command_strings() is invoked
  • THEN the first command in the output SHALL be a new-session containing the substrings -x 200 and -y 50

Scenario: Basic session sets global default-size after new-session

  • GIVEN any TmuxSessionBuilder with one or more panes
  • WHEN command_strings() is invoked
  • THEN the second command in the output SHALL be set-option -g default-size 200x50

Scenario: Supervisor session passes -x/-y to new-session

  • GIVEN a build_supervisor_session invocation with supervisor + dashboard + N agent panes
  • WHEN the command list is built
  • THEN the first emitted command SHALL be a new-session containing -x 200 and -y 50

Scenario: Supervisor session sets global default-size after new-session

  • GIVEN a build_supervisor_session invocation
  • WHEN the command list is built
  • THEN the second emitted command SHALL be set-option -g default-size 200x50

Scenario: Headless supervisor launch succeeds under socket isolation

  • GIVEN a cold tmux server (no pre-existing client, isolated socket via TMUX_TMPDIR)
  • WHEN git paw start --supervisor --branches a,b is invoked
  • THEN the supervisor session SHALL launch successfully
  • AND stderr SHALL NOT contain Tmux error: size missing
  • AND the exit code SHALL be 0

Test: tmux::tests::built_session_can_be_executed_and_killed, tmux::tests::supervisor_top_row_split_50_50, and the cli_supervisor_no_config::supervisor_without_section_uses_default_when_default_cli_present integration test.

Scenario: Supervisor splits use -l <N>% syntax (tmux 3.1+) not deprecated -p <N>

  • GIVEN a build_supervisor_session invocation with any agent count
  • WHEN the command list is built
  • THEN every emitted split-window command SHALL use the -l <N>% length flag, NOT the deprecated -p <N> percentage flag

Rationale: Linux apt-tmux 3.4 (Ubuntu 24.04) emits cmd-split-window.c: "size missing" when -p cannot resolve the percentage against the parent pane’s laid-out size — on a detached server with no attached client the pane geometry is unresolved. -l <N>% resolves against the window’s -y dimension (set by new-session -x 200 -y 50) instead, which is well-defined in headless mode. macOS tmux 3.6a tolerates either form.

Test: tmux::tests::supervisor_splits_use_l_percent_not_p.

Requirement: Session name override via builder

The builder SHALL support overriding the default paw-<project> session name with a custom name.

Scenario: Override replaces default name

  • GIVEN session_name("custom-session-name") is set on the builder
  • WHEN the session is built
  • THEN the session name SHALL be "custom-session-name" and commands SHALL target it

Test: tmux::tests::session_name_override_replaces_default

Requirement: Dynamic pane count matches input

The number of panes in the session’s window 0 SHALL match the number of PaneSpec entries added via the builder. When broker is enabled, the builder SHALL receive an additional PaneSpec for the dashboard in pane 0, increasing the total pane count by one. A detached service window (the broker host’s broker window) is not part of this count.

Scenario: Two agent panes plus dashboard created

  • GIVEN broker is enabled and 2 agent pane specs are added
  • WHEN the session is built
  • THEN exactly 3 panes SHALL exist in window 0: pane 0 (dashboard) + panes 1-2 (agents)

Scenario: Two panes without broker

  • GIVEN broker is disabled and 2 pane specs are added
  • WHEN the session is built
  • THEN exactly 2 panes SHALL exist in window 0 (same as v0.2.0)

Requirement: Correct commands sent to each pane

Each pane SHALL receive a cd <worktree> && <cli_command> command targeting the correct pane index.

Scenario: Each pane receives cd and CLI command

  • GIVEN two panes with different worktrees and CLIs
  • WHEN the session is built
  • THEN each send-keys command SHALL contain cd <worktree> && <cli>

Test: tmux::tests::each_pane_receives_cd_and_cli_command

Scenario: Commands are submitted with Enter

  • GIVEN a pane spec
  • WHEN the session is built
  • THEN the send-keys command SHALL include Enter

Test: tmux::tests::pane_commands_are_submitted_with_enter

Scenario: Each pane targets a distinct index

  • GIVEN 3 panes
  • WHEN the session is built
  • THEN send-keys SHALL target :0.0, :0.1, and :0.2 respectively

Test: tmux::tests::each_pane_targets_a_distinct_pane_index

Requirement: Pane titles show branch and CLI

Each pane SHALL be titled with <branch> → <cli_command> and border status configured.

Scenario: Pane titles contain branch and CLI

  • GIVEN panes with branches and CLIs
  • WHEN the session is built
  • THEN select-pane -T commands SHALL set titles like "feat/auth → claude"

Test: tmux::tests::each_pane_is_titled_with_branch_and_cli

Scenario: Pane border status configured

  • GIVEN any session
  • WHEN the session is built
  • THEN pane-border-status SHALL be set to top and pane-border-format SHALL use #{pane_title}

Test: tmux::tests::pane_border_status_is_configured

Requirement: Configurable mouse mode per session

Mouse mode SHALL be enabled by default and be disableable via the builder.

Scenario: Mouse mode enabled by default

  • GIVEN no explicit mouse mode setting
  • WHEN the session is built
  • THEN a mouse on command SHALL be emitted

Test: tmux::tests::mouse_mode_enabled_by_default

Scenario: Mouse mode can be disabled

  • GIVEN mouse_mode(false) is set on the builder
  • WHEN the session is built
  • THEN no mouse on command SHALL be emitted

Test: tmux::tests::mouse_mode_can_be_disabled

Requirement: Attach to a tmux session

The system SHALL attach the current terminal to a named tmux session, returning an error if the session does not exist.

Scenario: Attaching to a nonexistent session fails

  • GIVEN no tmux session with the given name exists
  • WHEN attach() is called
  • THEN it SHALL return an error

Test: e2e_tests::attach_fails_for_nonexistent_session

Requirement: Session liveness check

The system SHALL check whether a tmux session is alive by name.

Scenario: Nonexistent session reports not alive

  • GIVEN no tmux session with the queried name exists
  • WHEN is_session_alive() is called
  • THEN it SHALL return false

Test: tmux::tests::is_session_alive_returns_false_for_nonexistent

Requirement: Session lifecycle management

The system SHALL support creating, checking, and killing tmux sessions.

Scenario: Full create-check-kill lifecycle

  • GIVEN a tmux session is created
  • WHEN is_session_alive() is called, then kill_session(), then is_session_alive() again
  • THEN it SHALL be alive after creation and not alive after killing

Test: tmux::tests::session_lifecycle_create_check_kill

Scenario: Built session can be executed and killed

  • GIVEN a session built via TmuxSessionBuilder
  • WHEN execute() is called
  • THEN the tmux session SHALL be alive, and after kill_session() it SHALL be gone

Test: tmux::tests::built_session_can_be_executed_and_killed

Requirement: Session name collision resolution

The system SHALL resolve name collisions by appending -2, -3, etc. to the base session name.

Scenario: No collision returns base name

  • GIVEN no existing session with the base name
  • WHEN resolve_session_name() is called
  • THEN it SHALL return paw-<project_name>

Test: tmux::tests::resolve_session_name_returns_base_when_no_collision

Scenario: Collision appends numeric suffix

  • GIVEN a session with the base name already exists
  • WHEN resolve_session_name() is called
  • THEN it SHALL return paw-<project_name>-2

Test: tmux::tests::resolve_session_name_appends_suffix_on_collision

Requirement: Tmux session lifecycle SHALL work against a real tmux server

Scenario: Create and kill session lifecycle

  • GIVEN a tmux session is created via the builder
  • WHEN execute(), is_session_alive(), and kill_session() are called
  • THEN the session SHALL be alive after creation and gone after killing

Test: e2e_tests::tmux_session_create_and_kill_lifecycle

Scenario: Five panes with different CLIs

  • GIVEN 5 pane specs with different branch/CLI pairs
  • WHEN the session is executed
  • THEN tmux SHALL have 5 panes with correct titles

Test: e2e_tests::tmux_session_with_five_panes_and_different_clis

Scenario: Mouse mode enabled by default against live tmux

  • GIVEN a session built with default settings
  • WHEN tmux show-option is queried
  • THEN mouse SHALL be “on”

Test: e2e_tests::tmux_mouse_mode_enabled_by_default

Scenario: is_session_alive returns false for nonexistent (e2e)

  • GIVEN no session with the queried name
  • WHEN is_session_alive() is called
  • THEN it SHALL return false

Test: e2e_tests::tmux_is_session_alive_returns_false_for_nonexistent

Scenario: Attach succeeds for live session

  • GIVEN a live tmux session
  • WHEN attach() is called and the client is detached programmatically
  • THEN the function SHALL execute without panic

Test: e2e_tests::attach_succeeds_for_live_session

Requirement: E2E commands SHALL behave correctly against real repos

Scenario: Dry run shows session plan

  • GIVEN a git repo with branches and --dry-run --cli echo --branches feat/a,feat/b
  • WHEN the binary runs
  • THEN stdout SHALL contain “Dry run”, branch names, and the CLI name

Test: e2e_tests::dry_run_with_flags_shows_plan

Scenario: Preset not found returns error

  • GIVEN a git repo with no presets configured
  • WHEN start --preset nonexistent is run
  • THEN it SHALL fail with stderr mentioning “not found”

Test: e2e_tests::preset_not_found_returns_error

Scenario: Stop with no session

  • GIVEN a git repo with no active session
  • WHEN stop is run
  • THEN it SHALL succeed with stdout mentioning “No active session”

Test: e2e_tests::stop_with_no_session

Scenario: Purge with no session

  • GIVEN a git repo with no active session
  • WHEN purge --force is run
  • THEN it SHALL succeed with stdout mentioning “No session to purge”

Test: e2e_tests::purge_with_no_session

Scenario: Status with no session

  • GIVEN a git repo with no active session
  • WHEN status is run
  • THEN it SHALL succeed with stdout mentioning “No session”

Test: e2e_tests::status_with_no_session

Scenario: Stop from non-git directory fails

  • GIVEN a directory that is not a git repository
  • WHEN stop is run
  • THEN it SHALL fail with “Not a git repository”

Test: e2e_tests::stop_from_non_git_dir_fails

Scenario: Status from non-git directory fails

  • GIVEN a directory that is not a git repository
  • WHEN status is run
  • THEN it SHALL fail with “Not a git repository”

Test: e2e_tests::status_from_non_git_dir_fails

Requirement: TmuxSession supports pipe-pane command

The TmuxSession builder SHALL support queuing a pipe-pane command to attach logging to a specific pane.

Scenario: pipe-pane queued in builder

  • WHEN pipe_pane() is called on a TmuxSession with a pane target and log path
  • THEN the command queue SHALL contain a pipe-pane -o -t <pane> "cat >> <path>" entry

Scenario: pipe-pane in dry-run output

  • WHEN a session with pipe_pane() is rendered as dry-run
  • THEN the output SHALL include the tmux pipe-pane command string

Scenario: pipe-pane executed after pane creation

  • WHEN the session commands are executed
  • THEN the pipe-pane command SHALL execute after the corresponding split-window and send-keys commands for that pane

Requirement: TmuxSession supports session-level environment variables

The TmuxSessionBuilder SHALL support setting session-level environment variables via a set_environment(key, value) method. The resulting set-environment -t <session> <key> <value> command SHALL be emitted before any send-keys commands to ensure all panes inherit the variable.

Scenario: set_environment emits correct tmux command

  • GIVEN set_environment("GIT_PAW_BROKER_URL", "http://127.0.0.1:9119") is called on the builder
  • WHEN the session is built
  • THEN the command queue SHALL contain set-environment -t <session> GIT_PAW_BROKER_URL http://127.0.0.1:9119

Scenario: set_environment appears before send-keys

  • GIVEN a builder with environment variables and pane specs
  • WHEN the session is built
  • THEN all set-environment commands SHALL appear before any send-keys commands in the command queue

Scenario: set_environment in dry-run output

  • GIVEN a builder with set_environment called
  • WHEN the session is rendered as dry-run
  • THEN the output SHALL include the tmux set-environment command string

Scenario: Multiple environment variables

  • GIVEN set_environment("A", "1") and set_environment("B", "2") are both called
  • WHEN the session is built
  • THEN both set-environment commands SHALL appear in the command queue

Requirement: Supervisor-mode pane layout

When the tmux session is built for a supervised session (per the supervisor-launch capability), the system SHALL produce a layout with these structural properties:

  • Top row: pane 0 (dashboard) spanning the full window width.

  • Agent grid below: dynamically sized by agent count, with up to 5 columns per row. The agent grid is a sequence of horizontal rows; each row holds up to 5 agent panes side-by-side.

  • Pane indices: pane 0 = dashboard; panes 1..N = coding agents in row-major order (left-to-right, top-to-bottom). No pane runs an orchestrator CLI.

  • Vertical proportions: the dashboard row SHALL occupy 50% of the session height regardless of agent count, and the agent rows SHALL share the remaining 50% evenly — each agent row occupying 50 / agent_rows percent. Adding agents SHALL shrink the agent rows, never the dashboard row.

    Agent rowsDashboard row heightEach agent row height
    1 (1-5 agents)50%50%
    2 (6-10 agents)50%25% each
    3 (11-15 agents)50%16.7% each
    4 (16-20 agents)50%12.5% each
    5 (21-25 agents)50%10% each
  • Equal-width agent panes within a row: every agent pane in the same agent row SHALL be rendered at equal width (within a one-column rounding tolerance). Because agents are added to a row by successive tmux split-window -h — and each -h split halves the current pane — a row populated by raw splits renders unequal widths, so the system SHALL rebalance each agent row to equal width after its panes are created, by applying tmux select-layout even-horizontal scoped to that row’s agent panes OR by issuing tmux resize-pane to set each agent pane to 100 / agents_in_row percent of the row’s width. The rebalance SHALL NOT alter the dashboard row’s full width nor the vertical height proportions described above.

  • Minimum usable pane width: the layout SHALL keep agent panes wide enough to be usable up to the agent cap; at the maximum 5 columns per row the equal-width target is 20% of the window width per pane. The system SHALL NOT produce agent rows with more than 5 panes (SUPERVISOR_AGENTS_PER_ROW).

  • Hard cap: 25 agents per session. Above 25, the system SHALL reject the launch with a clear “split into multiple sessions” error before any tmux command runs.

The layout SHALL be built using tmux split-window -h and -v with explicit percentages, then enforced via tmux resize-pane -y <pct> for the height proportions and the per-row equal-width rebalance described above. select-layout tiled (or other whole-window auto-layouts) SHALL NOT be used for the supervised layout because they do not preserve the predictable pane-index ordering this layout relies on; select-layout even-horizontal MAY be used only when scoped so it rebalances a single agent row’s widths without reordering pane indices or disturbing the dashboard row. No swap-pane SHALL be emitted.

Scenario: 5-agent supervised layout has 1 agent row

  • GIVEN a supervised session with 5 agent branches
  • WHEN the tmux layout is built
  • THEN pane 0 SHALL be the dashboard spanning the full window width
  • AND panes 1-5 SHALL be agents arranged in a single row below the dashboard
  • AND the dashboard row’s height SHALL be 50% and the agent row’s height SHALL be 50%

Scenario: 10-agent supervised layout has 2 agent rows

  • GIVEN a supervised session with 10 agent branches
  • WHEN the tmux layout is built
  • THEN total row count SHALL be 3 (1 dashboard + 2 agent rows)
  • AND the dashboard row’s height SHALL be 50%
  • AND each agent row’s height SHALL be 25%
  • AND the first agent row SHALL contain panes 1-5, the second agent row SHALL contain panes 6-10

Scenario: 20-agent supervised layout has 4 agent rows

  • GIVEN a supervised session with 20 agent branches
  • WHEN the tmux layout is built
  • THEN total row count SHALL be 5 (1 dashboard + 4 agent rows)
  • AND the dashboard row’s height SHALL be 50%
  • AND each of the 4 agent rows’ height SHALL be 12.5%

Scenario: 26-agent supervised session is rejected

  • GIVEN 26 agent branches resolved
  • WHEN the supervised launch flow runs
  • THEN the launch SHALL be rejected with a PawError stating the requested count (26), the maximum (25), and a hint suggesting --branches <subset>
  • AND no tmux session SHALL be created

Scenario: Pane indices match row-major order

  • GIVEN a supervised session with 7 agents
  • WHEN the tmux layout is built
  • THEN pane 1 SHALL be the first agent (top-left of the agent grid)
  • AND pane 5 SHALL be the fifth agent (top-right of the first agent row)
  • AND pane 6 SHALL be the sixth agent (start of the second agent row)

Scenario: Three agents in a row render at equal width

  • GIVEN a supervised session with 3 agent branches (a single agent row)
  • WHEN the tmux layout is built and applied to a live tmux window
  • THEN the three agent panes SHALL each occupy approximately one third of the window width (within a one-column rounding tolerance)

Scenario: Row rebalance leaves the dashboard row full width

  • GIVEN a supervised session whose agent row is rebalanced to equal width
  • WHEN the layout is applied to a live tmux window
  • THEN pane 0 (dashboard) SHALL remain at the full window width
  • AND the vertical height proportions SHALL be unchanged: the dashboard row at 50% and each agent row at 50 / agent_rows percent

Scenario: Full agent row stays at five equal-width columns

  • GIVEN a supervised session with 5 agents in a single row
  • WHEN the layout is applied to a live tmux window
  • THEN each of the 5 agent panes SHALL occupy approximately 20% of the window width
  • AND no agent row SHALL contain more than 5 panes

Requirement: Launch-readiness gate before boot-block injection

The system SHALL verify that a pane’s CLI process has actually started and reached an interactive ready state before injecting any boot block, framing prompt, or task prompt into that pane, rather than relying solely on a fixed wall-clock sleep. The system SHALL poll the pane (via tmux capture-pane) for a CLI-readiness marker — content that distinguishes the launched CLI’s interactive UI from a bare shell prompt — up to a bounded timeout. The boot block SHALL NOT be injected while the pane is still a bare shell, because the shell would interpret the multi-line boot block line-by-line as failing commands (the v0.8.0 G1 dogfood failure).

If the readiness timeout elapses while the pane is still a bare shell, the system SHALL relaunch the CLI command into that pane (re-clearing the input line first) and poll again, up to a bounded number of relaunch attempts. The readiness gate and its timeout/attempt budget SHALL apply uniformly to EVERY launch path that injects text into an agent pane: bare start (which then pre-fills without submitting), spec-driven start, supervised start, git paw add, and the submission of a held boot prompt when a paused session is restarted. A pane the gate reports as blocked on an unanswered first-run dialog SHALL NOT receive the injected text and SHALL fail that launch path loudly (per “Launch gate handles a first-run permission/trust acceptance dialog”); the one exception is restart from pause, which SHALL keep that pane’s held prompt for the next restart, report the blocked pane on stderr, and continue restarting the session. On restart from pause a held prompt SHALL be submitted only to the pane resolved live from its agent’s worktree: when no live pane resolves, the prompt SHALL stay held and the agent SHALL be reported on stderr, and the prompt SHALL NOT be submitted at a computed pane index.

The readiness gate SHALL be conservative: a CLI that the gate cannot positively classify within the budget (e.g. an unrecognised custom CLI whose UI the marker heuristic does not match) SHALL fall back to injecting the boot block after the budget elapses rather than failing the launch, so launch behaviour is never worse than the previous fixed-sleep behaviour for an unrecognised CLI.

Scenario: Boot block withheld until the pane is CLI-ready

  • GIVEN a freshly split agent pane whose CLI process has not yet reached its interactive prompt
  • WHEN the launch flow reaches the boot-block injection step for that pane
  • THEN the system SHALL poll the pane for a CLI-readiness marker and SHALL NOT inject the boot block while the pane still shows only a bare shell prompt

Scenario: Boot block injected once the CLI is ready

  • GIVEN an agent pane whose CLI has reached its interactive ready state within the readiness timeout
  • WHEN the readiness poll observes the CLI-readiness marker
  • THEN the system SHALL inject the boot block into that pane exactly once

Scenario: CLI relaunched when the pane is still a bare shell

  • GIVEN an agent pane whose CLI never started (the pane is still a bare shell when the readiness timeout elapses)
  • WHEN the readiness gate exhausts its timeout for that pane
  • THEN the system SHALL relaunch the CLI command into that pane and poll for readiness again, up to the relaunch-attempt budget, before falling back to injection

Scenario: Unrecognised CLI falls back to fixed-budget injection

  • GIVEN an agent pane running a custom CLI whose interactive UI does not match any known readiness marker
  • WHEN the readiness budget elapses without a positive readiness classification
  • THEN the system SHALL inject the boot block after the budget rather than rejecting the launch, so behaviour is no worse than the prior fixed-sleep launch for an unrecognised CLI

Scenario: Bare start is gated

  • GIVEN a bare git paw start with the broker enabled whose first agent pane is still a bare shell when the launch reaches injection
  • WHEN the launch injects the boot block pre-fill
  • THEN the gate SHALL run first and the pre-fill SHALL NOT be typed into the bare shell

Scenario: Spec-driven start stuck on a first-run dialog fails loudly

  • GIVEN a spec-driven git paw start --specs a whose agent pane is blocked on an unanswered first-run acceptance dialog
  • WHEN the launch reaches injection for that pane
  • THEN the launch SHALL fail with an error naming the pane and the attach command
  • AND the boot block SHALL NOT be typed on top of the dialog

Scenario: Restart from pause gates a held prompt

  • GIVEN a paused session holding a boot prompt for an agent added while paused, whose pane is blocked on a first-run dialog
  • WHEN git paw start restarts the session
  • THEN the held prompt SHALL NOT be submitted to that pane
  • AND it SHALL remain held for the next restart
  • AND stderr SHALL name the blocked pane while the rest of the session restarts

Scenario: A held prompt whose pane cannot be found stays held

  • GIVEN a paused session holding a boot prompt for an agent whose worktree no longer resolves to a live pane, with another ready CLI at that agent’s computed pane index
  • WHEN git paw start restarts the session
  • THEN the held prompt SHALL NOT be submitted to any pane
  • AND it SHALL remain held for the next restart
  • AND stderr SHALL name the agent as “pane not found” while the rest of the session restarts

Requirement: The dry-run plan preview reflects worktree placement

The git paw start --dry-run session-plan preview SHALL render each agent’s worktree path according to the configured [WorktreePlacement], rather than assuming the sibling layout: .git-paw/worktrees/<branch-slug> (relative to the repo root) for child placement, and ../<project>-<branch-slug> for sibling placement. This applies uniformly to the plain, --supervisor, and --from-specs dry-run previews. Computing the preview path MUST NOT create any directory (the dry run stays read-only).

Test: git::tests::worktree_display_path_reflects_placement, git::tests::worktree_path_for_creates_nothing

Scenario: Child placement preview shows the in-repo path

  • GIVEN a repository configured with child worktree placement
  • WHEN the dry-run plan renders the worktree path for branch feat/auth-flow
  • THEN it SHALL show .git-paw/worktrees/feat-auth-flow
  • AND it SHALL NOT show a ../-prefixed sibling path

Scenario: Sibling placement preview shows the sibling path

  • GIVEN a repository configured with sibling worktree placement
  • WHEN the dry-run plan renders the worktree path for branch feat/auth-flow
  • THEN it SHALL show ../<project>-feat-auth-flow beside the repository

Scenario: Rendering the preview path creates nothing

  • WHEN the dry-run preview computes a child-placement worktree path
  • THEN no worktree directory or .git-paw/worktrees/ entry is created on disk

Requirement: Launch gate handles a first-run permission/trust acceptance dialog

The readiness gate SHALL, when the launch-readiness poll observes a pane presenting a first-run acceptance dialog for its CLI — a one-time bypass-permissions confirmation (e.g. the dialog Claude Code shows the first time --dangerously-skip-permissions runs against a config directory that never accepted it) or a “trust this folder?” prompt — either answer the dialog to reach the interactive ready state, or fail the launch loudly. It SHALL NOT treat the dialog as a bare shell and relaunch (which would re-open the dialog), and SHALL NOT treat it as ready and inject the boot block on top of it. A pane left blocked on an unanswered acceptance dialog SHALL NOT be reported as a healthy, running agent. Dialog detection SHALL be per-CLI and conservative: a CLI whose acceptance dialog the gate does not recognise SHALL fall back to the existing readiness / relaunch / fixed-budget behaviour, so launch is never worse than before.

Scenario: First-run bypass acceptance dialog is answered, not relaunched

  • GIVEN an agent pane launched with a full-auto bypass flag against a config directory that has never accepted bypass, showing the first-run acceptance dialog
  • WHEN the readiness gate polls the pane
  • THEN the gate SHALL answer the acceptance dialog (or fail the launch loudly) rather than relaunching the CLI into the dialog

Scenario: A pane stuck on an acceptance dialog is not reported healthy

  • GIVEN an agent pane blocked on an unanswered first-run acceptance dialog after the launch budget elapses
  • WHEN launch completes
  • THEN the pane SHALL NOT be reported as a healthy running agent (the launch fails loudly or the pane’s dead/blocked state is surfaced)

Scenario: Unrecognised CLI acceptance dialog falls back to prior behaviour

  • GIVEN a custom CLI whose first-run dialog the gate does not recognise
  • WHEN the readiness budget elapses
  • THEN the gate SHALL fall back to the existing readiness / relaunch / fixed-budget injection behaviour

Requirement: Start reattaches to an existing live session instead of forking

git paw start SHALL reattach to the repository’s existing live session rather than creating a numerically-suffixed parallel session (paw-<project>-2) when that repository already has a live session (resolved by repository path, per session-state find-by-repository). The -N collision suffix SHALL be reserved for genuinely distinct sessions (e.g. two different repositories that share a project name), not a re-invocation of start against the same repository’s running session. In a non-interactive context where reattaching is not possible, start SHALL refuse with an actionable error (or require an explicit opt-in flag) rather than silently forking a parallel wave.

With --unattended, start SHALL instead bind a drive loop to the live session, whether or not the invocation is interactive (per supervisor-unattended-operation, A drive loop can be bound to a live supervised session). It SHALL NOT attach the terminal, SHALL NOT create a suffixed parallel session, and SHALL refuse only for the reasons that requirement and At most one drive loop drives a session instance state. Without --unattended the behaviour above is unchanged.

Scenario: Re-start of a repo with a live session reattaches

  • GIVEN a repository with a live session paw-x
  • WHEN the user runs git paw start again in that repository (interactive)
  • THEN the system SHALL reattach to paw-x and SHALL NOT create paw-x-2

Scenario: A distinct repository sharing a name still gets its own session

  • GIVEN a live session paw-x belonging to a different repository
  • WHEN git paw start runs in a second repository whose project name is also x
  • THEN the system SHALL resolve a distinct session name (paw-x-2) for the second repository

Scenario: Non-interactive re-start refuses rather than forking

  • GIVEN a repository with a live session and a non-interactive (non-TTY) invocation
  • WHEN git paw start runs again in that repository without --unattended
  • THEN the system SHALL refuse with an actionable error (or require an explicit opt-in flag) rather than silently creating a suffixed parallel session

Test: tests/supervisor_reattach_integration.rs::supervisor_restart_refuses_rather_than_forking_a_parallel_session (unchanged).

Scenario: Non-interactive re-start with –unattended binds a drive loop

  • GIVEN a repository with a live supervised session paw-x, no drive loop running, and a non-interactive (non-TTY) invocation
  • WHEN git paw start --unattended runs again in that repository
  • THEN the system SHALL bind a drive loop to paw-x and run it to an exit condition
  • AND SHALL NOT create paw-x-2
  • AND SHALL NOT print the non-interactive reattach refusal

Test: tests/unattended_drive_e2e.rs::unattended_rebinds_a_loop_to_a_live_session_after_heartbeat.

Requirement: Every pane-less session uses the agent grid

Every launch path — bare start, spec-driven start, and supervised start — and the recovery of a stopped session SHALL build the window with the agent-grid geometry defined by “Supervisor-mode pane layout”: the dashboard as the full-width top row when the broker is enabled, coding agents row-major below at no more than five per row, each row rebalanced to equal width, and the 25-agent cap. When the session has no dashboard (broker disabled), there SHALL be no top row: coding agents SHALL start at pane 0 and the agent rows SHALL share the full window height evenly. The whole-window select-layout tiled and main-horizontal layouts SHALL NOT be used to arrange coding agents. Every coding agent’s pane index SHALL be its row-major position — the session’s first agent index plus the agent’s position — at any agent count: each split that creates an agent pane, including the one that opens a new row and the one git paw add performs, SHALL target the session’s last pane, so the new pane takes the next index and no existing pane is renumbered.

Scenario: Bare start builds the grid

  • GIVEN [broker] enabled = true and no supervisor mode
  • WHEN git paw start launches 7 branches
  • THEN pane 0 SHALL be the dashboard spanning the full width
  • AND panes 1-5 SHALL form the first agent row and panes 6-7 the second, each row at equal widths

Scenario: Spec-driven start builds the grid

  • GIVEN a spec-driven git paw start --specs a b c without supervisor mode and the broker enabled
  • WHEN the session is built
  • THEN pane 0 SHALL be the dashboard and panes 1-3 SHALL be a single equal-width agent row

Scenario: Broker-less start uses the full height

  • GIVEN [broker] enabled = false
  • WHEN git paw start launches 6 branches
  • THEN there SHALL be no dashboard pane
  • AND panes 0-4 SHALL form the first agent row and pane 5 the second, the two rows sharing the full height evenly

Scenario: Every agent’s pane index holds that agent

  • GIVEN a bare session started with 7 branches, with the broker enabled and again with it disabled
  • WHEN the window is built, and again when git paw add attaches a sixth agent to a 5-agent session
  • THEN pane first_agent + i SHALL run in the worktree of the i-th agent and run that agent’s CLI, for every agent
  • AND no launch command or prompt SHALL be typed into a pane other than the one its agent occupies

Scenario: Recovery rebuilds a grid

  • GIVEN a stopped bare session saved with the earlier tiled layout and 3 recorded worktrees with the broker enabled
  • WHEN git paw start recovers it
  • THEN the rebuilt window SHALL be the grid (dashboard row + one agent row of 3)
  • AND the re-saved session SHALL record the grid layout

Session State

Purpose

Persist session state to disk for recovery after crashes, reboots, or manual stops. Stores one JSON file per session under the XDG data directory, with atomic writes and tmux liveness checks. It also has git paw start write (and purge remove) a per-repo session JSON at .git-paw/sessions/paw-<project>.json — carrying the session name and each agent’s branch_id, worktree path, CLI, and pane index — as the discovery surface the bundled sweep.sh helper reads, with a live-tmux fallback when the file is absent; distinguishes stale session receipts from live ones via a single cheap tmux has-session probe (status reports 🔴 stale, start auto-invalidates stale receipts before launching, and purge --stale prunes only stale entries); and guarantees session recovery rebuilds exactly the panes a session defines (N + 2 in supervisor mode) on a headless canvas sized to tile them without overflow, preserving the persisted session JSON when a git paw start errors mid-launch. It also renders a Markdown session summary from BrokerState and PawSession — metadata, per-agent details (status, modified files, exports, blocked time), merge order, and totals — which the supervisor writes to a timestamped, filesystem-safe file under .git-paw/sessions/ so multiple runs coexist without overwriting.

Requirements

Requirement: Save session state atomically

The system SHALL serialize session data to JSON and write it atomically using a temp file and rename to prevent corruption.

The session data SHALL include optional broker fields: broker_port (Option<u16>), broker_bind (Option<String>), and broker_log_path (Option<PathBuf>). These fields SHALL be omitted from the JSON when None and SHALL default to None when absent during deserialization.

broker_log_path names one log per session — <session_state_dir>/<session_name>.broker.log — so two repositories on one machine never write to, or purge, the same file. A session saved by an earlier git-paw records the shared <session_state_dir>/broker.log instead; that value SHALL continue to load and remain authoritative for both the broker and purge until a resume or recovery re-stamps it.

Scenario: Saved session round-trips with all fields intact

  • GIVEN an active session with 3 worktrees
  • WHEN save_session() is called and the session is loaded back
  • THEN all fields (session_name, repo_path, project_name, created_at, status, worktrees) SHALL match the original

Scenario: Saved session with broker fields round-trips

  • GIVEN an active session with broker_port = Some(9119), broker_bind = Some("127.0.0.1"), broker_log_path = Some("/path/to/paw-my-project.broker.log")
  • WHEN save_session() is called and the session is loaded back
  • THEN all broker fields SHALL match the original

Scenario: Session without broker fields loads successfully

  • GIVEN a session JSON file saved by v0.2.0 (no broker fields)
  • WHEN the session is loaded
  • THEN broker_port, broker_bind, and broker_log_path SHALL all be None
  • AND all existing fields SHALL load correctly

Scenario: A session recording the old shared log name still loads

  • GIVEN a session JSON saved by an earlier git-paw with broker_log_path = Some("<session_state_dir>/broker.log")
  • WHEN the session is loaded
  • THEN broker_log_path SHALL be that recorded path
  • AND it SHALL be the path both the broker writes and purge deletes

Scenario: Saving again replaces previous state

  • GIVEN a previously saved session
  • WHEN save_session() is called with updated fields
  • THEN the new state SHALL overwrite the old state

Requirement: Load session by name

The system SHALL load a session from disk by name, returning None if the file does not exist.

Scenario: Loading a nonexistent session returns None

  • GIVEN no session file exists with the given name
  • WHEN load_session() is called
  • THEN it SHALL return Ok(None)

Test: session::tests::loading_nonexistent_session_returns_none

Requirement: Find session by repository path

The system SHALL scan all session files and return the session matching a given repository path.

The default lookup SHALL match a session’s repository path EXACTLY: a path that is the root of — or lies inside — a linked worktree SHALL NOT resolve to the owning repository’s session. This exactness is a safety boundary. Every command that MUTATES session state (stop, purge, add, remove, pause, and start’s recover/resume dispatch) resolves its repository to the root of the checkout it is run from, which inside an agent worktree is that WORKTREE’s root; were the default lookup to resolve the owner, such a command run by an agent in its own worktree would act on the whole session — deleting sibling worktrees and branches, or killing the session.

The system SHALL additionally provide a SEPARATE, read-only owner-resolving lookup that returns the session of the repository that OWNS a given path, by resolving a linked worktree to that repository. It SHALL try the exact match first and resolve the owner only on a miss. It SHALL be used ONLY by the MCP server’s repository context and its read-only session view, so a git paw mcp spawned by an agent’s CLI from inside the agent’s worktree with no --repo finds the session and its broker. No mutating command SHALL use it.

Scenario: Finds correct session among multiple

  • GIVEN two sessions for different repositories
  • WHEN find_session_for_repo() is called with one repo path
  • THEN it SHALL return the matching session

Test: session::tests::finds_correct_session_among_multiple_by_repo_path

Scenario: No matching session

  • GIVEN saved sessions for other repositories
  • WHEN find_session_for_repo() is called with a different path
  • THEN it SHALL return None

Test: session::tests::find_returns_none_when_no_repo_matches

Scenario: No sessions directory

  • GIVEN no sessions directory exists
  • WHEN find_session_for_repo() is called
  • THEN it SHALL return None

Test: session::tests::find_returns_none_when_no_sessions_exist

Scenario: A worktree root resolves to its repository’s session

  • GIVEN an active session for a repository and one of that session’s agent worktrees
  • WHEN find_session_for_repo_or_owner() is called with the worktree’s root
  • THEN it SHALL return the repository’s session

Scenario: The owner-resolving lookup prefers an exact match

  • GIVEN a session recorded for an agent worktree’s own root and another for the repository that owns it
  • WHEN find_session_for_repo_or_owner() is called with the worktree’s root
  • THEN it SHALL return the worktree’s own session, not the owner’s

Scenario: The default lookup does not cross the worktree boundary

  • GIVEN an active session for a repository and one of that session’s agent worktrees
  • WHEN find_session_for_repo() is called with the worktree’s root
  • THEN it SHALL return None

Scenario: A mutating command run inside an agent worktree does not act on the session

  • GIVEN an active session and one of its agent worktrees
  • WHEN git paw stop or git paw purge --force is run with that worktree as the working directory
  • THEN the session SHALL remain alive, its branches and its sibling agents’ worktrees SHALL remain, and the command SHALL report that there is no session for this repository

Scenario: The MCP server started in a worktree finds the broker

  • GIVEN an active broker-enabled session and git paw mcp started with its working directory inside an agent worktree and no --repo
  • WHEN the server resolves its repository context
  • THEN its broker URL SHALL be the session’s broker URL, not absent

Requirement: Delete session by name

The system SHALL delete a session file, succeeding even if the file does not exist (idempotent).

Scenario: Deleted session is no longer loadable

  • GIVEN a saved session
  • WHEN delete_session() is called
  • THEN load_session() SHALL return None

Test: session::tests::deleted_session_is_no_longer_loadable

Scenario: Deleting nonexistent session succeeds

  • GIVEN no session file with the given name
  • WHEN delete_session() is called
  • THEN it SHALL return Ok(())

Test: session::tests::deleting_nonexistent_session_succeeds

Requirement: Effective status combines file state with tmux liveness

Session::effective_status(is_tmux_alive) SHALL combine the persisted status field with the result of is_tmux_alive to produce the runtime-effective status:

Recorded statustmux alive?Effective status
ActiveyesActive
ActivenoStopped
PausedyesPaused
PausednoStopped
StoppedanyStopped

The rule for Paused: tmux must still be alive for the Paused state to be valid — pause’s whole purpose is to keep tmux + CLI panes running while the client is detached. If tmux died despite a recorded Paused state (e.g. tmux server crash), effective_status SHALL downgrade to Stopped, and cmd_start SHALL run the cold-recovery path (fresh CLI spawn) rather than the restart-from-pause path.

Scenario: Active + alive remains Active

  • GIVEN a session with status = Active
  • WHEN effective_status(|_| true) is called
  • THEN it SHALL return Active

Scenario: Active + dead downgrades to Stopped

  • GIVEN a session with status = Active
  • WHEN effective_status(|_| false) is called
  • THEN it SHALL return Stopped

Scenario: Paused + alive remains Paused

  • GIVEN a session with status = Paused
  • WHEN effective_status(|_| true) is called
  • THEN it SHALL return Paused

Scenario: Paused + dead downgrades to Stopped

  • GIVEN a session with status = Paused
  • WHEN effective_status(|_| false) is called
  • THEN it SHALL return Stopped

Scenario: Stopped remains Stopped regardless of tmux liveness

  • GIVEN a session with status = Stopped
  • WHEN effective_status is called with either liveness result
  • THEN it SHALL return Stopped

Requirement: SessionStatus display format

The SessionStatus enum SHALL display as lowercase strings.

Scenario: SessionStatus display strings

  • GIVEN SessionStatus::Active and SessionStatus::Stopped
  • WHEN formatted with Display
  • THEN they SHALL render as "active" and "stopped"

Test: session::tests::session_status_displays_as_lowercase_string

Requirement: Recovery data survives tmux crashes

After a tmux crash, the persisted session SHALL contain all data needed to reconstruct the session.

Scenario: Crashed session has all recovery data including broker fields

  • GIVEN a saved session with worktrees and broker enabled
  • WHEN tmux crashes and the session is loaded from disk
  • THEN it SHALL have the session name, repo path, all worktree details, AND broker_port, broker_bind, broker_log_path

Scenario: Session recovery recreates the broker host and dashboard pane when broker was enabled

  • GIVEN a saved session with broker_port = Some(9119) and broker_bind = Some("127.0.0.1")
  • WHEN recover_session() is called
  • THEN the rebuilt tmux session SHALL have:
    • A detached broker window running git-paw __broker
    • Dashboard pane in pane 0 running git-paw __dashboard
    • GIT_PAW_BROKER_URL environment variable set to http://127.0.0.1:9119
    • All original worktree panes in subsequent indices

Scenario: Session recovery uses original broker config, not current config

  • GIVEN a saved session with broker_port = Some(9119)
  • AND current repo config has broker.enabled = false
  • WHEN recover_session() is called
  • THEN the broker host and dashboard pane SHALL still be created with the original broker URL

Scenario: Session recovery without original broker creates no dashboard

  • GIVEN a saved session with broker_port = None
  • WHEN recover_session() is called
  • THEN no broker host and no dashboard pane SHALL be created

Requirement: Session persistence SHALL work through the public API

Scenario: Save and load round-trip

  • GIVEN a session with 2 worktrees
  • WHEN save_session_in() and load_session_from() are called
  • THEN all fields SHALL match

Test: session_integration::save_and_load_round_trip

Scenario: Find session by repo path

  • GIVEN a saved session
  • WHEN find_session_for_repo_in() is called with the matching repo path
  • THEN the correct session SHALL be returned

Test: session_integration::find_session_by_repo_path

Scenario: Find returns None for unknown repo

  • GIVEN no matching session
  • WHEN find_session_for_repo_in() is called
  • THEN it SHALL return None

Test: session_integration::find_session_returns_none_for_unknown_repo

Scenario: Find correct session among multiple

  • GIVEN two sessions for different repos
  • WHEN find_session_for_repo_in() is called for one
  • THEN the correct session SHALL be returned

Test: session_integration::find_correct_session_among_multiple

Scenario: Delete removes session

  • GIVEN a saved session
  • WHEN delete_session_in() is called
  • THEN load_session_from() SHALL return None

Test: session_integration::delete_removes_session

Scenario: Delete nonexistent is idempotent

  • GIVEN no session file
  • WHEN delete_session_in() is called
  • THEN it SHALL succeed

Test: session_integration::delete_nonexistent_is_idempotent

Scenario: Load nonexistent returns None

  • GIVEN no session file
  • WHEN load_session_from() is called
  • THEN it SHALL return None

Test: session_integration::load_nonexistent_returns_none

Scenario: Saving again replaces previous state

  • GIVEN a saved session
  • WHEN the status is changed and saved again
  • THEN the loaded session SHALL have the new status

Test: session_integration::saving_again_replaces_previous_state

Scenario: Effective status active when tmux alive

  • GIVEN a session with Active status and tmux alive
  • WHEN effective_status() is called
  • THEN it SHALL return Active

Test: session_integration::effective_status_active_when_tmux_alive

Scenario: Effective status stopped when tmux dead

  • GIVEN a session with Active status and tmux dead
  • WHEN effective_status() is called
  • THEN it SHALL return Stopped

Test: session_integration::effective_status_stopped_when_tmux_dead

Scenario: Effective status stopped stays stopped

  • GIVEN a session with Stopped status
  • WHEN effective_status() is called
  • THEN it SHALL return Stopped regardless of tmux

Test: session_integration::effective_status_stopped_stays_stopped

Scenario: Saved session has all recovery fields

  • GIVEN a saved and reloaded session
  • WHEN recovery fields are checked
  • THEN session_name, repo_path, project_name, and all worktree entries SHALL be non-empty

Test: session_integration::saved_session_has_all_recovery_fields

Requirement: Paused session status variant

The SessionStatus enum SHALL include a third variant Paused (alongside Active and Stopped). The serde representation SHALL serialize as the lowercase string "paused" and SHALL deserialize from the same string. The Display implementation SHALL render Paused as "paused".

The Paused state means: the tmux session is intended to remain alive, all coding-agent CLI panes are intended to remain running, the user’s tmux client is detached, and the broker is stopped. Session state files saved by v0.4.0 binaries (which only know Active and Stopped) SHALL continue to load successfully under v0.5+ binaries — the new variant only appears in files saved by v0.5+.

Scenario: Paused status serializes lowercase

  • GIVEN a Session with status = SessionStatus::Paused
  • WHEN save_session() is called and the JSON file is inspected
  • THEN the "status" field SHALL be "paused"

Scenario: Paused status round-trips

  • GIVEN a Session with status = SessionStatus::Paused saved to disk
  • WHEN the session is loaded back via load_session()
  • THEN status SHALL be SessionStatus::Paused

Scenario: v0.4-saved sessions load under v0.5

  • GIVEN a session JSON file saved by v0.4.0 (only "active" or "stopped" in the status field)
  • WHEN the file is loaded by a v0.5+ binary
  • THEN the load SHALL succeed
  • AND the status field SHALL match the original (Active or Stopped)

Scenario: Paused Display renders lowercase

  • WHEN format!("{}", SessionStatus::Paused) is evaluated
  • THEN the result SHALL be "paused"

Requirement: Dashboard pane index persisted in session state

The Session struct SHALL include an optional field dashboard_pane: Option<u32> that records the pane index of the dashboard pane within the tmux session. The field SHALL use #[serde(default, skip_serializing_if = "Option::is_none")] so sessions saved without it load with None. The field SHALL be populated with Some(0) by every supervised start flow (the supervised layout always places the dashboard at pane 0) and by every bare start flow that enables the broker, because the dashboard is pane 0 in both layouts; only a bare start without the broker records None.

Consumers that need the first coding-agent pane index or the dashboard index (status, stop, pause, restart from pause, remove, liveness, recovery, the drive loop) SHALL derive them from the session’s layout record, which reads the RECORDED dashboard_pane. When it is Some(n), the dashboard is pane n and the first coding agent is pane n + 1. A session saved by a pre-relocation binary with dashboard_pane = Some(1) (supervisor at 0, dashboard at 1, agents from 2) therefore keeps resolving its agent panes from index 2 while it is still alive after an upgrade. When the field is None, the layout record SHALL apply the earlier (pre-relocation) rules, so live sessions saved by older binaries keep resolving: a supervised session’s coding agents start at pane 2, a bare session’s at pane 1 when it recorded a broker and pane 0 when it did not, and the dashboard index defaults to 0 for pause and restart from pause. SessionMode SHALL be consulted only when dashboard_pane is None.

Scenario: Dashboard pane index round-trips

  • GIVEN a Session with dashboard_pane = Some(0) saved to disk
  • WHEN the session is loaded back
  • THEN dashboard_pane SHALL be Some(0)

Scenario: Session without dashboard_pane defaults to None on load

  • GIVEN a session JSON file with no dashboard_pane field
  • WHEN the session is loaded
  • THEN dashboard_pane SHALL be None

Scenario: Dashboard pane field is omitted when None

  • GIVEN a Session with dashboard_pane = None
  • WHEN save_session() is called and the JSON file is inspected
  • THEN the JSON SHALL NOT contain a dashboard_pane field

Scenario: Supervised start records the dashboard at pane 0

  • GIVEN [broker] enabled = true and a supervised launch
  • WHEN the session state is saved
  • THEN dashboard_pane SHALL be Some(0)
  • AND the first recorded coding agent’s pane_index in the per-repo discovery file SHALL be 1

Scenario: A pre-relocation live session keeps its agent offset

  • GIVEN a saved session with mode = Supervisor, dashboard_pane = Some(1) and broker enabled
  • WHEN git paw status resolves the session’s agent panes
  • THEN it SHALL treat pane 2 as the first coding-agent pane
  • AND no keystroke sender (the drive loop, or sweep.sh approve) SHALL treat pane 0 or pane 1 of that session as a coding agent

Scenario: A session without dashboard_pane follows the earlier rules

  • GIVEN a live saved session with mode = Supervisor, dashboard_pane = None and no broker (a broker-less supervised session from a pre-relocation binary)
  • WHEN git paw status resolves the session’s agent panes
  • THEN it SHALL treat pane 2 as the first coding-agent pane
  • AND a saved bare session with dashboard_pane = None SHALL resolve its first coding agent at pane 1 when it recorded a broker and at pane 0 when it did not

Scenario: A broker-less supervised start still records the dashboard

  • GIVEN [broker] enabled = false and a supervised launch
  • WHEN the session state is saved
  • THEN dashboard_pane SHALL be Some(0)

Requirement: start writes a per-repo session JSON

git paw start SHALL write a per-repo session JSON to .git-paw/sessions/paw-<project>.json describing the launched session. This is the discovery surface the bundled sweep.sh helper reads. The file SHALL be written on launch and removed on purge. Every path through which git paw start creates or rebuilds the session’s panes SHALL (re)write it — the bare launch, the spec-driven launch with or without supervisor mode, the supervisor launch, recovery of a stopped session, and restart of a paused session — and each agent entry’s pane_index SHALL identify the pane that agent occupies in the resulting session.

Scenario: start writes the per-repo JSON

  • WHEN git paw start (any flags) succeeds
  • THEN .git-paw/sessions/paw-<project>.json SHALL exist describing the session (name + agent list)

Scenario: A spec-driven launch without supervisor mode writes the per-repo JSON

  • WHEN git paw start --specs <names> succeeds without supervisor mode
  • THEN .git-paw/sessions/paw-<project>.json SHALL exist with one agents entry per launched spec branch
  • AND the bundled helper SHALL discover the session from it without relying on running inside the session

Scenario: Recovery rewrites the per-repo JSON

  • GIVEN a stopped session whose per-repo JSON is missing
  • WHEN git paw start recovers it
  • THEN .git-paw/sessions/paw-<project>.json SHALL exist and list every recovered agent with the pane index it occupies in the rebuilt session

Scenario: Restart from pause rewrites the per-repo JSON

  • GIVEN a paused session
  • WHEN git paw start resumes it
  • THEN .git-paw/sessions/paw-<project>.json SHALL list every agent with the pane index it occupies in the resumed session

Scenario: purge removes the per-repo JSON

  • GIVEN an active session with the per-repo JSON present
  • WHEN the user runs git paw purge --force
  • THEN the per-repo JSON SHALL be removed

Requirement: Per-repo JSON shape matches sweep.sh expectations

The per-repo paw-<project>.json SHALL include the session name plus an agent list, each entry carrying branch_id, worktree_path, cli, and pane_index. The shape SHALL match what the bundled assets/scripts/sweep.sh helper reads — invoked from the orchestrator’s own session at the repository root, outside the tmux session — so the helper works against the file without modification. The agent list SHALL list coding agents only.

Scenario: sweep.sh discovers agents via the per-repo JSON

  • GIVEN a freshly-started session and the bundled sweep.sh helper invoked from a shell at the repository root that is not attached to the tmux session
  • WHEN sweep.sh reads .git-paw/sessions/paw-<project>.json
  • THEN the helper SHALL enumerate the full agent list with branch_id, worktree path, CLI, and pane index — no fields missing or renamed relative to its expectations

Scenario: Adding a field is backwards-compatible

  • WHEN a future change adds a new field to the per-repo JSON
  • THEN the current sweep.sh SHALL still find every documented field; unknown extra fields SHALL be ignored

Requirement: sweep.sh falls back to live tmux when the file is absent

The bundled sweep.sh SHALL fall back to discovering the session name from the $TMUX environment variable, or tmux display-message -p '#S', when .git-paw/sessions/paw-<project>.json is absent (e.g. the supervisor attached to a pre-existing paw-* session created outside the normal git paw start flow). The helper SHALL NOT require manual authoring of the session JSON.

Scenario: Helper discovers the session with no JSON present

  • GIVEN a paw-myproj tmux session is active but .git-paw/sessions/paw-myproj.json does not exist
  • WHEN sweep.sh runs inside the session
  • THEN the helper SHALL resolve the session name via $TMUX / tmux display-message -p '#S' and operate without error, instead of failing to discover the session

Scenario: Per-repo JSON takes precedence when present

  • GIVEN both the per-repo JSON and a live tmux session exist
  • WHEN sweep.sh runs
  • THEN the helper SHALL prefer the per-repo JSON’s agent list (richer — it carries worktree paths + pane indices) over the bare session-name fallback

Requirement: Status distinguishes stale receipts from active sessions

git paw status SHALL probe tmux has-session -t paw-<project> when the receipt claims active. When the probe shows the tmux session is absent, status SHALL report 🔴 stale instead of 🟢 active. The system SHALL preserve the existing 🟢 active and 🟡 stopped displays unchanged.

Scenario: Active session reports active

  • GIVEN a session whose receipt says active AND whose tmux session exists
  • WHEN the user runs git paw status
  • THEN the output SHALL display 🟢 active

Scenario: Stale receipt reports stale

  • GIVEN a session whose receipt says active AND whose tmux session does NOT exist (crash or release-boundary carry-over)
  • WHEN the user runs git paw status
  • THEN the output SHALL display 🔴 stale (not 🟢 active)

Scenario: Stopped session reports stopped

  • GIVEN a session whose receipt says stopped
  • WHEN the user runs git paw status
  • THEN the output SHALL display 🟡 stopped regardless of tmux liveness

Requirement: JSON status output adds stale value

The system SHALL extend the JSON output of git paw status with a new "stale" value for the status field whenever the liveness probe identifies a stale receipt. The system SHALL preserve the existing "active" and "stopped" values from v0.5.0 without semantic change.

Scenario: JSON output reports stale

  • GIVEN a stale-receipt session
  • WHEN the user runs git paw status --json
  • THEN the response object SHALL contain "status": "stale"

Requirement: start invalidates stale receipts automatically

git paw start SHALL run the same tmux has-session probe before deciding whether to recover or launch fresh. When the receipt claims active but the tmux session is absent, the system SHALL invalidate the receipt (purging the recorded worktrees + branches equivalent to git paw purge --force) and SHALL emit a stderr notice naming the invalidated entry before proceeding with the requested launch.

Scenario: Stale receipt is invalidated before launch

  • GIVEN a stale receipt for paw-myproj
  • WHEN the user runs git paw start (any flags)
  • THEN the system SHALL purge the stale receipt’s worktrees + branches, SHALL emit a stderr notice identifying the purged entry, and SHALL proceed with the requested launch as if no prior session existed

Scenario: Active receipt is NOT invalidated

  • GIVEN a live session whose receipt says active AND whose tmux session exists
  • WHEN the user runs git paw start (no recovery flags)
  • THEN the system SHALL behave per its existing reattach-or-error semantics; it SHALL NOT purge anything

Scenario: Notice text identifies the purged entry

  • WHEN the auto-invalidation fires
  • THEN the stderr notice SHALL include the session name, the receipt’s last_seen timestamp (if present), and a one-line explanation that the tmux session no longer exists

Requirement: purge –stale flag

git paw purge SHALL accept a --stale flag. When passed, the system SHALL purge only sessions whose receipt is stale per the liveness probe, and by default SHALL scope the sweep to the CURRENT repository — it SHALL NOT purge or touch a session belonging to any other repository. A machine-wide sweep of stale receipts across all repositories SHALL require the explicit --all-repos flag. In every case the guarantee is fail-safe: a session that is live per the probe — or whose liveness cannot be positively confirmed — SHALL NEVER be purged by --stale, in the current repository or any other. The flag is additive to existing --force.

Scenario: –stale is scoped to the current repository by default

  • GIVEN a stale session belonging to the current repository AND a stale session belonging to a different repository
  • WHEN the user runs git paw purge --stale (without --all-repos)
  • THEN only the current repository’s stale session’s worktrees + branches + receipt SHALL be purged
  • AND the other repository’s session SHALL NOT be touched

Scenario: –stale never purges a live or liveness-indeterminate session

  • GIVEN a live session (in the current or another repository), or a session whose liveness cannot be positively confirmed
  • WHEN the user runs git paw purge --stale (with or without --all-repos)
  • THEN that session SHALL NOT be purged

Scenario: –all-repos sweeps stale receipts across repositories

  • GIVEN stale sessions belonging to multiple repositories
  • WHEN the user runs git paw purge --stale --all-repos
  • THEN every repository’s stale session SHALL be purged
  • AND every live session SHALL remain intact

Scenario: –stale with nothing stale exits cleanly

  • GIVEN no stale receipts in scope
  • WHEN the user runs git paw purge --stale
  • THEN the command SHALL exit 0 with a “nothing to purge” message, and SHALL NOT touch any active session

Scenario: –stale + –force is well-defined

  • GIVEN a stale receipt in the current repository
  • WHEN the user runs git paw purge --stale --force
  • THEN the system SHALL behave equivalently to --stale alone (the --force flag is redundant in this combination; explicitly documented as a no-op pairing)

Requirement: Liveness probe is cheap

The staleness check SHALL be a single tmux has-session -t paw-<project> invocation. The system SHALL NOT probe the broker, agent processes, or any other liveness signal as part of the receipt-staleness check.

Scenario: Probe runs only one tmux call

  • WHEN any of status, start, or purge --stale performs the staleness check
  • THEN the resulting process tree SHALL contain exactly one tmux has-session invocation per session being probed

Scenario: Probe failure modes are tolerated

  • GIVEN a system where the tmux binary is absent or unreachable
  • WHEN the staleness check runs
  • THEN the system SHALL treat the probe failure as inconclusive (preserve the receipt’s current state) and SHALL NOT report 🔴 stale based on a tmux-missing failure

Requirement: Recovery rebuilds exactly the session’s panes

Recovering a stopped session SHALL rebuild in window 0 exactly the panes the session defines — in a supervised session, the dashboard pane plus one pane per recorded worktree (N + 1). The recovery SHALL NOT create more panes than the session defines and SHALL NOT create an orchestrator pane, including when the stopped session was saved with an earlier layout. The detached broker service window is recreated separately (broker-runtime) and is not counted.

Scenario: Recovery of an N-worktree supervised session creates N+1 panes

  • GIVEN a stopped supervised session with 3 recorded worktrees
  • WHEN git paw start recovers it
  • THEN window 0 of the rebuilt tmux session SHALL have exactly 4 panes (dashboard + 3 agents)

Scenario: Recovery of a pre-relocation session rebuilds the new layout

  • GIVEN a stopped supervised session saved with dashboard_pane = Some(1) and 2 recorded worktrees
  • WHEN git paw start recovers it
  • THEN window 0 of the rebuilt session SHALL have exactly 3 panes with the dashboard at pane 0
  • AND the re-saved session SHALL record dashboard_pane = Some(0)

Scenario: Recovery that cannot fit fails cleanly

  • GIVEN a recovery whose panes cannot be tiled in the available canvas
  • WHEN the rebuild fails
  • THEN it SHALL NOT leave a half-tiled session and SHALL NOT mutate the persisted session state

Requirement: A failed start preserves persisted session state

The system SHALL leave the persisted per-repo session JSON unchanged when a git paw start invocation errors before completing its launch. The session state file SHALL be rewritten only after a launch completes successfully, so an aborted start cannot destroy a recoverable session (e.g. rewrite it to an empty worktree list).

Scenario: Aborted start does not corrupt the session JSON

  • GIVEN an existing saved session with N worktrees
  • WHEN a subsequent git paw start errors mid-launch
  • THEN the saved session JSON SHALL still list those N worktrees (unchanged), so a later git paw start can still recover it

Requirement: Headless canvas fits multi-agent supervisor layouts

The system SHALL size the detached/headless new-session canvas and the global default-size large enough to tile a supervisor session with the supported number of agents without tmux returning no space for new pane. An attached client still resizes the session to the real terminal on attach.

Scenario: Headless supervisor session tiles without overflow

  • GIVEN a detached (no attached client) supervisor launch with several agents
  • WHEN the session is built at the headless fallback size
  • THEN all panes SHALL tile successfully (no no space for new pane error)

Requirement: write_session_summary function

The system SHALL provide a public function pub fn write_session_summary(state: &BrokerState, session: &PawSession, merge_order: &[String], output_path: &Path) -> Result<(), PawError> in a new src/summary.rs module.

The function SHALL extract data from BrokerState (agent records and message log) and PawSession (project name, start time) to produce the summary. The merge_order parameter provides the sequence in which branches were merged.

The function SHALL write the summary to output_path as a UTF-8 Markdown file. If the file already exists, it SHALL be overwritten.

Scenario: Summary is written to the specified path

  • GIVEN a valid BrokerState with two agents and a PawSession
  • WHEN write_session_summary(&state, &session, &merge_order, &path) is called
  • THEN a file SHALL exist at path
  • AND the function returns Ok(())

Scenario: Existing summary is overwritten

  • GIVEN a summary file already exists at the output path
  • WHEN write_session_summary is called again
  • THEN the file SHALL contain only the new summary content

Scenario: Write failure returns PawError

  • GIVEN the output path is in a read-only directory
  • WHEN write_session_summary is called
  • THEN the function returns Err(PawError::...)

Requirement: Session metadata section

The summary SHALL begin with a # Session Summary heading followed by a metadata block containing:

  • Project name (from PawSession)
  • Session date (formatted as YYYY-MM-DD)
  • Session duration (from PawSession start time to now)
  • Number of agents

Scenario: Summary contains correct project name

  • GIVEN a PawSession with project_name = "my-app"
  • WHEN write_session_summary is called
  • THEN the output SHALL contain my-app

Scenario: Summary contains agent count

  • GIVEN a BrokerState with 3 known agents
  • WHEN write_session_summary is called
  • THEN the output SHALL contain the number 3 adjacent to “Agents”

Requirement: Per-agent details section

The summary SHALL include an ## Agents section with one subsection (### <branch> (<cli>)) per known agent, containing:

  • Status: — the agent’s final status from its last message
  • Files modified: — from the last agent.artifact message’s modified_files
  • Exports: — from the last agent.artifact message’s exports
  • Blocked time: — estimated from agent.blocked to next agent.status timestamps in the message log

When an agent has no agent.artifact message, the files and exports fields SHALL show (none).

Scenario: Per-agent section includes modified files

  • GIVEN agent "feat-config" published agent.artifact with modified_files = ["src/config.rs"]
  • WHEN write_session_summary is called
  • THEN the output SHALL contain src/config.rs under the feat-config agent section

Scenario: Per-agent section handles no artifact

  • GIVEN agent "feat-config" only published agent.status (never completed)
  • WHEN write_session_summary is called
  • THEN the output SHALL still include a section for feat-config
  • AND files and exports SHALL show (none)

Requirement: Merge order section

The summary SHALL include an ## Overview section listing the merge order as a comma-separated list or bullet list.

Scenario: Merge order is included

  • GIVEN merge_order = ["feat-errors", "feat-config", "feat-detect"]
  • WHEN write_session_summary is called
  • THEN the output SHALL list feat-errors, feat-config, feat-detect in that order under the merge order label

Requirement: Totals section

The summary SHALL include a ## Totals section with:

  • Total agents: count of all known agents
  • Total time: overall session duration

Scenario: Totals section is present

  • GIVEN a session with 3 agents
  • WHEN write_session_summary is called
  • THEN the output SHALL contain a “Totals” section with at least agent count and duration

Requirement: Summary module is public

The summary module SHALL be declared as pub mod summary in src/lib.rs. The write_session_summary function SHALL be callable from src/main.rs supervisor handler.

Scenario: Summary module is accessible from main

  • WHEN src/lib.rs is compiled
  • THEN git_paw::summary::write_session_summary SHALL be a callable public function

Requirement: Summary output path is timestamped under .git-paw/sessions/

The supervisor SHALL call write_supervisor_summary, which writes the rendered Markdown to a timestamped file under <repo_root>/.git-paw/sessions/<UTC-timestamp>.md. The timestamp SHALL be in YYYY-MM-DDTHH-MM-SSZ format (filesystem-safe — colons replaced with hyphens) so multiple supervisor runs against the same repository produce distinct files that can coexist on disk without overwriting each other. write_supervisor_summary SHALL create .git-paw/sessions/ if it does not already exist, and SHALL return the absolute path it wrote so the caller can log it.

Scenario: Supervisor writes timestamped summary under sessions dir

  • GIVEN a completed supervisor session with all agents verified and merged
  • WHEN the supervisor finishes
  • THEN a file <repo_root>/.git-paw/sessions/<UTC-timestamp>.md SHALL exist
  • AND the timestamp SHALL match YYYY-MM-DDTHH-MM-SSZ
  • AND the filename SHALL NOT contain a : character

Scenario: Two sequential supervisor runs produce distinct summary files

  • GIVEN an empty repository with no .git-paw/sessions/ directory
  • WHEN write_supervisor_summary is called twice with at least one second between calls
  • THEN both calls SHALL succeed
  • AND the returned paths SHALL differ
  • AND both files SHALL exist on disk

Scenario: Sessions directory is created on demand

  • GIVEN a repository where .git-paw/sessions/ does not exist
  • WHEN write_supervisor_summary is called
  • THEN .git-paw/sessions/ SHALL be created
  • AND the rendered summary SHALL be written inside it

Requirement: Session lookup matches on the canonical repository path

find_session_for_repo SHALL match a stored session to a repository by comparing the two paths in canonical form, so a repository referred to by a symlinked or non-normalized path (for example /var/… resolving to /private/var/…, or a path containing a .. segment) still resolves to its session. When a path cannot be canonicalized (for example the repository directory no longer exists), the system SHALL fall back to comparing the raw paths, preserving the prior behavior for that case.

Scenario: A session is found through a symlinked repo path

  • GIVEN a session registered at a repository directory
  • AND a symlink that resolves to that same directory
  • WHEN the session is looked up by the symlink path
  • THEN the session SHALL be found

Scenario: A non-existent path falls back to raw comparison

  • GIVEN a session registered at a repository path that does not exist on disk
  • WHEN it is looked up by that same (non-existent) path
  • THEN the session SHALL still be found via the raw-path fallback

Requirement: Liveness reflects dead agent panes, not just session existence

The tmux-liveness probe that feeds effective_status SHALL report a session as not alive when the tmux session object still exists but its agent panes’ CLI processes have all exited (the is_tmux_alive input becomes false) — so a session whose panes died shortly after launch is reported Stopped, not Active. Liveness SHALL NOT be satisfied by the mere existence of the named tmux session; it SHALL require at least one live agent pane (a pane whose launched CLI process is still running). This ensures git paw status and git paw doctor never report a session healthy after its panes have all died (the full-auto boot-exit failure mode). The probe SHALL tolerate the brief launch window in which panes are still starting their CLIs (it SHALL NOT flap a just-launched session to Stopped).

The probe SHALL report “agent panes exited” as a separate liveness outcome from “tmux session absent”. 🔴 stale and the JSON "stale" value SHALL mean only that the tmux session is absent. git paw status SHALL NOT report a session as stale when its tmux session exists and all its agent panes have exited. For such a session it SHALL:

  • display 🟡 stopped;
  • print a hint that the agent panes exited and that git paw start recovers them;
  • report the tmux session as running: Tmux: running in the human output and "tmux_running": true in JSON;
  • never say that the tmux session no longer exists.

start auto-invalidation and purge --stale SHALL keep deciding staleness with the single tmux has-session probe. As a result, git paw start recovers a session whose agent panes exited, and purge --stale never purges it.

The probe SHALL identify coding-agent panes by their pane-scoped @paw_role option rather than by pane position, because a pane index shifts when an earlier pane is killed. It SHALL classify each pane independently: a pane whose role is dashboard or supervisor is never a coding-agent pane, any other role is, and a pane carrying no role falls back to the positional rule. git-paw SHALL set @paw_role on every pane it creates, whether or not [layout].border_affordances is enabled. Consequently a paused session — git paw pause kills the dashboard pane, and tmux renumbers the survivors — SHALL NOT be reported as having exited agent panes while any of its agents is still running.

Scenario: A session whose agent panes all exited reports Stopped

  • GIVEN a session recorded Active whose tmux session object still exists but all agent panes’ CLI processes have exited
  • WHEN its effective status is resolved via the liveness probe
  • THEN the liveness probe SHALL report the session not alive
  • AND the effective status SHALL be Stopped (not Active)

Scenario: A session with at least one live agent pane remains Active

  • GIVEN a session recorded Active with at least one agent pane whose CLI process is still running
  • WHEN its effective status is resolved
  • THEN the liveness probe SHALL report the session alive
  • AND the effective status SHALL be Active

Scenario: Exited agent panes are a distinct liveness outcome from an absent session

  • GIVEN a tmux session that exists, created past the launch grace period, whose every agent pane runs a bare shell
  • WHEN the agent-pane liveness probe runs
  • THEN it SHALL return the agent-panes-exited outcome, not the outcome for an absent tmux session
  • AND for a tmux session that does not exist the same probe SHALL return the absent-session (stale) outcome

Scenario: Status shows exited agent panes as stopped with tmux running

  • GIVEN a session whose receipt says active, whose tmux session exists, and whose agent panes have all exited past the launch grace period
  • WHEN the user runs git paw status
  • THEN the output SHALL display 🟡 stopped, not 🔴 stale
  • AND it SHALL print a hint that the agent panes exited and that git paw start recovers them
  • AND it SHALL print Tmux: running
  • AND it SHALL NOT state that the tmux session no longer exists

Scenario: JSON status for exited agent panes reports stopped with tmux running

  • GIVEN the same session (tmux session present, all agent panes exited)
  • WHEN the user runs git paw status --json
  • THEN the response object SHALL contain "status": "stopped" and "tmux_running": true

Scenario: An absent tmux session still reports stale with tmux not running

  • GIVEN a session whose receipt says active and whose tmux session does not exist
  • WHEN the user runs git paw status --json
  • THEN the response object SHALL contain "status": "stale" and "tmux_running": false

Scenario: purge –stale leaves a session whose agent panes exited

  • GIVEN a session in the current repository whose receipt says active, whose tmux session exists, and whose agent panes have all exited
  • WHEN the user runs git paw purge --stale
  • THEN that session’s receipt SHALL NOT be purged
  • AND the command SHALL report that there is nothing to purge

Scenario: A paused session whose dashboard pane was killed reports paused, not exited

  • GIVEN a paused session whose dashboard pane git paw pause killed, whose remaining agent pane tmux has renumbered into the dashboard’s old index, and whose agent process is still running
  • WHEN the user runs git paw status and git paw doctor
  • THEN status SHALL display 🔵 paused and SHALL NOT print the agent-panes-exited hint
  • AND doctor’s session-state finding SHALL NOT say that its agent panes exited

Requirement: Worktree entry records the worker CLI session id

The WorktreeEntry struct SHALL include an optional field cli_session_id: Option<String> recording the session identifier git-paw assigned to the worker’s CLI at launch. The field SHALL be annotated #[serde(default, skip_serializing_if = "Option::is_none")] so session files written before this change load with None and an entry without an id serializes byte-identically to before.

Scenario: Assigned id round-trips

  • GIVEN a WorktreeEntry with cli_session_id = Some("3f2c…") saved to disk
  • WHEN the session is loaded back
  • THEN cli_session_id SHALL be Some("3f2c…")

Scenario: Older session files load with None

  • GIVEN a session JSON file with no cli_session_id field on its worktree entries
  • WHEN the session is loaded
  • THEN every entry’s cli_session_id SHALL be None
  • AND loading SHALL succeed without error

Scenario: Entry without an id serializes as before

  • GIVEN a WorktreeEntry with cli_session_id = None
  • WHEN the session is saved and the JSON inspected
  • THEN the JSON SHALL NOT contain a cli_session_id key

Requirement: Recovery relaunches a worker with its recorded session id

When git paw start recovers a stopped session, the system SHALL relaunch each worktree whose entry carries a cli_session_id AND whose CLI resolves a resume template with that template’s arguments appended to the CLI launch command, so the worker’s conversation context is restored. A worktree whose entry has no id, or whose CLI resolves no resume template, SHALL be relaunched exactly as before this change. Recovery SHALL NOT fail because a resume template is absent.

Scenario: Recovered Claude worker is resumed

  • GIVEN a stopped session whose worktree entry for feat/x has cli = "claude" and cli_session_id = Some("<uuid>"), and no [clis.claude] override
  • WHEN the session is recovered
  • THEN the pane command for feat/x SHALL contain --resume <uuid>

Scenario: Entry without an id launches fresh

  • GIVEN a stopped session whose worktree entry has cli_session_id = None
  • WHEN the session is recovered
  • THEN the pane command SHALL be identical to the pre-change recovery command for that entry

Scenario: CLI without a resume template launches fresh

  • GIVEN a worktree entry with cli = "my-agent", cli_session_id = Some("<id>"), and no [clis.my-agent].resume_args
  • WHEN the session is recovered
  • THEN the pane command SHALL contain no resume arguments
  • AND recovery SHALL succeed

Requirement: Status JSON exposes the worker CLI session id

git paw status --json SHALL include each agent’s cli_session_id (a string, or null when unassigned) so an operator or external orchestrator can resume a worker by hand. The human-readable git paw status output SHALL be unchanged.

Scenario: JSON status carries the id

  • GIVEN an active session whose feat/x entry has cli_session_id = Some("<uuid>")
  • WHEN git paw status --json is run
  • THEN the feat/x agent object SHALL contain "cli_session_id": "<uuid>"

Scenario: JSON status shows null when unassigned

  • GIVEN an agent whose entry has no session id
  • WHEN git paw status --json is run
  • THEN that agent object SHALL contain "cli_session_id": null

Scenario: Human-readable status is unchanged

  • WHEN git paw status is run without --json
  • THEN the output SHALL be byte-identical to the pre-change output for the same session

Requirement: Worktree entry records the spec it was launched from

The WorktreeEntry struct SHALL include an optional field spec_id: Option<String> recording the id of the SpecEntry an agent was launched from. Every launch path that creates an agent from a spec — the spec-driven launch with or without supervisor mode, and git paw add --from-spec — SHALL set it to that spec’s id; a launch without a spec (bare git paw start, git paw add <branch>) SHALL leave it None. The field SHALL be annotated #[serde(default, skip_serializing_if = "Option::is_none")] so session files written before this field existed load with None and an entry without a spec serializes byte-identically to before.

Scenario: A spec-driven launch records each agent’s spec id

  • GIVEN a spec-driven launch of specs 003-user-list and 004-error-handling
  • WHEN the session is saved and loaded back
  • THEN each agent’s worktree entry SHALL carry the spec_id of the spec it was launched from

Scenario: A launch without a spec records no spec id

  • GIVEN a bare git paw start with explicit branches
  • WHEN the session is saved and the JSON inspected
  • THEN no worktree entry SHALL contain a spec_id key

Scenario: Older session files load with no spec id

  • GIVEN a session JSON file whose worktree entries have no spec_id field
  • WHEN the session is loaded
  • THEN loading SHALL succeed without error
  • AND every entry’s spec_id SHALL be None

Requirement: Session records its pane layout

The Session struct SHALL include an optional field recording the window’s pane layout, written as the agent grid by every start, recovery and restart that builds or rebuilds the window. The field SHALL be annotated #[serde(default, skip_serializing_if = "Option::is_none")] so session files written earlier load unchanged. A session without the field SHALL be read as the agent grid when its mode is supervised, and as the earlier tiled layout when its mode is bare; consumers that depend on the geometry (add, remove) SHALL decide from this field and the mode, never from the mode alone.

Scenario: New sessions record the grid

  • GIVEN a bare git paw start with two branches
  • WHEN the session state is saved
  • THEN the session file SHALL record the grid layout

Scenario: Earlier session files load unchanged

  • GIVEN a session file written before this field existed
  • WHEN it is loaded
  • THEN loading SHALL succeed and the layout field SHALL be absent

Scenario: An earlier bare session reads as tiled

  • GIVEN a loaded bare session without the layout field
  • WHEN git paw add inspects its layout
  • THEN it SHALL be treated as the tiled layout

Scenario: An earlier supervised session reads as the grid

  • GIVEN a loaded supervised session without the layout field
  • WHEN git paw add inspects its layout
  • THEN it SHALL be treated as the agent grid

Requirement: Session file records the authority credential

The Session struct SHALL include an optional field broker_authority: Option<String> annotated #[serde(default, skip_serializing_if = "Option::is_none")], so session files written before this change load with None and a session without a broker serializes byte-identically to before. save_session SHALL write the session file with owner-only permissions (0600) on Unix. The per-repo discovery file (RepoSessionFile) SHALL NOT carry the credential.

Scenario: Credential round-trips

  • GIVEN a Session with broker_authority = Some("<hex>") saved to disk
  • WHEN the session is loaded back
  • THEN broker_authority SHALL be Some("<hex>")

Scenario: Older session files load with None

  • GIVEN a session JSON file with no broker_authority field
  • WHEN the session is loaded
  • THEN broker_authority SHALL be None
  • AND loading SHALL succeed

Scenario: Session file is owner-only

  • WHEN save_session writes a session file on Unix
  • THEN the file’s mode SHALL be 0600

Scenario: Discovery file never carries the credential

  • GIVEN a session with a credential
  • WHEN the per-repo discovery file is written
  • THEN it SHALL NOT contain a broker_authority key

Configuration

Purpose

Parse TOML configuration from global (~/.config/git-paw/config.toml) and per-repo (.git-paw/config.toml) files. Supports custom CLI definitions, presets, and programmatic add/remove of custom CLIs with repo config overriding global config.

Requirements

Requirement: Parse TOML config with all fields

The system SHALL parse a TOML configuration file containing default_cli, mouse, clis, presets, and optional sections [specs], [logging], [broker], and [supervisor].

Scenario: Config with all fields populated

  • GIVEN a TOML file with default_cli, mouse, custom CLIs, presets, [broker], and [supervisor] sections
  • WHEN the file is loaded
  • THEN all fields SHALL be correctly parsed including supervisor fields

Scenario: All fields are optional

  • GIVEN a TOML file with only default_cli
  • WHEN the file is loaded
  • THEN missing fields SHALL default to None or empty collections
  • AND supervisor SHALL be None

Requirement: Merge repo config over global config

The system SHALL merge per-repo configuration on top of global configuration, with repo values taking precedence for scalar fields and map entries.

Scenario: Repo overrides global scalar fields

  • GIVEN global config has default_cli = "claude" and mouse = true, and repo has default_cli = "gemini"
  • WHEN configs are merged
  • THEN default_cli SHALL be "gemini" and mouse SHALL be true (preserved from global)

Test: config::tests::repo_config_overrides_global_scalars

Scenario: CLI maps are merged

  • GIVEN global config has CLI agent-a and repo config has CLI agent-b
  • WHEN configs are merged
  • THEN both CLIs SHALL be present

Test: config::tests::repo_config_merges_cli_maps

Scenario: Repo CLI overrides global CLI with same name

  • GIVEN both global and repo define a CLI named my-agent
  • WHEN configs are merged
  • THEN the repo definition SHALL win

Test: config::tests::repo_cli_overrides_global_cli_with_same_name

Scenario: Only global config exists

  • GIVEN a global config file but no repo config
  • WHEN load_config() is called
  • THEN global values SHALL be used

Test: config::tests::load_config_from_reads_global_file_when_no_repo

Scenario: Only repo config exists

  • GIVEN a repo config file but no global config
  • WHEN load_config() is called
  • THEN repo values SHALL be used

Test: config::tests::load_config_from_reads_repo_file_when_no_global

Requirement: Preset lookup by name

The system SHALL provide access to named presets that define branches and a CLI.

Scenario: Preset accessible by name

  • GIVEN a config with a preset named "backend"
  • WHEN get_preset("backend") is called
  • THEN it SHALL return the preset with its branches and CLI

Test: config::tests::preset_accessible_by_name

Scenario: Missing preset returns None

  • GIVEN a config without the requested preset
  • WHEN get_preset("nonexistent") is called
  • THEN it SHALL return None

Test: config::tests::preset_returns_none_when_not_in_config

Requirement: Add custom CLIs to global config

The system SHALL add custom CLI definitions to the global config, resolving non-absolute commands via PATH.

Scenario: Add CLI with absolute path

  • GIVEN an absolute path to a CLI binary
  • WHEN add_custom_cli() is called
  • THEN the CLI SHALL be written to the config file

Test: config::tests::add_cli_writes_to_config_file

Scenario: Adding preserves existing entries

  • GIVEN an existing CLI in the config
  • WHEN a second CLI is added
  • THEN both CLIs SHALL be present

Test: config::tests::add_cli_preserves_existing_entries

Scenario: Adding CLI with missing command fails

  • GIVEN a command that does not exist on PATH
  • WHEN add_custom_cli() is called
  • THEN it SHALL return an error mentioning “not found on PATH”

Test: config::tests::add_cli_errors_when_command_not_on_path

Requirement: Remove custom CLIs from global config

The system SHALL remove a custom CLI by name, returning an error if the CLI is not found.

Scenario: Remove existing CLI

  • GIVEN a config with CLIs keep-me and remove-me
  • WHEN remove_custom_cli("remove-me") is called
  • THEN only keep-me SHALL remain

Test: config::tests::remove_cli_deletes_entry_from_config_file

Scenario: Remove nonexistent CLI returns error

  • GIVEN a config without the named CLI
  • WHEN remove_custom_cli() is called
  • THEN it SHALL return PawError::CliNotFound

Test: config::tests::remove_nonexistent_cli_returns_cli_not_found_error

Scenario: Remove CLI from empty/missing config returns error

  • GIVEN no config file exists
  • WHEN remove_custom_cli() is called
  • THEN it SHALL return PawError::CliNotFound

Test: config::tests::remove_cli_from_empty_config_returns_error

Requirement: Config survives round-trip serialization

A PawConfig SHALL be identical after save and reload.

Scenario: Config round-trip

  • GIVEN a fully populated config
  • WHEN saved and loaded back
  • THEN it SHALL be equal to the original

Test: config::tests::config_survives_save_and_load

Requirement: Config loading SHALL work with real files

The system SHALL provide a load_config(repo_root, user_config_path) function that loads the merged PawConfig from the per-repo .git-paw/config.toml and a user-level (global) config.toml. The second parameter user_config_path: Option<&Path> SHALL control which file is read as the user-level config:

  • When user_config_path is None, the loader SHALL resolve the user-level config path via the platform-default helper (global_config_path() → crate::dirs::config_dir().join("git-paw/config.toml")), preserving the v0.4 production behaviour.
  • When user_config_path is Some(p), the loader SHALL read p as the user-level config and SHALL NOT consult the platform-default helper. If p does not exist on disk, the user-level side of the merge SHALL be the default PawConfig, exactly as if no file existed at the platform-default path.

The merge semantics on top of the user-level config (per-repo config overrides user-level for scalar fields and map entries) are unchanged from prior requirements in this capability.

Scenario: Defaults when no files exist

  • GIVEN a temp directory with no config files
  • AND load_config is called with user_config_path = None
  • WHEN load_config() is called
  • THEN all fields SHALL be None/empty

Test: config_integration::load_config_returns_defaults_when_no_files_exist

Scenario: Reads repo .git-paw/config.toml

  • GIVEN a .git-paw/config.toml with default_cli and mouse
  • AND load_config is called with user_config_path = Some(&unused_temp_path)
  • WHEN load_config() is called
  • THEN the values SHALL be read correctly

Test: config_integration::load_config_reads_repo_config

Scenario: Repo config with custom CLIs

  • GIVEN a .git-paw/config.toml with two custom CLIs
  • AND load_config is called with user_config_path = Some(&unused_temp_path)
  • WHEN load_config() is called
  • THEN both CLIs SHALL be parsed with correct fields

Test: config_integration::repo_config_with_custom_clis

Scenario: Repo config with presets

  • GIVEN a .git-paw/config.toml with two presets
  • AND load_config is called with user_config_path = Some(&unused_temp_path)
  • WHEN load_config() is called
  • THEN presets SHALL be accessible with correct branches and CLI

Test: config_integration::repo_config_with_presets

Scenario: Default PawConfig has no presets

  • GIVEN a default PawConfig
  • WHEN get_preset("nonexistent") is called
  • THEN it SHALL return None

Test: config_integration::get_preset_returns_none_for_unknown

Scenario: Repo config overrides default fields

  • GIVEN a .git-paw/config.toml with specific values
  • AND load_config is called with user_config_path = Some(&unused_temp_path)
  • WHEN load_config() is called
  • THEN the repo values SHALL take precedence

Test: config_integration::repo_config_overrides_default_fields

Scenario: Repo config path is correct

  • GIVEN a temp directory
  • WHEN repo_config_path() is called
  • THEN it SHALL return <dir>/.git-paw/config.toml

Test: config_integration::repo_config_path_is_in_repo_root

Scenario: Malformed TOML returns error

  • GIVEN a .git-paw/config.toml with invalid TOML
  • AND load_config is called with user_config_path = Some(&unused_temp_path)
  • WHEN load_config() is called
  • THEN it SHALL return an error

Test: config_integration::malformed_toml_returns_error

Scenario: Empty config file is valid

  • GIVEN an empty .git-paw/config.toml
  • AND load_config is called with user_config_path = Some(&unused_temp_path)
  • WHEN load_config() is called
  • THEN it SHALL return a default config

Test: config_integration::empty_config_file_is_valid

Scenario: None preserves platform-default user-config resolution

  • GIVEN a repo TempDir with no .git-paw/config.toml
  • AND the platform-default user config path (crate::dirs::config_dir().join("git-paw/config.toml")) is a readable file containing a custom CLI named globally-registered
  • WHEN load_config(&repo, None) is called
  • THEN the returned PawConfig.clis SHALL contain globally-registered
  • AND the loader SHALL have resolved the user-level path via global_config_path(), exactly matching v0.4 behaviour

Test: config::tests::load_config_with_none_reads_platform_default_global

Scenario: Some(path) pins the user-level read to that path

  • GIVEN a TempDir containing two distinct files:
    • tmp/global-A.toml defining custom CLI cli-A
    • tmp/global-B.toml defining custom CLI cli-B
  • AND an unrelated CLI cli-C is registered at the platform-default user-config path
  • WHEN load_config(&repo, Some(&tmp.join("global-A.toml"))) is called
  • THEN the returned PawConfig.clis SHALL contain cli-A
  • AND it SHALL NOT contain cli-B or cli-C

Test: config::tests::load_config_with_some_pins_global_to_override_path

Scenario: Some(nonexistent path) returns defaults for the user-level side

  • GIVEN a TempDir and a path tmp/does-not-exist.toml that has never been written
  • AND an unrelated CLI cli-leak is registered at the platform-default user-config path
  • WHEN load_config(&repo, Some(&tmp.join("does-not-exist.toml"))) is called
  • THEN the user-level side of the merge SHALL be the default PawConfig
  • AND the returned PawConfig.clis SHALL NOT contain cli-leak
  • AND no error SHALL be returned (a missing user-config file is not an error)

Test: config::tests::load_config_with_some_nonexistent_returns_defaults

Scenario: Override path does not affect repo-config resolution

  • GIVEN a TempDir with .git-paw/config.toml defining default_cli = "claude"
  • AND a separate path tmp/global.toml defining default_cli = "gemini"
  • WHEN load_config(&tmp, Some(&tmp.join("global.toml"))) is called
  • THEN the repo-level default_cli = "claude" SHALL override the user-level default_cli = "gemini" per the existing repo-overrides-user merge semantics
  • AND the override parameter SHALL only control which user-level file is read, never the repo-level resolution

Test: config::tests::load_config_override_does_not_affect_repo_resolution

Requirement: Custom CLI management SHALL persist through file I/O

Scenario: Add CLI with absolute path

  • GIVEN no config file
  • WHEN add_custom_cli_to() is called with an absolute path
  • THEN the CLI SHALL be persisted and reloadable

Test: config_integration::add_custom_cli_with_absolute_path

Scenario: Add CLI with display name

  • GIVEN no config file
  • WHEN add_custom_cli_to() is called with a display name
  • THEN the display name SHALL be persisted

Test: config_integration::add_custom_cli_with_display_name

Scenario: Multiple CLIs preserved across adds

  • GIVEN 4 CLIs added sequentially
  • WHEN the config is loaded
  • THEN all 4 SHALL be present with correct fields

Test: config_integration::add_multiple_custom_clis_preserves_all

Scenario: Adding overwrites existing entry

  • GIVEN a CLI with name my-agent already exists
  • WHEN add_custom_cli_to() is called with the same name but different values
  • THEN the new values SHALL replace the old

Test: config_integration::add_cli_overwrites_existing_entry

Scenario: Add CLI with nonexistent command fails

  • GIVEN a non-absolute command that is not on PATH
  • WHEN add_custom_cli_to() is called
  • THEN it SHALL return an error

Test: config_integration::add_cli_with_nonexistent_path_command_fails

Scenario: Remove custom CLI

  • GIVEN two CLIs in the config
  • WHEN one is removed
  • THEN only the other SHALL remain

Test: config_integration::remove_custom_cli

Scenario: Remove nonexistent CLI returns error

  • GIVEN no CLIs in the config
  • WHEN remove_custom_cli_from() is called
  • THEN it SHALL return an error

Test: config_integration::remove_nonexistent_cli_returns_error

Scenario: Remove all CLIs leaves empty config

  • GIVEN one CLI in the config
  • WHEN it is removed
  • THEN the CLI map SHALL be empty

Test: config_integration::remove_all_custom_clis_leaves_empty_config

Requirement: Global and repo config SHALL merge custom CLIs correctly

Scenario: Repo custom CLIs merge with global

  • GIVEN global config with 2 CLIs and repo config with 2 CLIs (one overlapping)
  • WHEN load_config_from() is called
  • THEN the result SHALL have 3 CLIs, with repo winning on collision

Test: config_integration::repo_custom_clis_merge_with_global_custom_clis

Requirement: Config SHALL handle many custom CLIs

Scenario: Config with 10 custom CLIs

  • GIVEN a config file with 10 custom CLI definitions
  • WHEN load_config() is called
  • THEN all 10 SHALL be parsed correctly

Test: config_integration::config_with_many_custom_clis

Requirement: The system SHALL support a default_spec_cli config field

The system SHALL support a default_spec_cli field in PawConfig that specifies the CLI to use for --from-specs branches that don’t have a paw_cli override, bypassing the interactive picker.

Scenario: default_spec_cli set

  • WHEN a config has default_spec_cli = "claude"
  • THEN PawConfig.default_spec_cli SHALL be Some("claude")

Scenario: default_spec_cli absent

  • WHEN a config has no default_spec_cli field
  • THEN PawConfig.default_spec_cli SHALL be None

Scenario: Merge preserves repo override

  • WHEN global config has default_spec_cli = "claude" and repo config has default_spec_cli = "gemini"
  • THEN the merged config SHALL have default_spec_cli = Some("gemini")

Requirement: Repo SHALL override new v0.2.0 scalar fields

Scenario: Repo overrides new v0.2.0 scalar fields

  • GIVEN global config has default_spec_cli = "claude" and repo has default_spec_cli = "gemini"
  • WHEN configs are merged
  • THEN default_spec_cli SHALL be "gemini"

Requirement: Specs configuration section

The system SHALL support an optional [specs] section with a dir field and a type field. Field names SHALL match the spec-scanning capability and the implementation in src/config.rs::SpecsConfig.

  • dir: String — path (relative to the repo root) to the directory containing spec files
  • type: String — backend identifier (e.g. "openspec", "markdown"); the field is exposed as spec_type in Rust to avoid clashing with the type keyword and is serialised as type in TOML/JSON via #[serde(rename = "type")]

When the [specs] section is absent, the optional specs field on PawConfig SHALL be None.

Scenario: Specs section with all fields

  • GIVEN a TOML file with [specs] containing dir = "openspec/specs" and type = "openspec"
  • WHEN the file is loaded
  • THEN specs.dir SHALL be "openspec/specs"
  • AND specs.spec_type SHALL be "openspec"

Scenario: Specs section defaults

  • GIVEN a TOML file without a [specs] section
  • WHEN the file is loaded
  • THEN specs SHALL be None

Scenario: Round-trip preserves rename

  • GIVEN a SpecsConfig { dir: "openspec/specs".into(), spec_type: "openspec".into() }
  • WHEN the value is serialised to TOML and parsed back
  • THEN the resulting TOML SHALL contain type = "openspec" (not spec_type)
  • AND parsing SHALL succeed and reproduce the original struct

Requirement: Logging configuration section

The system SHALL support an optional [logging] section with enabled and log_dir fields.

Scenario: Logging section with all fields

  • GIVEN a TOML file with [logging] containing enabled = true and log_dir = ".git-paw/logs"
  • WHEN the file is loaded
  • THEN logging.enabled SHALL be true and logging.log_dir SHALL be ".git-paw/logs"

Scenario: Logging section defaults

  • GIVEN a TOML file without a [logging] section
  • WHEN the file is loaded
  • THEN logging SHALL be None

Requirement: Default config generation

The system SHALL provide a function to generate a default config.toml string with active defaults and commented-out fields including the [supervisor] section.

Scenario: Generated config contains commented supervisor examples

  • WHEN the default config string is generated
  • THEN it SHALL contain commented-out examples for [supervisor] with enabled, cli, test_command, and agent_approval fields

Scenario: Generated config contains commented examples

  • WHEN the default config string is generated
  • THEN it SHALL contain commented-out examples for default_spec_cli, branch_prefix, [specs], [logging], [broker], and [supervisor]

Requirement: Config round-trip with new fields

A PawConfig with v0.2.0 fields populated SHALL be identical after save and reload.

Scenario: Config with specs and logging round-trips

  • GIVEN a config with default_spec_cli, branch_prefix, specs, and logging populated
  • WHEN saved and loaded back
  • THEN it SHALL be equal to the original

Requirement: Broker configuration section

The system SHALL support an optional [broker] section with the following fields:

  • enabled: bool — defaults to false when the field or section is absent
  • port: u16 — defaults to 9119 when absent
  • bind: String — defaults to "127.0.0.1" when absent

The BrokerConfig struct SHALL provide a url(&self) -> String method returning http://<bind>:<port>.

Scenario: Broker section with all fields

  • GIVEN a TOML file with [broker] containing enabled = true, port = 9200, bind = "127.0.0.1"
  • WHEN the file is loaded
  • THEN broker.enabled SHALL be true, broker.port SHALL be 9200, broker.bind SHALL be "127.0.0.1"

Scenario: Broker section defaults

  • GIVEN a TOML file without a [broker] section
  • WHEN the file is loaded
  • THEN broker SHALL have enabled = false, port = 9119, bind = "127.0.0.1"

Scenario: Partial broker section

  • GIVEN a TOML file with [broker] containing only enabled = true
  • WHEN the file is loaded
  • THEN broker.enabled SHALL be true, broker.port SHALL be 9119, broker.bind SHALL be "127.0.0.1"

Scenario: BrokerConfig url method

  • GIVEN BrokerConfig { enabled: true, port: 9200, bind: "127.0.0.1" }
  • WHEN url() is called
  • THEN the result SHALL be "http://127.0.0.1:9200"

Scenario: Broker config round-trips through save and load

  • GIVEN a config with [broker] fully populated
  • WHEN saved and loaded back
  • THEN all broker fields SHALL match the original

Requirement: Internal callers SHALL preserve v0.4 behaviour by passing None

All production call sites of load_config inside the git-paw binary SHALL pass None as the user_config_path argument, so production behaviour is byte-identical to the v0.4 single-argument load_config(repo_root) API.

The Option<&Path> argument SHALL exist only to give test code a discoverable way to isolate the user-level config read from whatever exists at the dev machine’s platform-default path. No production code path SHALL pass Some(_).

Scenario: All production call sites pass None

  • GIVEN the source tree
  • WHEN every call site of config::load_config inside src/ is inspected
  • THEN every call SHALL be of the form config::load_config(&repo_root, None) (modulo whitespace and the exact name of the repo_root binding)
  • AND no production call site SHALL pass Some(_)

Test: covered by cargo build (compile-time) plus a focused grep-style assertion in src/main.rs::tests or equivalent — see tasks.md task 2.

Scenario: Production behaviour is byte-identical to v0.4

  • GIVEN a binary built from this change
  • AND the same .git-paw/config.toml and platform-default user config that a v0.4 binary would read
  • WHEN any production command that calls load_config runs (e.g. git paw start, git paw add-cli, git paw dashboard)
  • THEN the merged PawConfig the command operates on SHALL be equal to the merged PawConfig v0.4 would have produced

Test: behaviour preserved by construction (every production call passes None); verified by the v0.4 test suite continuing to pass unchanged plus the new load_config_with_none_reads_platform_default_global unit test.

Requirement: MCP configuration section

The system SHALL parse an optional [mcp] section in .git-paw/config.toml into an McpConfig exposed as PawConfig.mcp. The struct SHALL contain an optional name: Option<String> field defaulting to None when absent, and an allow_repo_writes: bool field defaulting to false when absent and omitted from serialized output when false. A config file with no [mcp] section SHALL load with McpConfig::default() (name: None, allow_repo_writes: false), so pre-existing configs load unchanged. When the user-level and repository configs are merged, allow_repo_writes SHALL take the repository config’s value only; a value set in the user-level config SHALL have no effect.

Scenario: Config with [mcp] name parses the field

  • GIVEN a config file with [mcp] setting name = "my-project"
  • WHEN the config is loaded
  • THEN PawConfig.mcp.name SHALL be Some("my-project")

Scenario: Config with [mcp] allow_repo_writes parses the field

  • GIVEN a repository config file with [mcp] setting allow_repo_writes = true
  • WHEN the config is loaded
  • THEN PawConfig.mcp.allow_repo_writes SHALL be true

Scenario: Config without [mcp] section loads with defaults

  • GIVEN a config file with no [mcp] section
  • WHEN the config is loaded
  • THEN PawConfig.mcp SHALL equal McpConfig::default() with name set to None and allow_repo_writes set to false
  • AND loading SHALL NOT error

Scenario: The user-level config cannot enable repository writes

  • GIVEN a user-level config setting [mcp] allow_repo_writes = true
  • AND a repository config that does not set it
  • WHEN the merged config is loaded
  • THEN PawConfig.mcp.allow_repo_writes SHALL be false

Scenario: MCP config survives round-trip serialization

  • GIVEN a PawConfig whose mcp.name is set and whose mcp.allow_repo_writes is true
  • WHEN it is serialized to TOML and re-parsed
  • THEN the re-parsed mcp field SHALL equal the original

Scenario: A false switch is not serialized

  • GIVEN a PawConfig whose mcp.allow_repo_writes is false
  • WHEN it is serialized to TOML
  • THEN the output SHALL NOT contain allow_repo_writes

Requirement: Worktree placement configuration field

The system SHALL support an optional worktree_placement field on PawConfig accepting the string values "child" or "sibling". When the field is absent, the effective placement SHALL be sibling, preserving v0.7.0 behaviour. The field SHALL participate in the repo-overrides-global merge as a scalar (repo value wins). A default-valued field SHALL be skipped on serialization so that a config without an explicit worktree_placement round-trips without the field appearing.

Scenario: worktree_placement set to child

  • GIVEN a TOML file with worktree_placement = "child"
  • WHEN the config is loaded
  • THEN the effective placement SHALL be child

Scenario: worktree_placement set to sibling

  • GIVEN a TOML file with worktree_placement = "sibling"
  • WHEN the config is loaded
  • THEN the effective placement SHALL be sibling

Scenario: worktree_placement absent defaults to sibling

  • GIVEN a TOML file with no worktree_placement field
  • WHEN the config is loaded
  • THEN the effective placement SHALL be sibling

Scenario: Repo placement overrides global

  • GIVEN global config has worktree_placement = "sibling" and repo config has worktree_placement = "child"
  • WHEN configs are merged
  • THEN the merged placement SHALL be child

Scenario: Placement config survives round-trip

  • GIVEN a PawConfig with worktree_placement = "child"
  • WHEN it is serialized to TOML and re-parsed
  • THEN the re-parsed placement SHALL be child

Scenario: Pre-existing config without the field loads unchanged

  • GIVEN a .git-paw/config.toml written before this field exists
  • WHEN the config is loaded
  • THEN loading SHALL NOT error
  • AND the effective placement SHALL be sibling

Requirement: Broker log panel height configuration

The system SHALL support an optional height_lines field on the [dashboard.broker_log] table, a positive integer giving the number of terminal rows the Broker log panel occupies when visible. When the field or the [dashboard.broker_log] table is absent, the effective height SHALL default to a value strictly greater than the v0.6.0 fixed 12 (the documented default), so configs written before this field exists — including existing configs — load unchanged. The field SHALL participate in the repo-overrides-global merge as a scalar (repo value wins) and SHALL survive round-trip serialization.

Scenario: height_lines explicitly configured

  • GIVEN a TOML file with [dashboard.broker_log] height_lines = 24
  • WHEN the config is loaded
  • THEN dashboard.broker_log.height_lines SHALL be 24

Scenario: height_lines absent uses the default

  • GIVEN a TOML file with a [dashboard.broker_log] table that omits height_lines
  • WHEN the config is loaded
  • THEN dashboard.broker_log.height_lines SHALL equal the documented default, which SHALL be strictly greater than 12

Scenario: Pre-existing config without the field loads unchanged

  • GIVEN a .git-paw/config.toml written before this field exists (e.g. a [dashboard] section with no broker_log table)
  • WHEN the config is loaded
  • THEN loading SHALL NOT error
  • AND dashboard.broker_log.height_lines SHALL equal the documented default

Scenario: height_lines round-trips through save and load

  • GIVEN a config with [dashboard.broker_log] height_lines = 30
  • WHEN the config is serialized to TOML and re-parsed
  • THEN the re-parsed dashboard.broker_log.height_lines SHALL be 30

Requirement: Configuration reference documents the specs type value

docs/src/configuration/README.md’s commented [specs] example SHALL document type as accepting "openspec", "markdown", AND "speckit". The v0.4-era example listing only "openspec" and "markdown" SHALL be updated to add "speckit".

Scenario: Configuration reference lists all three specs.type values

  • WHEN the [specs] example in docs/src/configuration/README.md is inspected
  • THEN it documents "speckit" as a valid value alongside "openspec" and "markdown"

docs/src/configuration/README.md SHALL NOT contain Markdown links pointing at anchors that do not exist in their target files. Specifically the link previously pointing at coordination.md#automatic-conflict-detection-v050 SHALL either target a real anchor that exists in coordination.md post-refresh or be removed.

Scenario: Configuration reference has no broken conflict-detection anchor

  • WHEN docs/src/configuration/README.md is inspected
  • THEN any [...]( ... ) link targeting coordination.md points at an anchor that exists in coordination.md (verified by the anchor heading being present in the target file’s rendered TOC)

Requirement: Configuration reference documents the configuration tables

docs/src/configuration/README.md SHALL include subsections documenting [supervisor.conflict], [supervisor.learnings_config], and [governance]. Each subsection SHALL list every field, its type, its default value (where applicable), and a one-line description. It SHALL also document [supervisor.auto_approve] and [supervisor.common_dev_allowlist] as deprecated: both are still accepted, have no effect, print a deprecation warning when present, and can be removed.

Scenario: Configuration reference documents supervisor.conflict

  • WHEN docs/src/configuration/README.md is inspected
  • THEN it contains a subsection or table for [supervisor.conflict]
  • AND the subsection mentions window_seconds, warn_on_intent_overlap, and escalate_on_violation

Scenario: Configuration reference marks the auto-approval tables deprecated

  • WHEN docs/src/configuration/README.md is inspected
  • THEN it names [supervisor.auto_approve] and [supervisor.common_dev_allowlist]
  • AND states that both are deprecated and have no effect
  • AND points to the agent CLI’s own permission rules, or the full-auto approval level inside a sandbox, as the replacement

Scenario: Configuration reference documents supervisor.learnings_config

  • WHEN docs/src/configuration/README.md is inspected
  • THEN it contains a subsection or table for [supervisor.learnings_config]
  • AND the subsection mentions flush_interval_seconds

Scenario: Configuration reference documents governance

  • WHEN docs/src/configuration/README.md is inspected
  • THEN it contains a subsection or table for [governance]
  • AND the subsection lists all five fields: adr, test_strategy, security, dod, constitution

Interactive Selection

Purpose

Interactive selection prompts for choosing branches and AI CLIs. Supports uniform (same CLI for all branches) and per-branch assignment modes, with CLI flags that skip prompts. Logic is separated from UI via the Prompter trait for testability.

Requirements

Requirement: CLI flags skip all prompts when both provided

When both --cli and --branches flags are provided, the system SHALL skip all interactive prompts and map the CLI to all specified branches.

Scenario: Both flags skip all prompts

  • GIVEN --cli alpha and --branches feature/auth,fix/api flags
  • WHEN run_selection() is called
  • THEN it SHALL return mappings without invoking any prompts

Test: interactive::tests::both_flags_skips_all_prompts_and_maps_cli_to_all_branches

Requirement: CLI flag skips CLI prompt but prompts for branches

When only --cli is provided, the system SHALL prompt for branch selection but skip CLI selection.

Scenario: CLI flag provided, branches prompted

  • GIVEN --cli alpha flag and no branches flag
  • WHEN run_selection() is called
  • THEN branch selection SHALL be prompted and the flag CLI SHALL be used

Test: interactive::tests::cli_flag_skips_cli_prompt_but_prompts_for_branches

Requirement: Branches flag skips branch prompt but prompts for CLI

When only --branches is provided, the system SHALL skip branch selection but prompt for CLI assignment.

Scenario: Branches flag provided, CLI prompted in uniform mode

  • GIVEN --branches flag and no CLI flag
  • WHEN user selects uniform mode
  • THEN the selected CLI SHALL be mapped to all flagged branches

Test: interactive::tests::branches_flag_skips_branch_prompt_but_prompts_for_cli_uniform

Requirement: Uniform mode maps same CLI to all branches

In uniform mode, the system SHALL assign the selected CLI to every selected branch.

Scenario: Uniform mode selection

  • GIVEN user selects uniform mode, picks 2 branches and 1 CLI
  • WHEN run_selection() completes
  • THEN both branches SHALL be mapped to the same CLI

Test: interactive::tests::uniform_mode_maps_same_cli_to_all_selected_branches

Requirement: Per-branch mode maps different CLIs to each branch

In per-branch mode, the system SHALL prompt for a CLI for each selected branch individually.

Scenario: Per-branch mode selection

  • GIVEN user selects per-branch mode with 2 branches
  • WHEN different CLIs are chosen for each branch
  • THEN each branch SHALL be mapped to its respective CLI

Test: interactive::tests::per_branch_mode_maps_different_cli_to_each_branch

Scenario: Per-branch mode with branches flag

  • GIVEN branches provided via flag and per-branch mode selected
  • WHEN different CLIs are chosen
  • THEN each flagged branch SHALL be mapped to its selected CLI

Test: interactive::tests::per_branch_mode_with_branches_flag

Requirement: Error when no CLIs available

The system SHALL return PawError::NoCLIsFound when the CLI list is empty.

Scenario: Empty CLI list

  • GIVEN no CLIs available
  • WHEN run_selection() is called
  • THEN it SHALL return Err(PawError::NoCLIsFound)

Test: interactive::tests::no_clis_available_returns_error

Requirement: Error when no branches available

The system SHALL return PawError::BranchError when the branch list is empty.

Scenario: Empty branch list

  • GIVEN no branches available
  • WHEN run_selection() is called
  • THEN it SHALL return Err(PawError::BranchError)

Test: interactive::tests::no_branches_available_returns_error

Requirement: User cancellation propagates as PawError::UserCancelled

The system SHALL propagate cancellation (Ctrl+C or empty selection) as PawError::UserCancelled.

Scenario: User cancels branch selection

  • GIVEN user presses Ctrl+C during branch selection
  • WHEN run_selection() is called
  • THEN it SHALL return Err(PawError::UserCancelled)

Test: interactive::tests::user_cancels_branch_selection_returns_cancelled

Scenario: User selects no branches

  • GIVEN user confirms with zero branches selected
  • WHEN run_selection() is called
  • THEN it SHALL return Err(PawError::UserCancelled)

Test: interactive::tests::user_selects_no_branches_returns_cancelled

Scenario: User cancels CLI selection

  • GIVEN user presses Ctrl+C during CLI selection
  • WHEN run_selection() is called
  • THEN it SHALL return Err(PawError::UserCancelled)

Test: interactive::tests::user_cancels_cli_selection_returns_cancelled

Requirement: Subset branch selection

The system SHALL support selecting a subset of available branches.

The branch picker (select_branches) SHALL present a fuzzy-filter multi-select. The user SHALL be able to type a query that filters the visible branch candidates by case-insensitive substring match against the branch name. An empty query SHALL show the full list of available branches in their original order. Toggling selection SHALL operate on the currently visible (filtered) rows; branches selected under one query that are then hidden by a different query SHALL remain selected and SHALL be returned on confirm. Clearing the query (back to empty) SHALL restore the full list with all prior selections intact.

Cancellation semantics SHALL be unchanged: pressing Ctrl+C SHALL yield PawError::UserCancelled, and confirming with zero branches selected SHALL yield PawError::UserCancelled.

Scenario: Selecting one of two branches

  • GIVEN 2 available branches
  • WHEN user selects only the second
  • THEN only that branch SHALL appear in the result

Test: interactive::tests::selecting_subset_of_branches_works

Scenario: Typing a query filters the branch candidates

  • GIVEN branches feature/auth, fix/api, feature/login
  • WHEN the user types feature into the branch picker filter
  • THEN only feature/auth and feature/login SHALL be visible
  • AND fix/api SHALL NOT be visible

Scenario: Empty filter shows the full branch list

  • GIVEN the branch picker with 3 candidates
  • WHEN the filter query is empty
  • THEN all 3 branches SHALL be visible in their original order

Scenario: Selection under an active filter is preserved when the filter changes

  • GIVEN branches feature/auth, fix/api, feature/login
  • WHEN the user types feature, toggles feature/auth, then changes the query to fix and toggles fix/api, then confirms
  • THEN the result SHALL contain both feature/auth and fix/api

Scenario: Clearing the filter restores the full list with selections intact

  • GIVEN the branch picker where the user has typed feature and toggled feature/auth
  • WHEN the user clears the query back to empty
  • THEN all branches SHALL be visible again
  • AND feature/auth SHALL still be marked as selected

Scenario: User cancels the filtered branch picker via Ctrl+C

  • GIVEN the branch picker is open with an active filter query
  • WHEN the user presses Ctrl+C
  • THEN select_branches SHALL return Err(PawError::UserCancelled)

Scenario: Confirming with zero branches selected cancels

  • GIVEN the branch picker is open
  • WHEN the user confirms without toggling any branch
  • THEN select_branches SHALL return Err(PawError::UserCancelled)

Requirement: CliMode display format

The CliMode enum SHALL display as human-readable descriptions.

Scenario: CliMode display strings

  • GIVEN CliMode::Uniform and CliMode::PerBranch
  • WHEN formatted with Display
  • THEN they SHALL render as "Same CLI for all branches" and "Different CLI per branch"

Test: interactive::tests::cli_mode_display

Requirement: CliInfo display format

CliInfo SHALL display as the binary name when it matches the display name, or as "DisplayName (binary)" when they differ.

Scenario: Same display and binary name

  • GIVEN a CliInfo where display_name equals binary_name
  • WHEN formatted with Display
  • THEN it SHALL render as just the binary name

Test: interactive::tests::cli_info_display_same_names

Scenario: Different display and binary name

  • GIVEN a CliInfo where display_name differs from binary_name
  • WHEN formatted with Display
  • THEN it SHALL render as "DisplayName (binary_name)"

Test: interactive::tests::cli_info_display_different_names

Requirement: CLI picker with optional pre-selection

The select_cli method on the Prompter trait SHALL accept an optional default CLI name for pre-selection in the interactive picker.

Scenario: Picker with default pre-selected

  • WHEN select_cli() is called with default = Some("claude") and "claude" is in the CLI list
  • THEN the picker SHALL display with "claude" highlighted as the default selection

Scenario: Picker without default

  • WHEN select_cli() is called with default = None
  • THEN the picker SHALL display with the first item selected (no pre-selection)

Scenario: Default CLI not in available list

  • WHEN select_cli() is called with default = Some("nonexistent") and that CLI is not available
  • THEN the picker SHALL display with no pre-selection (graceful fallback)

Requirement: Spec multi-select picker

The Prompter trait SHALL include a select_specs(&self, specs: &[SpecEntry]) -> Result<Vec<SpecEntry>, PawError> method that presents a multi-select picker for spec entries and returns the user’s chosen subset.

The default TerminalPrompter implementation SHALL display one row per logical spec unit (a feature in Spec Kit terms; a change in OpenSpec; a file in plain Markdown), grouping multiple SpecEntry values that decompose from the same Spec Kit feature into a single row. Selecting a row SHALL cause every SpecEntry belonging to that row’s logical unit to be returned.

Each row’s display label SHALL include the unit identifier and, for Spec Kit features that decompose into multiple worktrees, a worktree-count hint summarising the breakdown (e.g. "003-user-list — 3 worktrees: 2 [P] + 1 phase/"). For OpenSpec changes and Markdown specs, the label SHALL be the unit identifier alone (one entry → one worktree, no hint needed).

The spec picker SHALL present a fuzzy-filter multi-select that behaves identically to the branch picker (select_branches). The user SHALL be able to type a query that filters the visible rows by case-insensitive substring match against the row’s display label. An empty query SHALL show every grouped row in its original order. Toggling selection SHALL operate on the currently visible (filtered) rows; rows selected under one query that are then hidden by a different query SHALL remain selected and SHALL be returned on confirm. Clearing the query (back to empty) SHALL restore the full set of rows with all prior selections intact. The logical-unit grouping and worktree-count hint labels SHALL be unchanged from the non-filtered behavior — filtering matches against the displayed labels, and selecting a visible row SHALL still expand to every underlying SpecEntry for that unit.

The picker SHALL behave the same way as select_branches for cancellation:

  • User pressing Ctrl+C → PawError::UserCancelled.
  • User confirming with zero rows selected → PawError::UserCancelled.

Scenario: select_specs returns the chosen subset

  • GIVEN 3 OpenSpec entries add-auth, fix-session, add-logging
  • WHEN the user toggles add-auth and add-logging and presses enter
  • THEN select_specs returns a Vec containing those two entries

Scenario: select_specs groups Spec Kit entries by feature

  • GIVEN 4 SpecEntry values from a Spec Kit project: two [P] entries (003-user-list-T009, 003-user-list-T010), one consolidated entry (003-user-list-phase-2), and one entry from a different feature (004-error-handling-phase-1)
  • WHEN the picker renders
  • THEN it displays exactly 2 rows — one per logical feature
  • AND the row for feature 003-user-list shows a worktree-count hint summarising the 3 underlying entries (2 [P] + 1 phase/)

Scenario: Selecting a Spec Kit feature row pulls in all its entries

  • GIVEN a picker rendering one row for feature 003-user-list (3 underlying SpecEntry values)
  • WHEN the user selects only that row and confirms
  • THEN select_specs returns all 3 underlying SpecEntry values

Scenario: User cancels spec picker via Ctrl+C

  • GIVEN the spec picker is open
  • WHEN the user presses Ctrl+C
  • THEN select_specs returns Err(PawError::UserCancelled)

Scenario: User confirms with zero rows selected

  • GIVEN the spec picker is open with N rows displayed
  • WHEN the user confirms without toggling any row
  • THEN select_specs returns Err(PawError::UserCancelled)

Scenario: Typing a query filters the spec rows

  • GIVEN OpenSpec entries add-auth, fix-session, add-logging
  • WHEN the user types add into the spec picker filter
  • THEN only the rows for add-auth and add-logging SHALL be visible
  • AND the row for fix-session SHALL NOT be visible

Scenario: Empty filter shows every grouped spec row

  • GIVEN the spec picker with 3 grouped rows
  • WHEN the filter query is empty
  • THEN all 3 rows SHALL be visible in their original order

Scenario: Selecting a filtered Spec Kit row still expands to all its entries

  • GIVEN a Spec Kit feature 003-user-list (3 underlying SpecEntry values) and another feature 004-error-handling
  • WHEN the user types 003, selects the visible 003-user-list row, and confirms
  • THEN select_specs SHALL return all 3 underlying SpecEntry values for 003-user-list

Scenario: Clearing the spec filter restores all rows with selections intact

  • GIVEN the spec picker where the user has typed add and toggled the add-auth row
  • WHEN the user clears the query back to empty
  • THEN all rows SHALL be visible again
  • AND the add-auth row SHALL still be marked as selected

Requirement: Spec picker requires an interactive terminal

When the start command would invoke select_specs (i.e. the user passed --specs with no values), the system SHALL detect whether stdin is connected to a terminal before invoking the picker. If stdin is NOT a terminal (CI, scripted invocation, redirected input), the system SHALL exit with an actionable error pointing at the explicit forms (--specs NAME[,NAME...] to narrow, --from-all-specs to launch every discovered spec).

The system SHALL NOT block waiting for picker input on a non-interactive stdin.

Scenario: Bare –specs in non-TTY environment exits with guidance

  • GIVEN the user runs git paw start --specs with stdin redirected (or no controlling terminal)
  • WHEN the start command attempts to open the picker
  • THEN the command SHALL exit with a non-zero status before any picker UI is drawn
  • AND the error message SHALL point the user at --specs NAME[,NAME...] and --from-all-specs

Scenario: Bare –specs on TTY proceeds to picker

  • GIVEN the user runs git paw start --specs from an interactive terminal
  • WHEN the start command runs
  • THEN the picker SHALL open
  • AND no TTY-required error SHALL be emitted

Requirement: Spec name resolution for narrow mode

When --specs is passed with one or more values (narrow mode), the system SHALL resolve each value against the discovered SpecEntry set returned by scan_specs(). Resolution SHALL apply the following matching strategies in order, taking the first that succeeds:

  1. Exact match on SpecEntry.id (case-sensitive). For Spec Kit, this matches a specific decomposed entry like 003-user-list-T009. For OpenSpec / Markdown, it matches the change name or filename stem.
  2. Spec Kit feature match on the feature directory prefix of the SpecEntry.id (e.g. 003-user-list matches all entries belonging to that feature). When the value matches a Spec Kit feature unambiguously, ALL entries belonging to that feature SHALL be selected.
  3. Spec Kit numeric prefix match (e.g. 003) matching a Spec Kit feature directory name’s leading numeric portion. The match SHALL succeed only when exactly one feature directory begins with the given prefix followed by a non-digit boundary; ambiguous prefixes SHALL be rejected (see below).

Resolution SHALL fail (and the start command SHALL exit before any worktrees are created) when:

  • A value matches no SpecEntry and no feature.
  • A Spec Kit numeric prefix matches more than one feature directory (ambiguous).

The resulting error SHALL list the unresolved or ambiguous names AND the discovered candidate names so the user can correct quickly.

Scenario: Exact match resolves to a single SpecEntry

  • GIVEN a discovered set including OpenSpec change add-auth
  • WHEN the user passes --specs add-auth
  • THEN the resolved set SHALL contain exactly that one SpecEntry

Scenario: Spec Kit feature name resolves to all decomposed entries

  • GIVEN a Spec Kit feature 003-user-list decomposing into 3 SpecEntry values (2 [P] + 1 consolidated)
  • WHEN the user passes --specs 003-user-list
  • THEN the resolved set SHALL contain all 3 entries belonging to that feature

Scenario: Spec Kit numeric prefix resolves unambiguously

  • GIVEN a Spec Kit project with a single feature directory beginning with 003- (e.g. 003-user-list)
  • WHEN the user passes --specs 003
  • THEN the resolved set SHALL contain all entries belonging to that feature

Scenario: Ambiguous numeric prefix is rejected

  • GIVEN a Spec Kit project containing both 003-user-list and 003a-experiment
  • WHEN the user passes --specs 003
  • THEN the start command SHALL exit with an error
  • AND the error message SHALL list both candidate feature names

Scenario: Unknown spec name is rejected with candidate list

  • GIVEN a discovered set containing add-auth, fix-session
  • WHEN the user passes --specs no-such-spec
  • THEN the start command SHALL exit with an error
  • AND the error message SHALL include no-such-spec
  • AND the error message SHALL list add-auth and fix-session as candidates

Scenario: Multiple values are resolved independently

  • GIVEN a discovered set including add-auth, fix-session, add-logging
  • WHEN the user passes --specs add-auth,add-logging
  • THEN the resolved set SHALL contain entries for add-auth and add-logging
  • AND the resolved set SHALL NOT contain the entry for fix-session

Scenario: Partial-failure batches do not partially start

  • GIVEN a user passes --specs add-auth,no-such-spec
  • WHEN resolution runs
  • THEN the start command SHALL exit with the unknown-name error
  • AND no worktrees SHALL be created
  • AND the error message SHALL include no-such-spec (the unresolved name)

Requirement: Configured default CLI is pre-selected in the interactive CLI picker

run_selection() SHALL accept an optional default CLI name and SHALL pass it to the CLI picker in both assignment modes: to select_cli() when the user chooses uniform mode, and to select_cli_for_branch() for every branch when the user chooses per-branch mode. The Prompter trait’s select_cli_for_branch() SHALL therefore accept an optional default with the same semantics as select_cli()’s. Every caller of the interactive selection flow (git paw start, and the supervisor launch’s branch selection) SHALL supply the loaded configuration’s default_cli.

default_cli SHALL only choose the picker’s starting entry. It SHALL NOT skip the picker: when --cli is not given, the mode prompt and the CLI picker (or, in per-branch mode, each per-branch picker) SHALL still appear, and the user can accept the pre-selected CLI or pick any other listed CLI. If default_cli is unset, or names a CLI that is not available, the picker SHALL start on the first entry (the existing select_cli() behaviour). --cli SHALL still skip the CLI prompt entirely.

Scenario: default_cli pre-selects in the uniform-mode picker

  • GIVEN default_cli = "beta", no --cli flag, and the user chooses uniform mode
  • WHEN run_selection() is called with the default CLI "beta"
  • THEN select_cli() SHALL receive default = Some("beta")
  • AND every selected branch SHALL map to the CLI the picker returns

Scenario: default_cli pre-selects in every per-branch picker

  • GIVEN default_cli = "beta", no --cli flag, two selected branches, and the user chooses per-branch mode
  • WHEN run_selection() is called with the default CLI "beta"
  • THEN select_cli_for_branch() SHALL receive default = Some("beta") for each branch
  • AND each branch SHALL map to the CLI its own picker returns

Scenario: The picker is still shown when default_cli is set

  • GIVEN default_cli = "beta" and no --cli flag
  • WHEN run_selection() is called
  • THEN the mode prompt and the CLI picker SHALL both be invoked
  • AND a different CLI the user picks there SHALL be the one mapped to the branches

Scenario: No default_cli starts the picker on the first entry

  • GIVEN no default_cli and no --cli flag, with the user choosing uniform mode
  • WHEN run_selection() is called with no default CLI
  • THEN select_cli() SHALL receive default = None

Scenario: Per-branch picker starts on a configured default

  • GIVEN a terminal prompter and a CLI list in which "beta" is the second entry
  • WHEN select_cli_for_branch() is called with default = Some("beta")
  • THEN the picker SHALL display with "beta" highlighted as the default selection
  • AND with default = None or a CLI that is not listed, it SHALL start on the first entry

Scenario: –cli flag wins over default_cli

  • GIVEN --cli alpha and default_cli = "beta"
  • WHEN run_selection() is called
  • THEN no mode prompt or CLI picker SHALL be invoked
  • AND every selected branch SHALL map to "alpha"

Scenario: Bare git paw start pre-selects the configured default CLI

  • GIVEN a repository configuring two available CLIs and default_cli naming the one that sorts second in the picker
  • WHEN the user runs git paw start --branches <branch> --dry-run in a terminal, chooses uniform mode, and confirms the CLI picker without moving the cursor
  • THEN the dry-run plan SHALL map the branch to the configured default_cli

Error Handling

Purpose

Define the central error type PawError used across all git-paw modules. Every variant carries an actionable, user-facing message and maps to a process exit code.

Requirements

Requirement: Actionable error messages for each variant

Each PawError variant SHALL produce a user-facing message that explains the problem and suggests a remedy where appropriate.

Scenario: NotAGitRepo is actionable

  • GIVEN PawError::NotAGitRepo
  • WHEN formatted with Display
  • THEN the message SHALL mention “git repository” and name the tool

Test: error::tests::test_not_a_git_repo_is_actionable

Scenario: TmuxNotInstalled includes install instructions

  • GIVEN PawError::TmuxNotInstalled
  • WHEN formatted with Display
  • THEN the message SHALL include both brew install and apt install hints

Test: error::tests::test_tmux_not_installed_includes_install_instructions

Scenario: NoCLIsFound suggests add-cli

  • GIVEN PawError::NoCLIsFound
  • WHEN formatted with Display
  • THEN the message SHALL suggest the add-cli command

Test: error::tests::test_no_clis_found_suggests_add_cli

Scenario: WorktreeError includes detail

  • GIVEN PawError::WorktreeError("failed to create")
  • WHEN formatted with Display
  • THEN the message SHALL include the inner detail string

Test: error::tests::test_worktree_error_includes_detail

Scenario: SessionError includes detail

  • GIVEN PawError::SessionError("file corrupt")
  • WHEN formatted with Display
  • THEN the message SHALL include the inner detail string

Test: error::tests::test_session_error_includes_detail

Scenario: ConfigError includes detail

  • GIVEN PawError::ConfigError("invalid toml")
  • WHEN formatted with Display
  • THEN the message SHALL include the inner detail string

Test: error::tests::test_config_error_includes_detail

Scenario: BranchError includes detail

  • GIVEN PawError::BranchError("not found")
  • WHEN formatted with Display
  • THEN the message SHALL include the inner detail string

Test: error::tests::test_branch_error_includes_detail

Scenario: UserCancelled has a message

  • GIVEN PawError::UserCancelled
  • WHEN formatted with Display
  • THEN the message SHALL not be empty

Test: error::tests::test_user_cancelled_is_not_empty

Scenario: TmuxError includes detail

  • GIVEN PawError::TmuxError("session failed")
  • WHEN formatted with Display
  • THEN the message SHALL include the inner detail string

Test: error::tests::test_tmux_error_includes_detail

Scenario: CliNotFound includes CLI name

  • GIVEN PawError::CliNotFound("my-agent")
  • WHEN formatted with Display
  • THEN the message SHALL include the missing CLI name

Test: error::tests::test_cli_not_found_includes_cli_name

Requirement: Exit codes distinguish cancellation from errors

UserCancelled and NotAttempted SHALL exit with code 2; all other errors SHALL exit with code 1. NotAttempted reports that a command wrote nothing because a condition for proceeding was not met — a declined confirmation, a confirmation that could not be shown because standard input is not a terminal, or a requested item that is not supported or not enabled for its target. Its message SHALL state that condition and, where one exists, name the flag or command that proceeds, and SHALL NOT read only Cancelled..

Scenario: UserCancelled exit code

  • GIVEN PawError::UserCancelled
  • WHEN exit_code() is called
  • THEN it SHALL return 2

Test: error::tests::test_user_cancelled_exit_code

Scenario: NotAttempted exit code and message

  • GIVEN PawError::NotAttempted built for a confirmation that could not be shown, naming --yes
  • WHEN exit_code() is called and the error is formatted with Display
  • THEN exit_code() SHALL return 2
  • AND the message SHALL name --yes
  • AND the message SHALL NOT be Cancelled.

Test: error::tests::test_not_attempted_exit_code_and_message

Scenario: General errors exit code

  • GIVEN any error variant other than UserCancelled and NotAttempted
  • WHEN exit_code() is called
  • THEN it SHALL return 1

Test: error::tests::test_general_errors_exit_code

Requirement: Exit method prints to stderr and exits with correct code

PawError::exit() SHALL print the error message to stderr and terminate with the appropriate exit code.

Scenario: NotAGitRepo exits with code 1

  • GIVEN the binary is run outside a git repository
  • WHEN the error propagates to exit()
  • THEN the process SHALL exit with code 1 and stderr SHALL contain the error message

Test: e2e_tests::error_exit_code_is_1_for_not_a_git_repo

Scenario: ConfigError exits with code 1

  • GIVEN a nonexistent preset is requested
  • WHEN the error propagates to exit()
  • THEN the process SHALL exit with code 1 and stderr SHALL mention “not found”

Test: e2e_tests::error_exit_code_is_1_for_preset_not_found

Requirement: Debug representation is derivable

All PawError variants SHALL support Debug formatting.

Scenario: Debug format includes variant name

  • GIVEN PawError::NotAGitRepo
  • WHEN formatted with Debug
  • THEN the output SHALL contain "NotAGitRepo"

Test: error::tests::test_debug_derived

Requirement: SkillError variants with actionable messages

The system SHALL define a SkillError type with variants for skill loading failures. Each variant SHALL produce a user-facing message that explains the problem and suggests a remedy. SkillError SHALL be wrappable inside PawError as a variant.

The following variants SHALL exist:

  • UnknownSkill { name: String } — no embedded or user override found for the requested skill name
  • UserOverrideRead { path: PathBuf, source: std::io::Error } — a user override file exists but cannot be read

Scenario: UnknownSkill is actionable

  • GIVEN SkillError::UnknownSkill { name: "nonexistent" }
  • WHEN formatted with Display
  • THEN the message SHALL mention the skill name "nonexistent" and indicate no embedded default exists

Scenario: UserOverrideRead is actionable

  • GIVEN SkillError::UserOverrideRead { path: "/home/user/.config/git-paw/agent-skills/coordination.md", .. }
  • WHEN formatted with Display
  • THEN the message SHALL include the file path and suggest checking permissions

Scenario: SkillError exit code

  • GIVEN any SkillError variant wrapped in PawError
  • WHEN exit_code() is called
  • THEN it SHALL return 1

Requirement: BrokerError variants with actionable messages

The system SHALL define a BrokerError type with variants for broker-specific failures. Each variant SHALL produce a user-facing message that explains the problem and suggests a remedy. BrokerError SHALL be wrappable inside PawError as a variant.

The following variants SHALL exist:

  • PortInUse { port: u16, source: std::io::Error } — the configured port is already occupied by a process that is not a git-paw broker; source carries the underlying bind/probe io::Error so callers can chain or log the original cause
  • PortHeldByOtherRepo { port: u16, repo_root: Option<String> } — the configured port is held by a git-paw broker serving a different repository, or one that reports no repository at all (an older git-paw). Distinct from PortInUse, because the two have different causes and different fixes and must be distinguishable to users and to tests
  • ProbeTimeout { port: u16 } — the stale-broker probe timed out
  • BindFailed(std::io::Error) — socket bind failed for a reason other than port-in-use
  • RuntimeFailed(std::io::Error) — tokio runtime construction failed

PortInUse.source SHALL be marked #[source] (or equivalent thiserror attribute) so it participates in std::error::Error::source() chains. The Display output SHALL NOT include the source by default — it is reserved for explicit chaining via {:?} or programmatic .source() access — to avoid duplicated diagnostics in user-facing CLI output.

PortHeldByOtherRepo.repo_root is reported by whatever process answered the probe and is therefore untrusted; it SHALL be stripped of control characters and capped in length before it appears in the Display output.

Scenario: PortInUse is actionable

  • GIVEN BrokerError::PortInUse { port: 9119, source: io::Error::from(io::ErrorKind::AddrInUse) }
  • WHEN formatted with Display
  • THEN the message SHALL mention port 9119 and suggest changing [broker] port in config
  • AND the message SHALL NOT contain the underlying io::Error Display text

Scenario: PortInUse exposes underlying cause

  • GIVEN a PortInUse value with an AddrInUse source
  • WHEN std::error::Error::source() is called on it
  • THEN the result SHALL be Some(&dyn Error) referencing the wrapped io::Error

Scenario: PortHeldByOtherRepo is actionable

  • GIVEN BrokerError::PortHeldByOtherRepo { port: 9119, repo_root: Some("/home/you/other-repo".to_string()) }
  • WHEN formatted with Display
  • THEN the message SHALL mention port 9119
  • AND the message SHALL name the holding repository /home/you/other-repo
  • AND the message SHALL tell the user to set a distinct [broker] port

Scenario: PortHeldByOtherRepo names an unknown holder without guessing

  • GIVEN BrokerError::PortHeldByOtherRepo { port: 9119, repo_root: None }
  • WHEN formatted with Display
  • THEN the message SHALL mention port 9119 and the [broker] port fix
  • AND the message SHALL say an older git-paw broker may be holding the port
  • AND the message SHALL NOT claim the broker belongs to the caller

Scenario: PortHeldByOtherRepo renders untrusted text safely

  • GIVEN a PortHeldByOtherRepo whose repo_root was reported with ANSI escape sequences and other control characters
  • WHEN formatted with Display
  • THEN the rendered message SHALL contain no control characters

Scenario: ProbeTimeout is actionable

  • GIVEN BrokerError::ProbeTimeout { port: 9119 }
  • WHEN formatted with Display
  • THEN the message SHALL mention the port and suggest checking for stuck processes

Scenario: BrokerError exit code

  • GIVEN any BrokerError variant wrapped in PawError
  • WHEN exit_code() is called
  • THEN it SHALL return 1

Requirement: A wrapping error’s Display does not embed a redundant error prefix

A PawError variant that wraps a message SHALL NOT embed the word error in its own Display, because the top-level exit path prepends error: when printing. Doubling it produces user-facing output like error: Worktree error: …. Each string-wrapping variant SHALL render as <Category>: <message> (e.g. Worktree: …, Session: …), so the printed line carries exactly one error: prefix.

Scenario: A wrapping variant prints a single error prefix

  • GIVEN a PawError variant that wraps a message (e.g. a worktree failure)
  • WHEN its Display is rendered and the exit path prepends error:
  • THEN the resulting line SHALL contain exactly one error: prefix
  • AND the variant’s own Display SHALL NOT contain the word error

Scenario: Standalone messages are unchanged

  • GIVEN a variant whose message stands alone (e.g. the not-a-git-repository or cancelled messages)
  • WHEN its Display is rendered
  • THEN it SHALL be unchanged by this requirement

Complete capability index

Every capability spec in the repository, grouped by its <namespace>- prefix (links go to the canonical openspec/specs/ source on GitHub). Each entry’s blurb is condensed from that spec’s ## Purpose.

A few entries are internal process specs — they govern the test suite, CI, or the verification workflow itself (e.g. core-ci-hygiene, core-opsx-role-gating) rather than a user-facing feature. These intentionally have no user-guide chapter: the spec itself is their documentation.

core-

  • core-configuration — parses and merges TOML config from global and per-repo files, with repo config overriding global.
  • core-init — bootstraps a repo for git-paw (.git-paw/ scaffold, default config, .gitignore update), idempotently.
  • core-error-handling — the central PawError type: every variant carries an actionable message and maps to a process exit code.
  • core-lang-agnostic — keeps bundled skills project-agnostic via config-sourced placeholder substitutions, an authored-in-assets rule for agent-facing prose, and a CI no-language-leak audit.
  • core-ci-hygiene — isolates the test suite from the live environment and closes the local-vs-CI gap via cold-start smoke recipes and convention-enforcement tests.
  • core-memory-isolation — keeps coding agents from writing outside their worktree into the operator’s config and memory territory, carried by the bundled skills’ doctrine and the supervisor’s violation response.
  • core-project-conventions — repo-wide contributor conventions and their doc accuracy: the AGENTS.md dependency table in sync with Cargo.toml, the commit-scope list, the mdBook architecture module list and changelog include, and the README’s enumeration of the current user-facing surface.
  • core-governance-config — PawConfig.governance: optional root-relative pointers to a project’s governance/doc artifacts, paths-only with no gating semantics.
  • core-opsx-role-gating — enforces the supervisor-only boundary on /opsx:verify and /opsx:archive, with a post-commit guard detecting archive activity by coding agents.
  • core-selftest — git paw selftest runs an isolated end-to-end session lifecycle against a throwaway repo with a dummy CLI and reports a single pass/fail verdict.
  • core-security-posture — the consolidated trust-model disclaimer (what the agent CLI’s permission system, the sandbox and remote control do and do not guarantee, and that git-paw makes no safety decision and approves no prompt) and the documented FS-scoped sandbox workflow (sandbox-exec/bwrap via the custom-CLI seam), plus the git paw doctor sandbox-availability pointer.

cli-

  • cli-parsing — the clap v4 CLI: all subcommands, flags, and argument validation, defaulting to start when no subcommand is given.
  • cli-resolution — detects AI coding CLIs on PATH, merges user-defined custom CLIs, and resolves which CLI each branch uses via a priority chain.
  • cli-interactive-selection — interactive prompts for choosing branches and CLIs (uniform or per-branch), with logic separated from UI via the Prompter trait.
  • cli-prompt-profiles — per-CLI prompt-shape profiles: the readiness/approval/mid-response/mode markers and command-header, file-prompt and option-line forms describing how one CLI’s terminal surface looks — appearance only, never permission policy, and never able to widen the live-prompt gate or change which option sweep.sh approve presses — with the Claude Code profile embedded as the per-field default and shared as the single source of prompt-shape literals for the compiled code and the supervisor helper script.
  • cli-setup — git paw setup-cli <CLI>, the only path by which git-paw writes into an agent CLI’s settings, run by the user as their consent: a compiled per-CLI fact table with unverified items left plan-only, a printed plan and a confirmation before any write, path-scoped helper allow rules shipped with the rules that guard those helpers against modification, a setup record, and an exact --undo.

code-

  • code-architecture — the enforceable structural and regression contract for behavior-preserving refactors: byte-identical observable surface (CLI/config/wire), injectable process/tmux/git seam, single newtype construction boundary, domain-module organization with preserved re-exports, frozen serde/wire/lock/SIGHUP surfaces untouched, and test-gated waves.

git-

  • git-operations — validates repos, lists branches, creates/removes worktrees, and derives worktree directory names — the git plumbing under parallel sessions.
  • git-hook-injection — installs a post-commit dispatcher, pre-push block, and per-worktree branch-guard hooks: committing publishes agent.artifact, pushes are blocked, and pre-existing user content is preserved.
  • git-add-remove-branch — git paw add/remove attach or detach a single agent worktree+pane on a live session, re-tiling the grid without a restart.

tmux-

  • tmux-orchestration — orchestrates tmux sessions with per-pane CLIs in worktrees via a testable builder with dry-run and the agent-grid layout.

safe-

  • safe-process-invocation — safe construction of the tmux/process commands built from untrusted inputs: the tmux session name is sanitized at a single boundary and shell-typed paths/commands are shell-quoted, so names with spaces or shell metacharacters cannot break out of a tmux target or a shell word.

session-

  • session-state — persists one JSON file per session for crash recovery, with atomic writes, tmux-liveness stale detection, and per-repo session receipts.
  • session-logging — captures raw per-pane terminal output via tmux pipe-pane and replays it (ANSI strip/preserve, fuzzy branch match, most-recent auto-select).
  • session-runtime-isolation — per-worktree runtime provisioning: verbatim env-file copy, index-derived port blocks written to a generated .env.local, and consumer on_create/on_remove lifecycle hooks (stdout merged into .env.local).

boot-

  • boot-block — the standardized boot-instruction block injected into each agent: its format/content, {{VARIABLE}} substitution, and the shared build_boot_block renderer.
  • boot-agents-md — injects a marker-delimited git-paw section into AGENTS.md and writes per-worktree AGENTS.md combining root content with worktree assignment sections.

broker-

  • broker-protocol — the complete wire protocol: the BrokerMessage tagged enum, agent_id slugification, publish validation, and the delivery layer’s per-variant routing, sequencing, and cursor polling.
  • broker-runtime — the in-process coordination server: [broker] config, the tokio/axum lifecycle, HTTP surface (/publish, /messages, /status, /log, /watch), and session-lifecycle integration.
  • broker-watcher-and-state — keeps the /status roster honest as worktrees change: filesystem watcher, roster hygiene, working/committed republishing, terminal-status stickiness, and the introspection surface.
  • broker-agent-helper — bundled shell helpers (broker.sh for agents, sweep.sh for the supervisor) that wrap every broker interaction and detect stuck-agent shapes.
  • broker-conflict-detection — a broker-internal, supervisor-mode detector that flags forward/in-flight/ownership conflicts across agent intents and modified-file sets (with sub-file regions).
  • broker-dashboard — a ratatui TUI observing broker state: an agent-status table with a pinned supervisor row plus a scrolling, filterable broker-log panel.
  • broker-messages — forward-compatible payload deserialization for the wire freeze: no-non-empty-contract list fields default to empty when absent (lean/third-party publishers parse), contracted fields stay required, and serialized output is unchanged.
  • broker-server — runtime robustness of the broker HTTP server: blocking subprocess work runs off the tokio worker threads so a publish burst cannot stall other endpoints, and a poisoned state lock is recovered rather than fatal.

supervisor-

  • supervisor-launch — orchestrates the full supervisor session launch via git_paw::commands::supervisor: layout, worktrees, pane structure, boot prompts, and the attach vs --unattended drive-loop branch.
  • supervisor-config — defines the [supervisor] config schema: the approval level and its per-CLI flag mapping, nested sub-tables, gate-command templates, and the deprecated-but-parsed auto-approval tables.
  • supervisor-skill-discipline — encodes the disciplines the bundled supervisor skill teaches: sweep-driven pane work, isolated verification worktrees, stream-timeout recovery, per-event verification, and the no-fail-fast testing gate.
  • supervisor-unattended-operation — the in-process drive loop under --unattended that keeps a wave moving with no human: polling every pane, escalating each live permission prompt for a human decision without blocking, recovering buffered input, detecting completion and CLI unavailability, and exiting with a per-agent summary. It approves nothing.
  • supervisor-learnings — an opt-in, broker-internal aggregator that derives deterministic and qualitative learning signals into .git-paw/session-learnings.md (and the agent.learning broker variant), performing no telemetry.
  • supervisor-correction-loop — the optional [supervisor.correction] self-healing loop that re-engages a gate-failed worker’s pane with the gate feedback, bounds retries by a per-branch cycle count, and applies an escalate-or-abandon policy when the budget is exhausted.
  • supervisor-autonomous-orchestrator — the pump-to-brain layer: when a supervisor pane is present, the unattended loop injects each judgment call (risky prompt, agent.question, merge decision, non-converging branch) into that pane and nudges its orchestration sweep, instead of parking them in an unread inbox; with no supervisor pane nothing is injected.
  • supervisor-branch-refresh — opt-in, default-off post-merge rebase of a live worker branch onto the default branch, gated on a conjunction of safety preconditions (clean per the watcher, idle, claimed, predicted conflict-free, behind), with conflict prediction, failed-rebase abort-and-restore, exclusion of verified-awaiting-merge branches, and a HEAD-moved notification that is never agent.advanced-main.

approval-

  • approval-auto — the approval-send discipline that remains after the classifier’s retirement: the shared live-prompt gate, and approval keystrokes reaching a pane only through an explicit sweep.sh approve that re-confirms on a fresh capture, refuses pane 0 and answers once.
  • approval-pattern-surfacing — records every permission prompt the drive loop escalates to per-session JSONL and provides git paw approvals to aggregate recurring patterns, hinting which of the agent CLI’s own allowlists could absorb each.
  • approval-command-safety — detects agent-CLI permission prompts non-invasively via tmux capture-pane and the resolved prompt profile, reporting only whether an approval marker is present. git-paw makes no safety decision about an agent’s command; the CLI’s own permission system or the human decides.

spec-

  • spec-providers — discovers pending specs and represents them as launchable SpecEntry values through a pluggable backend system (OpenSpec, Markdown, Spec Kit, superpowers plans).

preflight-

  • preflight-diagnostics — git paw doctor runs read-only environment/config/repository checks, reporting each as ✓/⚠/✗ with a remedy, a --json surface, and an exit code equal to the worst check.

mcp-

  • mcp-server — git paw mcp runs a stdio JSON-RPC MCP server exposing read-only, deterministically-sourced tools over a resolved repo/worktree root.
  • mcp-agent-docs — makes the docs site machine-consumable (llms.txt, sitemap, robots, per-page metadata) and bundles a path-allowlisted docs-fetch helper and skill.
  • mcp-agent-publish — the agent→broker write path over MCP: a fixed set of publish tools for the four agent boot events (status, artifact, blocked, question), wire-identical to the shell helper, acting as the calling agent only (derived agent_id, no impersonation) and excluding the supervisor authority verbs so an agent cannot self-verify.

skill-

  • skill-agent — defines the embedded coordination and supervisor skill templates git-paw injects, their resolution order (user override then embedded default), and the placeholder-rendering contract.
  • skill-standardized — supports the agentskills.io standardized skill format (a SKILL.md directory): auto-detecting, loading, and schema-validating it with actionable errors.

Contributing

Contributions to git-paw are welcome! This guide covers the development workflow.

Prerequisites

  • Rust (stable, per rust-toolchain.toml; the MSRV is 1.96, per rust-version in Cargo.toml)
  • tmux
  • just (task runner)

Getting Started

git clone https://github.com/bearicorn/git-paw.git
cd git-paw
just check

Development Commands

git-paw uses just as a task runner. Key recipes:

CommandDescription
just checkRun fmt + clippy + tests
just testRun all tests (tmux tests included — tmux is a hard dependency)
just verifyLint + cargo deny + the full suite with --no-fail-fast + cargo audit
just lintRun cargo fmt --check and cargo clippy --all-targets -- -D warnings
just denyRun cargo deny check
just auditRun cargo audit
just smoke-allCold-start integration tests (host, plus a container on macOS)
just coverageGenerate HTML coverage report
just docsBuild and open mdBook documentation
just api-docsBuild and open Rustdoc API docs
just buildBuild release binary
just installInstall from local source
just cleanClean build artifacts

Building

# Debug build
cargo build

# Release build
cargo build --release

# Install locally
cargo install --path .

Testing

# All tests (unit + integration; tmux tests run normally, so tmux must be installed)
cargo test

# Coverage report
cargo llvm-cov --html
# Open: target/llvm-cov/html/index.html

Tests are organized as:

  • Unit tests — #[cfg(test)] modules within each source file
  • Integration tests — tests/ directory (CLI binary tests, worktree lifecycle, session round-trips)
  • Tmux-dependent tests — run normally (tmux is a hard dependency)

Code Style

  • Formatting: cargo fmt (config in rustfmt.toml)
  • Linting: cargo clippy --all-targets -- -D warnings with pedantic lints enabled
  • No panics: No unwrap() or expect() in non-test src/ code
  • Documentation: //! module-level doc comments, /// on all public items

The no-panics rule is enforced by the lint gate. src/lib.rs and src/main.rs carry #![warn(clippy::unwrap_used, clippy::expect_used)], so cargo clippy --all-targets -- -D warnings fails on a new unwrap() or expect() anywhere in library or binary code. The repo-root clippy.toml sets allow-unwrap-in-tests = true and allow-expect-in-tests = true, keeping #[test] functions and #[cfg(test)] modules exempt; integration-test crates under tests/ are outside the rule.

The gate has two limits. Platform-gated code is linted only on a host where that cfg is active, and CI lints on Linux, so a violation inside a macOS-only block will not be caught there. And an #[allow(clippy::expect_used)] silences the lint by construction, so the suppression set is pinned by a test (static_regex_helper_is_the_only_panic_lint_suppression) rather than by the gate.

Regex patterns fixed at build time are compiled through static_regex in src/static_regex.rs, the single audited helper that holds the only #[allow(clippy::expect_used)] in non-test code. Every pattern passed to it is compiled by a unit test, so an invalid static pattern fails cargo test rather than panicking at run time. Patterns that arrive at run time (configuration or other input) are compiled directly, with the error propagated.

The library boundary

Command bodies live in the library, in git_paw::commands, so the CLI, the MCP server and the tests drive the same use cases. A command takes a CommandContext (repository root and config), an options struct and an injected Output sink, and returns a typed outcome. It never reads the current directory or stdin, prompts, prints — no print!-family macro and no std::io::stdout() / std::io::stderr() handle, not even one passed to a helper — attaches a terminal client, or runs the unattended drive loop. It writes through Output and returns; main.rs does the rest. A source audit (tests/command_library_audit.rs) enforces this over src/commands/, exempting only completions.rs and broker_host.rs by name, and a main.rs guard keeps command bodies from creeping back into the entry layer.

Where agent-facing text lives

Instructions, rules, directives, framing text and task-prompt scaffolding that an agent reads — in its boot prompt, sidecar, or skill content — belong in a bundled asset under assets/ (embedded at compile time via include_str!), never assembled from a Rust push_str!/format! call. Compiled code keeps the assembly: placeholder substitution, conditional inclusion, per-backend selection, dedupe, and region gating. Assets are what the no-language-leak audit (tests/lang_agnostic_skill_audit.rs) can see; prose compiled into a &str literal is invisible to it, which is exactly how a git-paw-specific assumption once leaked into every consumer’s session. See the export-agnosticism dev skill (.agents/skills/export-agnosticism/) for the full export-surface inventory.

Commit Format

This project uses Conventional Commits:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Types: feat, fix, docs, style, refactor, test, ci, chore, perf

Scopes: detect, git, tmux, session, config, interactive, error, cli, docs, ci, specs, agents, logging, replay, init, broker, dashboard, skills, supervisor, merge-loop, user-guide, worktree, governance, learnings, pause

Compound scopes are written (scope1,scope2,...) when one change spans several scopes, e.g. feat(cli,config): add new flag with config wiring.

Examples:

feat(tmux): add mouse mode support
fix(session): handle missing state file gracefully
docs: update installation instructions
test(git): add worktree creation edge cases

Branch Naming

feat/<description>     # New features
fix/<description>      # Bug fixes
docs/<description>     # Documentation
test/<description>     # Test additions
ci/<description>       # CI/CD changes

Pull Request Process

  1. Fork the repository
  2. Create a feature branch from main
  3. Make your changes
  4. Ensure just check passes (fmt, clippy, tests)
  5. Write or update tests as needed
  6. Open a PR against main

PRs should:

  • Have a clear title and description
  • Pass all CI checks
  • Include tests for new functionality
  • Follow the commit format above

Architecture

See the Architecture chapter for an overview of the module structure and design decisions.

New code follows the Target Architecture: decisions are pure functions, and tmux, git, the broker and the filesystem are reached only through ports named in git-paw’s own terms.

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[0.18.0] - 2026-10-29

Features

  • (supervisor,config) Hand unattended judgment calls to a headless orchestrator one-shot
  • (mcp,config) Add consented spec-authoring tools confined to the change directory
  • (skills,init) Export the orchestrator skill for driving a session from your own agent
  • (mcp) Offer session lifecycle and verdict tools to a root session
  • (broker,session) Require the session authority credential for verdicts BREAKING
  • (broker,dashboard) Host the broker in its own process and make the dashboard a thin client
  • (supervisor,cli) Re-attach a drive loop to a live session with start –unattended
  • (session,tmux,cli) Lay out every pane-less session as the agent grid with hot-attach
  • (supervisor,session) Move the orchestrator out of the session and drop the supervisor pane BREAKING
  • (skills) Forbid git hook bypass, tmux driving and self-approval in the coordination skill
  • (broker,mcp,cli) Add a cursor-based event read surface with wave staleness
  • (cli,config) Judge applied cli setup against the settings file and report leftover grants
  • (cli) Check that a spec workflow is reachable from a new worktree in git paw doctor

Bug Fixes

  • (broker) Treat a connection reset during the stale-broker probe as no live broker
  • (cli,session) Submit the workflow entry point as its own line and name the worktree root
  • (tmux,session) Split new grid rows full width so each agent’s keystrokes reach its own pane
  • (supervisor) Measure the unattended heartbeat from the last observed progress
  • (broker,mcp,skills) Derive no publishing agent id in the main working tree

Refactor

  • (cli) Move the command bodies into the library as git_paw::commands
  • (supervisor) Split the drive loop into pure decisions and an effect-applying shell
  • (tmux,supervisor,cli) Reach terminal surfaces through a terminal backend port
  • (session,supervisor) Resolve every surface role through a typed layout record

Documentation

  • (specs) Count window 0’s panes now that the broker has its own window
  • (specs) Spec the v0.18.0 orchestrator relocation changes

Testing

  • (broker) Wait for a freed test port to refuse connects before using it

CI/CD

  • Drop truncated profiles before merging coverage

[0.17.0] - 2026-10-25

Features

  • (cli) Add git paw setup-cli to seed cli allow rules with consent
  • (supervisor) Retire the auto-approve classifier BREAKING
  • (cli,config) Launch a cli’s configured command and detect cursor-agent BREAKING
  • (broker) Guard the listener against browser and foreign-host requests BREAKING
  • (skills) Teach read-tool and scratch-path discipline in the coordination skill
  • (session,cli) Capture worker cli session ids and resume them on recovery

Bug Fixes

  • (error,ci) Remove non-test panics, lint-enforce the rule and run the binary’s tests
  • (supervisor,session) Wire governance docs and launch specs into the launch path
  • (init,session) Provision bundled helpers on every launch path
  • (cli,tmux) Report exited agent panes as stopped and identify panes by role
  • (broker) Refuse a port held by another repository’s broker
  • (broker) Keep conflict escalation decisions across commits
  • (cli) Stop never asks for confirmation; remove its –force flag BREAKING
  • (supervisor,cli) Close the cli-unavailable detection gaps
  • (learnings) Derive learning ids from a stable fnv-1a hash
  • (git,session) Never delete a branch with unmerged commits on purge or remove
  • (skills,mcp) Correct false claims in shipped boot blocks, prompts and messages
  • (mcp) Accept the superpowers spec backend in startup validation
  • (supervisor) Anchor the live-prompt gate on the modal block
  • (session) Submit spec-driven boot prompts and gate every launch path on readiness
  • (supervisor) Verify drive-loop nudges by input-box state
  • (skills) Replace serde_yaml with a dependency-free frontmatter parser
  • (supervisor) Derive the drive-loop exit summary from git refs

Documentation

  • (agents) Replay curated history with commit-tree in release-history-curation
  • (agents) Add the target architecture guide and refresh stale docs
  • (user-guide) Document that sandbox-exec refuses setuid binaries
  • (specs) Spec the v0.17.0 reliability release changes

Testing

  • (ci) Isolate every session-touching test from the real session directory

[0.16.0] - 2026-09-14

Features

  • (config) Add per-CLI prompt-shape profiles
  • (mcp) Add an agent boot-event publish tool
  • (supervisor) Refresh the integration branch before verification
  • (skills) Export worker-guidance skills for permissions and worktree orientation
  • (cli) Add attach subcommand and shell completions
  • (supervisor,session) Harden unattended boot to first tool call

Bug Fixes

  • (merge-loop) Make drive-loop nudges submit reliably and stop at wind-down
  • (merge-loop) Resolve the wave exit summary and bind the drive loop to its session
  • (session) Scope purge –stale to the current repo
  • (session) Honor default_spec_cli, resolve origin/HEAD, and reattach on start
  • (supervisor) Stop seeding inert command allowlists into cli settings
  • (specs) Point Spec Kit agents at their spec path in the boot prompt
  • (broker) Exempt shared task-list edits from ownership-conflict detection
  • (user-guide) Allow /private/tmp in the sandbox profile so heredocs work
  • (agents) Stop leaking git-paw’s own conventions into exported runtime prose
  • (merge-loop) Stop instructing peers to cherry-pick each other’s branches

Refactor

  • (skills) Move compiled export prose into assets

Documentation

  • (agents) Add the release-history-curation dev skill
  • (specs) Spec v0.16.0 unattended-boot and friction-killers changes

[0.15.0] - 2026-09-02

Features

  • (doctor,user-guide) FS-scoped sandbox workflow + security posture
  • (worktree,cli) Per-worktree runtime isolation

Documentation

  • (agents) Link the remaining dev skills in AGENTS.md

[0.14.0] - 2026-08-11

Features

  • (supervisor) Enforce one approver per pane with an exclusive claim
  • (cli,supervisor) Single-source the classifier via a hidden __classify seam
  • (supervisor,approval) Harden the unattended loop against four dogfood stalls
  • (dashboard,broker) Status activity phrase and broker-log heartbeat dedup
  • (supervisor,tmux) Fix supervisor top row at half the session height
  • (supervisor) Autonomous orchestrator (pump-to-brain hand-off)
  • (init,config) Backfill missing top-level config keys on init
  • (supervisor,config,broker) Self-healing correction loop

Bug Fixes

  • (session,error,tmux) Pre-freeze code-quality closeout

Refactor

  • (supervisor) Model the worker lifecycle as a WorkerPhase enum
  • (dashboard) Restructure the dashboard TUI as Model-View-Update
  • (git) Route git.rs through the CommandRunner seam
  • (interactive) Split interactive.rs into resolver/prompter/picker submodules
  • (init,broker,supervisor) Source shared _paw_common.sh preamble from bundled helpers

Documentation

  • (user-guide) Add tiered-model workflow guide

[0.13.0] - 2026-08-05

Features

  • (broker) Default absent no-contract list fields to empty vectors
  • (cli) Give doctor a live session-lifecycle smoke arm
  • (cli) Add git paw doctor read-only preflight diagnostics
  • (cli) Hide selftest from the public command surface
  • (skills) Add spec-organization dev skill for the Gate-3 spec audit
  • (detect,config) Replace gemini cli with antigravity (agy)
  • (skills) Add doc-completeness, security-review, safety-review, definition-of-done skills
  • (skills) Add export-agnosticism agent skill across all export surfaces
  • (skills) Consult the project’s .agents/skills standards at author and review time
  • (skills) Add code-standards agent skill (patterns, NFRs, do-not-touch)
  • (skills) Add test-strategy agent skill and conformance guard

Bug Fixes

  • Lower the declared MSRV from 1.97 to 1.96
  • (broker) Offload blocking publish work and recover a poisoned state lock
  • (dashboard,cli) Shell-quote the binary path in the __dashboard command
  • (tmux,logging) Shell-quote the pipe-pane log path
  • (tmux,session) Sanitize the tmux session name at construction
  • (logging) Attach pipe-pane on every launch path
  • (specs) Reconcile the auto-approval live-prompt window contradiction
  • (specs) Weave the agy-roster correction into merged cli-resolution

Refactor

  • (tmux) Route the runtime path through the CommandRunner seam
  • (cli) Move supervisor mode into commands::supervisor (repoint source_audit)
  • (cli) Extract the recover/start/add/remove handlers into src/commands
  • (cli) Split main.rs handlers into src/commands modules
  • (git,tmux) Add domain newtypes as the construction seam
  • (tmux) Introduce CommandRunner seam at the builder execute site
  • (tmux) Split tmux.rs into a tmux/ module tree (R1, behavior-preserving)
  • (config) Split config.rs into a config/ module tree (R1, behavior-preserving)
  • Code-analysis-refactor R1 idiom/hygiene (behavior-preserving)
  • (skills) Merge security-review + safety-review into one security-and-safety-review skill

Documentation

  • (user-guide) Document git paw doctor and fold selftest under it
  • (specs) Doctor checks for a python3 interpreter
  • (skills) Capture the net-first consolidation doctrine in test-strategy
  • (skills) Fold the namespacing doctrine into the spec-organization skill
  • (specs) Spec-consolidation wave 6 — domain-grouped Specifications index
  • (specs) Omit the deprecated –from-specs alias from the cli-interaction-e2e matrix

Testing

  • (broker) Make the H1 non-stall assertion deterministic on constrained CI
  • (broker) Assert populated artifact payloads round-trip byte-equivalently
  • (cli) Cover the doctor exit-code, –json and read-only contracts
  • (tmux) Reap the isolated tmux server on TmuxTestEnv drop
  • (interactive) Cover init migrate-supervisor prompt gating
  • (worktree) Parse list-panes with a space delimiter
  • (cli) Delete source-grep cli_specs_tty_proceeds_to_picker (behaviorally replaced)
  • (cli) Add bare –specs spec-picker shown-gate (PTY)
  • (cli) Add spec-launch dispatch rows to the cli-interaction-e2e matrix
  • (broker) Trim raw-TCP per-code dups + table-ify delivery routing
  • (config) Fold absent-section backward-compat fixtures into one table
  • (session) Delete redundant session_integration.rs (unit tests fully cover it)
  • (config) Table-ify default/section-absent/enum-parse clusters (safe wave)
  • (cli,supervisor) Table-ify cli arg-parse + classifier clusters (safe wave)
  • (broker) Table-ify messages.rs getter/slugify/payload clusters (safe wave)
  • Consolidate error/layout/dashboard unit clusters into tables (safe wave)
  • (session) Add session-logging capture-on-launch guard (net-first)
  • (cli) Add CLI-resolution-chain guard (net-first)
  • (approval) Add protected-path / out-of-worktree enforcement guard (net-first)
  • (specs) Add superpowers backend integration guard (net-first)
  • (cli) Fix clippy lints in prompt-matrix helpers
  • (interactive) Add destructive-confirmation gating rows (stop, purge –force)
  • (interactive) Add start-family PTY shown picker rows
  • (interactive) Add start-family bypass rows to the prompt matrix
  • (interactive) Promote shared PTY harness and add the init prompt-matrix slice
  • (skills) Explicitly guard the standards-consult wording in the lang-agnostic audit

CI/CD

  • (smoke) Install python3 in the container smoke image
  • (container) Run smoke-container as non-root + –init to mirror the CI runner

Miscellaneous

  • (skills) Rustfmt tests/standards_skill_consult.rs

[0.12.0] - 2026-07-24

Features

  • (specs) Select the spec system from config/CLI only; remove filesystem auto-detection
  • (specs) Add superpowers as a fourth spec backend
  • (supervisor) Tier approval so the drive loop and supervisor act on disjoint sets

Bug Fixes

  • (broker) Monotonic poll cursor and duplicate-question suppression
  • (broker) Accept any branch prefix in agent_id, not just feat (#58)

Documentation

  • (agents) Enum-variant-ripple checklist (BrokerMessage + SpecBackendKind) and gate-exit-code note
  • (broker) Update validate() rustdoc to the generalized agent_id regex

CI/CD

  • Scope caches to main and split coverage from the test matrix

[0.11.0] - 2026-07-17

Features

  • (supervisor) Isolate operator memory via config-driven protected-path denial
  • (supervisor) Supervisor-native approval mode decoupled from agent approval
  • (supervisor) Seed per-worktree claude settings allowlists at attach and recovery
  • (supervisor) Harden the approval send gate
  • (supervisor) Source auto-approve whitelist from stack presets, add worktree dev-test rules
  • (broker,dashboard) Add agent.answer message variant with targeted delivery (W15-11)
  • (broker) Detect same-region edits via normalized cross-kind matching (W15-22a)

Documentation

  • (user-guide) Close doc-audit low-severity gaps in config, pause, and specs index
  • (skills) De-opinionate exported verify/archive and spec-layout prose

Testing

  • (e2e) Use cat stand-in in add-remove e2e to avoid bare-shell boot-block dirt
  • (e2e) Force remove_middle_agent past the bare-shell boot-block artifact

CI/CD

  • (docs) Split docs job into build-docs and deploy-docs

[0.10.0] - 2026-07-13

Features

  • (cli,init) Provision bundled helpers into agent worktrees
  • (skills) Bundle the on-demand docs-fetch skill and helper, gated on docs_base_url
  • (docs) Generate agent-friendly discovery surface at build time
  • (init) Document every config section in the generated init template

Bug Fixes

  • (dashboard) Exit on tty hang-up instead of trapping the input poll
  • (dashboard) Harden orphan-exit against bind-failure and shell-reparent busy-loops
  • (dashboard) Exit when orphaned and stop busy-redrawing while idle
  • (git,agents) Harden the remove dirty-check against porcelain parse bleed

Documentation

  • (specs) Index every capability on the specifications page
  • (specs) Backfill Purpose sections across archived capability specs
  • Align docs with shipped behavior and add design principles

Testing

  • (e2e) Make broker-port-release checks TIME_WAIT-immune
  • (supervisor) Assert approval-log rows track first/last-seen
  • (specs) Guard against the archived Purpose placeholder

CI/CD

  • (docs) Add workflow_dispatch for on-demand pages redeploy

[0.9.0] - 2026-07-06

Features

  • (supervisor) Add git paw start –unattended in-process drive loop
  • (supervisor) Capture qualitative learnings via sweep.sh learn and the tooling_friction category
  • (supervisor) Add a broker-mediated approval-send gate with live-prompt re-confirm
  • (broker) Widen the sweep.sh helper surface for status-publish and by-path grants
  • (supervisor) Detect stream-timeout, context-bloat, no-progress, and blocked stalls
  • (broker) Classify in-flight overlaps as additive vs true conflicts
  • (cli) Add git paw selftest subcommand with an isolated lifecycle harness
  • (supervisor) Auto-approve classifier for safe permission prompts
  • (interactive) Add fuzzy-filter multi-select branch and spec pickers

Bug Fixes

  • (supervisor) Run the five verification gates against the re-resolved branch tip
  • (skills) De-opinionate commit-message format in the bundled coordination skill
  • (worktree) Ignore git-paw-managed files in the remove dirty-check

Documentation

  • (governance) Add behavioral guidelines to AGENTS.md
  • (readme) Refresh logo and banner assets, resize logo to 288px

[0.8.0] - 2026-06-26

Features

  • (agents) Inject managed block into gitignored sidecar, not tracked AGENTS.md
  • (agent) Bundle broker.sh helper; least-privilege boot allowlist
  • (dashboard) Enlarge broker-log panel with configurable height
  • (orchestration) Launch-readiness gate, remove-by-pane-id, equal-width rows
  • (supervisor) Prefix-grant dev-allowlist + genericise DEV_ALLOWLIST_PRESET
  • (worktree) Configurable worktree placement (child vs sibling)
  • (skills) Add stand-by-after-commit + releasable-unit commit discipline
  • (dashboard) Drop the always-blank Summary column from the agent table

Bug Fixes

  • (init) Gitignore .git-paw/session-learnings.md

Documentation

  • (readme) Add centered logo to the README header
  • Add logo to README header (#54)

[0.7.0] - 2026-06-24

Features

  • (mcp) Add source-browsing tools (list_files, read_file, search_code)
  • (mcp) Add read-only documentation tools (get_readme, list_docs, get_doc)
  • (mcp) Add read-only MCP server (git paw mcp) over stdio
  • (learnings) Disclose no-telemetry stance and opt-in sharing
  • (broker) Live watch-target registration for hot-added agents

Bug Fixes

  • (mcp) Advertise git-paw server identity + configurable [mcp] name

[0.6.0] - 2026-06-17

Features

  • (ci) Cold-start CI parity with containerised smoke recipes
  • (config,init) CLI-agnostic boot, config-driven dev-allowlist, repo-local tmp scratch
  • (session,cli) Git paw add/remove, session bugfixes, launch/recovery robustness
  • (supervisor,skills) /tell routing, opsx role-gating, verification discipline, lang-agnostic skills
  • (dashboard,tmux) Broker-log panel, supervisor introspection, pane affordances
  • (broker) Advanced-main + learning message variants, region-level conflict detection, roster hygiene

Documentation

  • (governance) Document PR-based release flow
  • V0.6.0 user guide, CLI reference, and configuration updates

Testing

  • Cover v0.6.0 capabilities (broker, dashboard, supervisor, session, init)

[0.5.0] - 2026-05-25

Features

  • (cli,main,interactive) SpecMode dispatcher, pause subcommand, –no-supervisor, –specs picker, –from-specs –supervisor routing
  • (init,skills) Bundle sweep.sh helper installed by git paw init, idempotent merge against existing configs
  • (supervisor) Supervisor-as-pane, dev-allowlist seeding, default-config fallback, auto-approve, stall detection, layout helper
  • (tmux,git,agents,session,dirs) Pause primitives, idempotent worktrees, AGENTS.md boot-block lifecycle, worktree base rebase
  • (dashboard) Supervisor-as-pane row, prompt-inbox removal, phase-aware status, layout collapse
  • (config) [governance], [common_dev_allowlist], supervisor gate-command keys, user_config_path override
  • (skills) Supervisor + coordination skill v0.5.0 doctrine
  • (specs) Spec Kit backend, backend-tagged SpecEntry, per-backend boot-prompt dispatch
  • (broker) Agent.intent, learnings aggregator, conflict detector, status payload metadata, agent_id validation

Bug Fixes

  • (tmux,test-isolation) CI failures from -p vs -l N% split syntax and test-process env leakage
  • (tmux) Pass -x/-y plus set default-size for headless tmux environments
  • (docs) List all crates in third-party licenses page

Documentation

  • Align README, mdBook, AGENTS.md, and user-guide with v0.5.0 surface
  • (specs) V0.5.0 OpenSpec changes, archive plan, and main-spec alignment

Testing

  • Behavioral coverage for v0.5.0 surfaces + tmux/config-integration isolation harness

[0.4.0] - 2026-05-06

Features

  • (supervisor) Auto-approve patterns
  • (supervisor) Mode with merge loop, session summary, recovery, question forwarding
  • (cli,config,init,git) Supervisor + force flags, supervisor config schema, branch handling
  • (dashboard) Committed counter, prompt-inbox interactivity, message log panel, layout
  • (broker) Hook injection, watcher, sticky terminal status, real uptime, verified/feedback messages
  • (skills) Standardize agent-skill resolution to agentskills.io layout
  • (detect) Expand auto-detection to cover 10 additional AI CLI tools

Documentation

  • Align README, mdBook, and AGENTS.md with v0.4.0 surface
  • (specs) V0.4.0 OpenSpec changes, archive plan, and main-spec alignment

Testing

  • Behavioral integration and unit tests for v0.4.0

[0.3.0] - 2026-04-10

Features

  • (broker) Wire broker into session lifecycle and update docs (#43)
  • Add dashboard, skills, and agent coordination
  • (broker) Add HTTP broker with message types, delivery, and config

CI/CD

  • (deps) Switch dependabot to monthly and ignore cargo-dist actions
  • (deps) Bump actions/deploy-pages from 4 to 5 (#40) (#40)

Build

  • (deps) Bump toml from 0.9.12+spec-1.1.0 to 1.1.2+spec-1.1.0 (#41) (#41)

[0.2.0] - 2026-04-08

Features

  • Add v0.2.0 spec-driven launch, init, logging, replay, and AGENTS.md integration (#42)

[0.1.0] - 2026-03-25

Features

  • Add CLI tool for parallel AI coding sessions across git worktrees [0.18.0]: https://github.com/bearicorn/git-paw/compare/v0.17.0…v0.18.0 [0.17.0]: https://github.com/bearicorn/git-paw/compare/v0.16.0…v0.17.0 [0.16.0]: https://github.com/bearicorn/git-paw/compare/v0.15.0…v0.16.0 [0.15.0]: https://github.com/bearicorn/git-paw/compare/v0.14.0…v0.15.0 [0.14.0]: https://github.com/bearicorn/git-paw/compare/v0.13.0…v0.14.0 [0.13.0]: https://github.com/bearicorn/git-paw/compare/v0.12.0…v0.13.0 [0.12.0]: https://github.com/bearicorn/git-paw/compare/v0.11.0…v0.12.0 [0.11.0]: https://github.com/bearicorn/git-paw/compare/v0.10.0…v0.11.0 [0.10.0]: https://github.com/bearicorn/git-paw/compare/v0.9.0…v0.10.0 [0.9.0]: https://github.com/bearicorn/git-paw/compare/v0.8.0…v0.9.0 [0.8.0]: https://github.com/bearicorn/git-paw/compare/v0.7.0…v0.8.0 [0.7.0]: https://github.com/bearicorn/git-paw/compare/v0.6.0…v0.7.0 [0.6.0]: https://github.com/bearicorn/git-paw/compare/v0.5.0…v0.6.0 [0.5.0]: https://github.com/bearicorn/git-paw/compare/v0.4.0…v0.5.0 [0.4.0]: https://github.com/bearicorn/git-paw/compare/v0.3.0…v0.4.0 [0.3.0]: https://github.com/bearicorn/git-paw/compare/v0.2.0…v0.3.0 [0.2.0]: https://github.com/bearicorn/git-paw/compare/v0.1.0…v0.2.0

FAQ

General

What does “paw” stand for?

Parallel AI Worktrees.

Does git-paw work on Windows?

Only through WSL (Windows Subsystem for Linux). git-paw requires tmux, which is not natively available on Windows. See the Installation chapter for WSL setup instructions.

Do I need tmux experience to use git-paw?

No. git-paw creates and manages tmux sessions for you. Mouse mode is enabled by default, so you can click to switch panes and drag to resize. The only tmux shortcut you might need is Ctrl-b d to detach.

Can I use git-paw with AI CLIs not in the supported list?

Yes! Use git paw add-cli to register any CLI binary. See Custom CLIs.

Sessions

What happens if I close my terminal?

The tmux session keeps running in the background. Run git paw attach (or git paw again) to reattach.

What happens if tmux crashes or my machine reboots?

git-paw saves session state to disk. The next time you run git paw, it detects the saved state and automatically recovers: reuses existing worktrees, recreates the tmux session, and relaunches your AI CLIs.

Can I run multiple git-paw sessions?

One session per repository. To work with multiple repos, open separate terminals and run git paw in each repo directory.

How do I switch between branches in a session?

Click the pane you want (mouse mode is on by default), or use Ctrl-b followed by arrow keys to navigate between panes. Each pane’s border label shows its pane index and branch.

Worktrees

What are git worktrees?

Git worktrees let you check out multiple branches simultaneously in separate directories. Each worktree is a fully functional working copy of the repository sharing the same .git data. Changes in one worktree don’t affect others.

Where does git-paw create worktrees?

It depends on worktree_placement. git paw init writes "child", which keeps worktrees inside the repo; with no worktree_placement set, they are created as siblings of the repo. For a project at ~/projects/my-app with branch feat/auth:

~/projects/my-app/                                ← your repo
~/projects/my-app/.git-paw/worktrees/feat-auth/   ← "child" (written by git paw init)
~/projects/my-app-feat-auth/                      ← "sibling" (field absent)

See Worktree Placement.

Does stopping a session delete my worktrees?

No. git paw stop kills the tmux session but keeps worktrees and any uncommitted work intact. Only git paw purge (or git paw remove <branch> for a single agent) removes worktrees.

Can I manually work in a git-paw worktree?

Yes. Worktrees are regular git working directories. You can cd into them, edit files, commit, push — anything you’d do in a normal repo. When you restart the session, git-paw reuses the existing worktrees.

Configuration

Where are config files stored?

LevelPath
Global~/.config/git-paw/config.toml
Per-repo.git-paw/config.toml (in repo root)

Both are optional. See Configuration.

How do I set a default CLI?

Add to your global or repo config:

default_cli = "my-cli"

It pre-selects that CLI in the interactive picker; the picker still appears, so you can pick another. Pass --cli to skip the prompt entirely.

How do I disable mouse mode?

mouse = false

This only affects git-paw’s tmux sessions, not your other tmux usage.

Troubleshooting

“Not a git repository”

Run git-paw from inside a git repository. It needs to be anywhere within a repo’s working tree.

“tmux is required but not installed”

Install tmux:

  • macOS: brew install tmux
  • Ubuntu/Debian: sudo apt install tmux
  • Fedora: sudo dnf install tmux

“No AI CLIs found on PATH”

Install at least one AI coding CLI (see Supported AI CLIs), or register a custom one:

git paw add-cli my-tool /path/to/my-tool

“no space for new pane” in tmux

This can happen with many branches on a small terminal. Make your terminal window larger before launching, or select fewer branches. In supervisor mode, git-paw caps each agent row at five panes and rebalances every row to equal width, so panes stay as wide as the layout allows; the hard cap is 25 agents per session.

Session state seems stale

git-paw checks tmux liveness to determine effective status. git paw status shows a session whose receipt claims it is active but whose tmux session is gone as 🔴 stale, and git paw start self-heals such a receipt before launching. To clear stale receipts without touching a live session:

git paw purge --stale

Repository names with dots or spaces, and install paths with spaces

Supported. git-paw derives the tmux session name from your repository’s directory name, and it sanitizes that name to a valid tmux target: everything outside A-Z a-z 0-9 _ - becomes -, runs collapse, and leading or trailing runs are dropped. So a repo in my.app gets the session paw-my-app, and My Project gets paw-My-Project. Sanitizing matters because tmux reads . and : as its own window and pane separators — left alone, a dotted name made tmux parse paw-my.app as session paw-my plus pane app, and pane commands failed with can't find pane. A name already made only of safe characters is used unchanged, so existing sessions keep the names they have.

Paths with spaces are handled too: the per-pane log path passed to tmux’s pipe-pane and the dashboard command typed into its pane are both shell-quoted, so a repository under /Users/My User/code logs and launches normally.

Third-Party Licenses

git-paw includes the following third-party software.

Summary

  • 205 crates under MIT License (MIT)
  • 7 crates under Apache License 2.0 (Apache-2.0)
  • 1 crates under BSD 3-Clause “New” or “Revised” License (BSD-3-Clause)
  • 1 crates under Unicode License v3 (Unicode-3.0)
  • 1 crates under zlib License (Zlib)

License notices

Each section below covers crates that share an identical license-text variant. License-IDs with multiple copyright holders therefore appear more than once, with the relevant crates and copyright notice grouped together.

Apache License 2.0 (Apache-2.0)

Used by:

  • sdd 3.0.10
License text
                                 Apache License
                           Version 2.0, April 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   Copyright 2024-present Changgyoo Park

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

Apache License 2.0 (Apache-2.0)

Used by:

  • normalize-line-endings 0.3.0
License text
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright {yyyy} {name of copyright owner}

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

Apache License 2.0 (Apache-2.0)

Used by:

  • scc 2.4.0
License text
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   Copyright 2020-2024 Changgyoo Park

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

Apache License 2.0 (Apache-2.0)

Used by:

  • rmcp-macros 1.7.0
  • rmcp 1.7.0
  • ryu 1.0.23
  • sync_wrapper 1.0.2
License text
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.

"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:

     (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and

     (b) You must cause any modified files to carry prominent notices stating that You changed the files; and

     (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and

     (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.

     You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!)  The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.

Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

BSD 3-Clause “New” or “Revised” License (BSD-3-Clause)

Used by:

  • matchit 0.8.4
License text
BSD 3-Clause License

Copyright (c) 2013, Julien Schmidt
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

MIT License (MIT)

Used by:

  • instability 0.3.12
License text
# MIT License

Copyright (c) 2020 Stephen M. Coakley
Copyright (c) The Ratatui Developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • schemars_derive 0.8.22
License text
#![allow(clippy::all)]
// Copied from regex_syntax crate to avoid pulling in the whole crate just for a utility function
// https://github.com/rust-lang/regex/blob/431c4e4867e1eb33eb39b23ed47c9934b2672f8f/regex-syntax/src/lib.rs
//
// Copyright (c) 2014 The Rust Project Developers
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

pub fn escape(text: &str) -> String {
    let mut quoted = String::new();
    escape_into(text, &mut quoted);
    quoted
}

fn escape_into(text: &str, buf: &mut String) {
    buf.reserve(text.len());
    for c in text.chars() {
        if is_meta_character(c) {
            buf.push('\\');
        }
        buf.push(c);
    }
}

fn is_meta_character(c: char) -> bool {
    match c {
        '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$'
        | '#' | '&' | '-' | '~' => true,
        _ => false,
    }
}

MIT License (MIT)

Used by:

  • schemars 1.2.1
License text
#![allow(clippy::all)]
use crate::_alloc_prelude::*;
// Copied from regex_syntax crate to avoid pulling in the whole crate just for a utility function
// https://github.com/rust-lang/regex/blob/431c4e4867e1eb33eb39b23ed47c9934b2672f8f/regex-syntax/src/lib.rs
//
// Copyright (c) 2014 The Rust Project Developers
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

pub fn escape(text: &str) -> String {
    let mut quoted = String::new();
    escape_into(text, &mut quoted);
    quoted
}

fn escape_into(text: &str, buf: &mut String) {
    buf.reserve(text.len());
    for c in text.chars() {
        if is_meta_character(c) {
            buf.push('\\');
        }
        buf.push(c);
    }
}

fn is_meta_character(c: char) -> bool {
    match c {
        '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$'
        | '#' | '&' | '-' | '~' => true,
        _ => false,
    }
}

MIT License (MIT)

Used by:

  • atomic-waker 1.1.2
License text
===============================================================================

Copyright (c) 2016 Alex Crichton
Copyright (c) 2017 The Tokio Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

===============================================================================

Copyright (c) 2016 Alex Crichton
Copyright (c) 2017 The Tokio Authors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • core-foundation-sys 0.8.7
License text
Copyright (c) 2012-2013 Mozilla Foundation

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • form_urlencoded 1.2.2
License text
Copyright (c) 2013-2016 The rust-url developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • percent-encoding 2.3.2
License text
Copyright (c) 2013-2025 The rust-url developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • cc 1.2.61
  • cfg-if 1.0.4
  • find-msvc-tools 0.1.9
  • js-sys 0.3.97
  • socket2 0.6.3
  • wait-timeout 0.2.1
  • wasm-bindgen-macro-support 0.2.120
  • wasm-bindgen-macro 0.2.120
  • wasm-bindgen-shared 0.2.120
  • wasm-bindgen 0.2.120
License text
Copyright (c) 2014 Alex Crichton

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • mio 1.2.0
License text
Copyright (c) 2014 Carl Lerche and other MIO contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • errno 0.3.14
License text
Copyright (c) 2014 Chris Wong

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • mime 0.3.17
License text
Copyright (c) 2014 Sean McArthur

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


MIT License (MIT)

Used by:

  • bitflags 2.11.0
  • log 0.4.29
  • num-traits 0.2.19
  • regex-automata 0.4.14
  • regex-syntax 0.8.10
  • regex 1.12.3
License text
Copyright (c) 2014 The Rust Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • float-cmp 0.10.0
License text
Copyright (c) 2014-2020 Optimal Computing (NZ) Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • hyper 1.9.0
License text
Copyright (c) 2014-2026 Sean McArthur

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • either 1.15.0
  • itertools 0.13.0
License text
Copyright (c) 2015

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • tempfile 3.27.0
License text
Copyright (c) 2015 Steven Allen

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • heck 0.5.0
  • unicode-segmentation 1.13.2
  • unicode-width 0.1.14
  • unicode-width 0.2.0
License text
Copyright (c) 2015 The Rust Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • which 8.0.2
License text
Copyright (c) 2015 fangyuanziti

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • winapi 0.3.9
License text
Copyright (c) 2015-2018 The winapi-rs Developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • httparse 1.10.1
License text
Copyright (c) 2015-2025 Sean McArthur

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


MIT License (MIT)

Used by:

  • futures-channel 0.3.32
  • futures-core 0.3.32
  • futures-executor 0.3.32
  • futures-io 0.3.32
  • futures-macro 0.3.32
  • futures-sink 0.3.32
  • futures-task 0.3.32
  • futures-util 0.3.32
  • futures 0.3.32
License text
Copyright (c) 2016 Alex Crichton
Copyright (c) 2017 The Tokio Authors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • hashbrown 0.15.5
  • hashbrown 0.17.0
License text
Copyright (c) 2016 Amanieu d'Antras

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • serde_urlencoded 0.7.1
License text
Copyright (c) 2016 Anthony Ramine

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • utf8parse 0.2.2
License text
Copyright (c) 2016 Joe Wilm

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • httpdate 1.0.3
License text
Copyright (c) 2016 Pyfisch

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • lock_api 0.4.14
  • parking_lot 0.12.5
  • parking_lot_core 0.9.12
License text
Copyright (c) 2016 The Rust Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • shell-words 1.1.1
License text
Copyright (c) 2016 Tomasz Miąsko

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • indexmap 2.14.0
License text
Copyright (c) 2016--2017

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • equivalent 1.0.2
License text
Copyright (c) 2016--2023

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • scopeguard 1.2.0
License text
Copyright (c) 2016-2019 Ulrik Sverdrup "bluss" and scopeguard developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • redox_syscall 0.5.18
License text
Copyright (c) 2017 Redox OS Developers

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • http 1.4.0
License text
Copyright (c) 2017 http-rs authors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • signal-hook-mio 0.2.5
  • signal-hook-registry 1.4.8
  • signal-hook 0.3.18
License text
Copyright (c) 2017 tokio-jsonrpc developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • bytes 1.11.1
License text
Copyright (c) 2018 Carl Lerche

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • autocfg 1.5.0
License text
Copyright (c) 2018 Josh Stone

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • smallvec 1.15.1
License text
Copyright (c) 2018 The Servo Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • serial_test 3.4.0
  • serial_test_derive 3.4.0
License text
Copyright (c) 2018 Tom Parker-Shemilt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • want 0.3.1
License text
Copyright (c) 2018-2019 Sean McArthur

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


MIT License (MIT)

Used by:

  • try-lock 0.2.5
License text
Copyright (c) 2018-2023 Sean McArthur
Copyright (c) 2016 Alex Crichton

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


MIT License (MIT)

Used by:

  • getrandom 0.4.2
License text
Copyright (c) 2018-2026 The rust-random Project Developers
Copyright (c) 2014 The Rust Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • unicode-truncate 1.1.0
License text
Copyright (c) 2019 Aetf <aetf at unlimitedcodeworks dot xyz>

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • slab 0.4.12
License text
Copyright (c) 2019 Carl Lerche

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • bumpalo 3.20.2
License text
Copyright (c) 2019 Nick Fitzgerald

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • tracing-attributes 0.1.31
  • tracing-core 0.1.36
  • tracing 0.1.44
License text
Copyright (c) 2019 Tokio Contributors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • tower-layer 0.3.3
  • tower-service 0.3.3
  • tower 0.5.3
License text
Copyright (c) 2019 Tower Contributors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • axum 0.8.8
License text
Copyright (c) 2019 axum Contributors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • http-body 1.0.1
License text
Copyright (c) 2019-2024 Sean McArthur & Hyper Contributors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • http-body-util 0.1.3
License text
Copyright (c) 2019-2025 Sean McArthur & Hyper Contributors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • iana-time-zone-haiku 0.1.2
  • iana-time-zone 0.1.65
License text
Copyright (c) 2020 Andrew D. Straw

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • hyper-util 0.1.20
License text
Copyright (c) 2023-2025 Sean McArthur

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • anstream 1.0.0
  • anstyle-parse 1.0.0
  • anstyle-query 1.1.5
  • anstyle-wincon 3.0.11
  • anstyle 1.0.14
  • assert_cmd 2.2.0
  • clap 4.6.0
  • clap_builder 4.6.0
  • clap_complete 4.6.9
  • clap_derive 4.6.0
  • clap_lex 1.1.0
  • colorchoice 1.0.5
  • is_terminal_polyfill 1.70.2
  • once_cell_polyfill 1.70.2
  • predicates-core 1.0.10
  • predicates-tree 1.0.13
  • predicates 3.1.4
  • serde_spanned 1.1.1
  • termtree 0.5.1
  • toml 1.1.2+spec-1.1.0
  • toml_datetime 1.1.1+spec-1.1.0
  • toml_parser 1.1.2+spec-1.1.0
  • toml_writer 1.1.1+spec-1.1.0
License text
Copyright (c) Individual contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • libc 0.2.184
License text
Copyright (c) The Rust Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • lru 0.12.5
License text
MIT License

Copyright (c) 2016 Jerome Froelich

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • static_assertions 1.1.0
License text
MIT License

Copyright (c) 2017 Nikolai Vazquez

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • darling 0.23.0
  • darling_core 0.23.0
  • darling_macro 0.23.0
License text
MIT License

Copyright (c) 2017 Ted Driggs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • zeroize 1.8.2
License text
MIT License

Copyright (c) 2018-2021 The RustCrypto Project Developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • schemars 0.8.22
  • schemars 1.2.1
  • schemars_derive 0.8.22
  • schemars_derive 1.2.1
License text
MIT License

Copyright (c) 2019 Graham Esau

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • strum 0.26.3
  • strum_macros 0.26.4
License text
MIT License

Copyright (c) 2019 Peter Glotfelty

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • tokio-macros 2.7.0
License text
MIT License

Copyright (c) 2019 Yoshua Wuyts
Copyright (c) Tokio Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • axum-core 0.5.6
License text
MIT License

Copyright (c) 2019–2025 axum Contributors

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • compact_str 0.8.1
License text
MIT License

Copyright (c) 2021 Parker Timmerman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • castaway 0.2.4
License text
MIT License

Copyright (c) 2021 Stephen M. Coakley

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • matchit 0.8.4
License text
MIT License

Copyright (c) 2022 Ibraheem Ahmed

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • git-paw 0.18.0
License text
MIT License

Copyright (c) 2026 bearicorn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • chrono 0.4.44
  • difflib 0.4.0
  • r-efi 6.0.0
  • wasip2 1.0.2+wasi-0.2.9
  • wasip3 0.4.0+wasi-0.3.0-rc-2026-01-06
  • winapi-i686-pc-windows-gnu 0.4.0
  • winapi-x86_64-pc-windows-gnu 0.4.0
  • windows-core 0.62.2
  • windows-implement 0.60.2
  • windows-interface 0.59.3
  • windows-link 0.2.1
  • windows-result 0.4.1
  • windows-strings 0.5.1
  • windows-sys 0.59.0
  • windows-sys 0.61.2
  • windows-targets 0.52.6
  • windows_aarch64_gnullvm 0.52.6
  • windows_aarch64_msvc 0.52.6
  • windows_i686_gnu 0.52.6
  • windows_i686_gnullvm 0.52.6
  • windows_i686_msvc 0.52.6
  • windows_x86_64_gnu 0.52.6
  • windows_x86_64_gnullvm 0.52.6
  • windows_x86_64_msvc 0.52.6
License text
MIT License

Copyright (c) <year> <copyright holders>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • tokio-util 0.7.18
  • tokio 1.51.6
License text
MIT License

Copyright (c) Tokio Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • ident_case 1.0.1
License text
MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • crossterm 0.28.1
  • crossterm_winapi 0.9.1
License text
MIT License

Copyright (c) 2019 Timon

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • async-trait 0.1.89
  • atomic-waker 1.1.2
  • dyn-clone 1.0.20
  • fastrand 2.4.1
  • indoc 2.0.7
  • itoa 1.0.18
  • linux-raw-sys 0.12.1
  • linux-raw-sys 0.4.15
  • once_cell 1.21.4
  • paste 1.0.15
  • pastey 0.2.3
  • pin-project-lite 0.2.17
  • proc-macro2 1.0.106
  • quote 1.0.45
  • ref-cast-impl 1.0.25
  • ref-cast 1.0.25
  • rustix 0.38.44
  • rustix 1.1.4
  • rustversion 1.0.22
  • serde 1.0.228
  • serde_core 1.0.228
  • serde_derive 1.0.228
  • serde_derive_internals 0.29.1
  • serde_json 1.0.149
  • serde_path_to_error 0.1.20
  • syn 2.0.117
  • thiserror-impl 2.0.18
  • thiserror 2.0.18
  • unicode-ident 1.0.24
  • wasi 0.11.1+wasi-snapshot-preview1
  • wit-bindgen 0.51.0
  • zmij 1.0.21
License text
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • allocator-api2 0.2.21
License text
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • winnow 1.0.1
License text
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • encode_unicode 1.0.0
License text
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

MIT License (MIT)

Used by:

  • android_system_properties 0.1.5
License text
The MIT License (MIT)

Copyright (c) 2013 Nicolas Silva

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

MIT License (MIT)

Used by:

  • tracing-core 0.1.36
License text
The MIT License (MIT)

Copyright (c) 2014 Mathijs van de Nes

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • base64 0.22.1
License text
The MIT License (MIT)

Copyright (c) 2015 Alice Maz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • aho-corasick 1.1.4
  • memchr 2.8.0
License text
The MIT License (MIT)

Copyright (c) 2015 Andrew Gallant

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • strsim 0.11.1
License text
The MIT License (MIT)

Copyright (c) 2015 Danny Guo
Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>
Copyright (c) 2018 Akash Kurdekar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • shlex 1.3.0
License text
The MIT License (MIT)

Copyright (c) 2015 Nicholas Allegra (comex).

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • ratatui 0.29.0
License text
The MIT License (MIT)

Copyright (c) 2016-2022 Florian Dehau
Copyright (c) 2023-2024 The Ratatui Developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

MIT License (MIT)

Used by:

  • console 0.16.3
  • dialoguer 0.12.0
License text
The MIT License (MIT)

Copyright (c) 2017 Armin Ronacher <armin.ronacher@active-4.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


MIT License (MIT)

Used by:

  • bstr 1.12.1
License text
The MIT License (MIT)

Copyright (c) 2018-2019 Andrew Gallant

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

MIT License (MIT)

Used by:

  • cassowary 0.3.0
License text
The MIT License (MIT)

Copyright (c) 2016 Dylan Ede

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Unicode License v3 (Unicode-3.0)

Used by:

  • unicode-ident 1.0.24
License text
UNICODE LICENSE V3

COPYRIGHT AND PERMISSION NOTICE

Copyright © 1991-2023 Unicode, Inc.

NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.

Permission is hereby granted, free of charge, to any person obtaining a
copy of data files and any associated documentation (the "Data Files") or
software and any associated documentation (the "Software") to deal in the
Data Files or Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Data Files or Software, and to permit persons to whom the
Data Files or Software are furnished to do so, provided that either (a)
this copyright and permission notice appear with all copies of the Data
Files or Software, or (b) this copyright and permission notice appear in
associated Documentation.

THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
THIRD PARTY RIGHTS.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
FILES OR SOFTWARE.

Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in these Data Files or Software without prior written
authorization of the copyright holder.

zlib License (Zlib)

Used by:

  • foldhash 0.1.5
License text
Copyright (c) 2024 Orson Peters

This software is provided 'as-is', without any express or implied warranty. In
no event will the authors be held liable for any damages arising from the use of
this software.

Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to
the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim
    that you wrote the original software. If you use this software in a product,
    an acknowledgment in the product documentation would be appreciated but is
    not required.

2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.