# 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