git-workty 0.1.0

Git worktrees as daily-driver workspaces
# design.md — `workty` (git workspaces powered by worktrees)

## Summary

**`workty`** is a Git subcommand that makes Git worktrees feel like **workspaces/tabs**.

It targets a *daily* pain: switching context (PRs, hotfixes, reviews) without stashing or WIP commits.

Under the hood it uses:
- `git worktree` as the storage/ground truth
- Optional `gh` integration for PR workspaces

Primary UX: a **dashboard** + **one-command switching** with shell integration.

---

## Goals

### Product goals
1. **Zero-stash context switching**: make switching tasks feel like switching directories.
2. **One-screen dashboard**: show everything in flight (dirty, ahead/behind, path, optional PR).
3. **Safe cleanup**: remove merged worktrees and keep the repo tidy.
4. **Scriptable and composable**: easy to integrate with shell, editor, and other tools.

### Engineering goals
- Single binary, fast startup (<100ms target).
- Minimal external dependencies; rely on Git.
- Works from *any* worktree of the repo (main or linked).

---

## Non-goals (for MVP)

- A full-screen TUI Git client (lazygit/tig replacement).
- Replacing Git workflows (stacked diffs, branchless, etc.).
- Hosting-provider dependence (GitHub integration is optional).

---

## Naming

Working name: **workty**
- Command: `git workty`
- Binary: `git-workty` (Git discovers it automatically)

Rationale: short, distinct from the many `wt`/`worktree` tools, and easy to type.

> Keep the name isolated in the code (crate/bin/constants) so the project can be renamed later with minimal changes.

---

## Key concepts

### Workspace
A “workspace” is a **git worktree** plus a convention for:
- where it lives on disk
- how it’s named
- how it’s listed/selected/cleaned

### Worktree identity
A worktree may be addressed by:
- branch name (e.g., `feat/login`)
- friendly name (defaults to branch name)
- for PR workspaces: `pr-<number>`

For MVP: branch name is sufficient; friendly alias mapping is optional.

---

## CLI design

### Global flags
- `--no-color` : disable colors
- `--ascii` : ASCII-only symbols
- `--json` : machine output (for `list` and `status`, maybe others)
- `-C <path>` : run as if started in `<path>` (pass-through to `git -C` internally)
- `--yes` : assume “yes” to prompts (for destructive operations)

> Respect `NO_COLOR` automatically.

### Commands overview
- `list` (default)
- `new`
- `go`
- `pick`
- `rm`
- `clean`
- `init`
- `doctor`
- `completions`
- (optional) `pr`

---

## Command specs

### 1) `git workty` / `git workty list`
Show dashboard of worktrees.

**Output (human default):**
- Sorted:
  1) current worktree first
  2) dirty next
  3) then clean, alphabetical by branch/name
- Columns (suggested):
  - marker: `` current
  - name/branch
  - dirty: `● <n>` or ``
  - ahead/behind: `↑<n>↓<n>` or `-`
  - path (shortened, `~` for home)
  - optional: `PR#123` (if `gh` integration later)

**Example:**
```
▶ main              ✓     ↑0↓0   ~/src/repo
  feat/login         ● 3   ↑2↓0   ~/.workty/repo/feat-login
  pr-512             ✓     -      ~/.workty/repo/pr-512
```

**JSON shape (if `--json`):**
```json
{
  "repo": {"root": "...", "common_dir": "..."},
  "current": "...path...",
  "worktrees": [
    {
      "path": "...",
      "branch": "refs/heads/feat/login",
      "branch_short": "feat/login",
      "head": "abc123",
      "detached": false,
      "locked": false,
      "dirty": {"count": 3},
      "upstream": "origin/feat/login",
      "ahead": 2,
      "behind": 0
    }
  ]
}
```

---

### 2) `git workty new <name>`
Create a new workspace.

**Behavior**
- Creates a new branch `<name>` from base (auto-detected or configured).
- Creates a linked worktree in the workspace root directory.
- By default checks out the branch in the new worktree.
- If the branch already has a worktree, do not error cryptically—print a helpful message and exit with non-zero, or offer `--reuse` later.

**Flags**
- `--from <base>`: override base branch/commit-ish
- `--path <path>`: override the filesystem path
- `--print-path`: print only the created worktree path to stdout
- `--open`: run editor opener (config/env controlled) after creation

**Important stdout rule**
- With `--print-path`, stdout must be the path only.
- Any progress/logging goes to stderr.

---

### 3) `git workty go <name>`
Print the path of a workspace by name (for `cd "$(…)"`).

- Resolve by:
  1) exact branch short name match
  2) exact worktree dir basename match
  3) (optional) fuzzy/contains match if unambiguous

**Output**
- stdout: path only
- exit non-zero if not found (and suggest `git workty pick`)

---

### 4) `git workty pick`
Interactive fuzzy selector (TTY-only).
- Uses fuzzy search over display labels like:
  `feat/login  ●3  ↑2↓0   ~/.workty/repo/feat-login`
- On selection, prints selected path to stdout.

**Non-TTY behavior**
- If not a TTY: error with hint to use `go` or `--query` (optional).
- Never hang waiting for input in CI.

---

### 5) `git workty rm <name>`
Remove a workspace.

**Safety checks**
- Refuse if:
  - target is the current worktree
  - target has uncommitted changes, unless `--force`

**Flags**
- `--force`: allow removing dirty worktrees
- `--delete-branch`: after removing the worktree, delete its branch if safe
- `--yes`: skip confirmation prompts

---

### 6) `git workty clean`
Remove merged (or otherwise stale) worktrees.

**Default behavior**
- Shows what it *would* delete and asks for confirmation (TTY) unless `--yes`.
- Use `--dry-run` to print without prompting.

**Flags**
- `--merged`: remove worktrees whose branch is merged into base
- `--dry-run`
- `--yes`

**Merged detection algorithm**
- Determine base branch (config or auto-detect).
- For each worktree with a local branch:
  - consider merged if: `git merge-base --is-ancestor <branch> <base>` returns success
- Never remove:
  - base branch worktree(s)
  - current worktree
  - detached worktrees by default (can be future flag)

---

### 7) `git workty init <shell>`
Print shell integration.

Supported shells:
- `bash`, `zsh`, `fish`, `powershell`

**What init provides**
- `wcd` function:
  - `cd "$(git workty pick)"` (or go + query)
- `wnew` function:
  - creates workspace and cds into it
- completions hook (optional)
- optional git wrapper (`--wrap-git`) that intercepts `git workty …` and auto-cds

**Flags**
- `--wrap-git`: generate an optional `git()` wrapper (off by default)
- `--no-cd`: generate completions only / disable cd helpers

> Keep the wrapper opt-in: wrapping `git` can conflict with other tooling.

---

### 8) `git workty doctor`
Diagnose common issues and print next actions:
- is Git installed?
- is inside a Git repo?
- can we run `git worktree list`?
- are there stale/prunable worktrees?
- is config parseable?

Should suggest commands like:
- `git worktree prune`
- `git worktree repair …` (if detected)

---

### 9) `git workty completions <shell>`
Emit shell completion script (clap can generate this).

---

### 10) (Optional) `git workty pr <number>`
If `gh` is available:
- Create a worktree at `pr-<number>`
- Execute `gh pr checkout <number>` inside that worktree
- Print the path (for `cd`) and/or open editor

This is a major differentiator, but can be delivered after core workflow is stable.

---

## Config design

### Location
Store config in the repo’s common Git directory:
- `$(git rev-parse --git-common-dir)/workty.toml`

### Schema (v1)
```toml
version = 1

# Base branch used for new workspaces and merged cleanup.
base = "main"

# Root directory for workspaces.
# Supports `{repo}` token and `{id}` token.
root = "~/.workty/{repo}-{id}"

# How paths are created from branch names.
# "flat": root/<slug>
# "grouped": root/<prefix>/<slug> (future)
layout = "flat"

# Optional command to open a worktree (e.g. "code", "cursor", "zed", "idea")
open_cmd = "code"

# Optional post-create hooks (MVP: optional; can be v0.2)
# copy = [".env", ".env.local"]
# run  = ["pnpm i"]
```

### Repo identity `{id}`
Compute `{id}` deterministically:
- Prefer hashing the `origin` URL (normalized) if available
- Fallback: hash of absolute common git dir path
- Short hash (8 chars) for readable paths

---

## Git plumbing (how to implement reliably)

### Identify repo + common dir
- repo root: `git rev-parse --show-toplevel`
- common dir: `git rev-parse --git-common-dir`

Always run Git commands with `-C <repo_root_or_target_worktree>` to avoid cwd ambiguity.

### Enumerate worktrees
Use:
- `git worktree list --porcelain`

Parse into:
- `path`
- `head`
- `branch` (full ref) or `detached`
- `locked` (if present)

### Dirty detection
In each worktree:
- `git status --porcelain` (count lines)

### Ahead/behind
For each worktree:
- Determine upstream:
  - `git rev-parse --abbrev-ref --symbolic-full-name @{u}` (may fail)
- If upstream exists:
  - `git rev-list --left-right --count HEAD...@{u}`
    - parse “<behind>\t<ahead>” (note left/right order depends on args; validate!)

---

## UX rules (what makes it feel premium)

1. **One screen by default.**
2. **Icons + color communicate state instantly**, but ASCII mode must exist.
3. **Every error includes a “next step”.**
4. **Stable, predictable sorting.**
5. **Fast**: don’t do 50 git invocations unnecessarily; small parallelism is OK.
6. **No surprises**: never create/delete unless the user asked.

---

## Edge cases to handle

- Worktree paths with spaces.
- Detached HEAD worktrees (show them; don’t delete by default).
- Missing upstream (ahead/behind should show `-`).
- Branch name with slashes (path slugging).
- Already-existing directory at target path.
- Branch already checked out in a different worktree.
- Running from a linked worktree (must still find common dir/config).

---

## Implementation notes (Rust)

- Use `Command` with `arg` arrays.
- Capture stdout/stderr; include stderr in error messages when Git fails.
- Normalize paths with `dunce::canonicalize` on Windows (optional).
- Keep all “print a path” commands absolutely clean on stdout.

---

## Acceptance criteria

MVP is done when:
- `git workty` dashboard is useful and pretty
- `eval "$(git workty init zsh)"` provides a joyful `wcd` / `wnew` loop
- Safety rails prevent accidental deletion
- Tests cover parsing and core flows
- Works from main repo and any worktree

---

## Future (post-MVP) ideas

- Post-create hooks: copy `.env*`, run install commands, open editor
- PR + issue workflows (`gh issue develop`, `gh pr checkout`)
- Per-worktree metadata (labels, last-used timestamp)
- “Archive” instead of delete
- Clipboard copy of selected path