pf
v0.0.7
Published
Agent- and human-friendly Git multitasking, powered by worktrees
Readme
Install
$ npm i -g pfHow pf works
There have been many attempts to nail a DX for parallel work in the agentic coding era. Most are thin wrappers over git worktree. Instead pf introduces a new paradigm: the workshell.
A workshell is an ephemeral worktree whose lifecycle is bound to a subshell.
Here's how it works (key points in bold).
- You open a Git branch with
pf open <branch>or create a new one withpf new <branch>. - An ephemeral worktree is created for this branch (in
.git/pf/worktrees) and opened in a fresh subshell. - You are now in an fresh checkout of your repo that is isolated on disk. Make changes with your agent/editor of choice and commit them.
- You close the subshell with
pf close. The associated worktree is auto-pruned. - Your changes still exist on the associated branch, as Git commits/branches are shared among all worktrees. The worktree is destroyed—but your commits aren't.
Features
This approach has some nice properties.
- 🖥️ Tab-local workspaces — Normally a
git checkout/git switchchanges your active branch for all terminals. With workshells, you can functionality open branches in the current tab only. - 🌳 Full isolation — Each workshell is isolated on disk, so the changes you make don't interfere anything else you're doing.
- 🙅♂️ Never stash again — You can
pf opena branch even with uncommitted changes. When you exit the subshell, things will be exactly the same as they were. ☕️ - Consistent with branch semantics — As with regular
git switch,pf closewon't let you close the subshell if you have unstaged/uncommitted changes. This is a feature, not a bug! Regular worktrees make it easy to lose your work in a forgotten corner of your file system. - 🤖 Agent-ready — Spin up parallel workshells so multiple agents can work simultaneously without conflicts.
Quickstart
This section is entirely linear and self-contained. Try running all these commands in order to get a feel for how pf works. First, install pf.
$ npm i -g pfThen clone a repo (any repo works):
$ git clone [email protected]:colinhacks/zod.git
$ cd zodAfter cloning, the main branch is checked out. Let's say we want to start work on a new feature:
$ pf new feat-1
✓ feat-1 (from main)
Opened branch in ephemeral subshell at .git/pf/worktrees/zod@feat-1
Type 'pf close' to return.You're now in a workshell. Check where you are:
$ pwd
/Users/colinmcd94/Documents/repos/zod/.git/pf/worktrees/zod@feat-1
$ git branch --show-current
feat-1Now let's make some changes. (You can also open the repo in an IDE, start an agent run, etc.)
$ touch a.txtNow let's try to close the workshell.
$ pf close
⚠ Uncommitted changes found. Commit, stash, or reset your changes first.
Or use --force/-f to discard changes
pf close -fWe aren't able to close because we have uncommitted changes. Let's commit them.
$ git add -A && git commit -am "Add a.txt"Now we can try closing again. Since our changes can be fast-forwarded from the base branch, pf offers to auto-merge the changes.
$ pf close
✓ Back in main
Pruned worktree. Your changes are still in the 'feat-1' branch.
To merge your changes:
git merge feat-1
Auto-merge? (y/n/never) y
✓ Merged 'feat-1' into 'main'CLI
pf v0.x.y - Human- and agent-friendly Git multitasking
Usage: pf <command> [options]
Commands:
new [branch] Create a branch and open it [--from <branch>]
open <branch> Open a branch in a workshell
close Exit current workshell
ls List orphaned worktrees
status Show current branch
rm <branch> Remove a branch's worktree
config Show or create config file
Options:
--help, -h Show help
--version, -v Show versionList orphaned worktrees
Normally the worktree will be auto-pruned when you close its associated workshell. If a worktree is left behind for some reason, you can list them with pf ls.
$ pf ls
┌────────┬───────────┬───────────────┐
│ branch │ status │ created │
├────────┼───────────┼───────────────┤
│ main * │ clean │ - │
│ feat-1 │ 1 changed │ 5 minutes ago │
└────────┴───────────┴───────────────┘Open a branch in a workshell
You can open any existing Git branch in a workshell.
$ pf open feat-1
✓ feat-1 (existing worktree)
Type 'pf close' to return.Show current branch status
$ pf status
branch: main (root)
worktree: /path/to/zod
status: cleanRemove a worktree
Remove the worktree for a branch (the branch itself is kept):
$ pf rm feat-1
✓ Pruned worktree for feat-1Closing a workshell
This closes the current workshell and auto-prunes the associated worktree. Your changes survive in your branch commits.
$ pf close
✓ Back in main
Pruned worktree. Your changes are still in the 'feat-1' branch.
To merge your changes:
git merge feat-1
Auto-merge? (y/n/never) y
✓ Merged 'feat-1' into 'main'If the branch hasn't been pushed to a remote, you'll be prompted to auto-merge:
y— merge into base branchn— skip (branch is kept, merge manually later)never— permanently disable auto-merge prompt
That command will fail if you have unstaged/uncommited changes. Use the --force/-f flag to force close the workshell; this will discard uncommitted changes.
$ pf close --forcePrint or create a pf.toml
To print or create a config file:
$ pf config
✓ Config file: /path/to/repo/.git/pf.toml
────────────────────────────────────────────────────────────
setup = "npm install"
────────────────────────────────────────────────────────────If no config exists, you'll be prompted to create one.
$ pf config
No config file found.
Where would you like to create one?
1. .git/pf.toml (local only, not committed)
2. pf.toml (project root, can be committed)
Choice (1/2): 1
✓ Created /path/to/repo/.git/pf.tomlpf.toml
You can configure pf with a pf.toml file. This is useful for running setup scripts when opening a workshell (e.g., npm install).
pf looks for config files in the following order:
| Path | Description |
|------|-------------|
| .git/pf.toml | Local only, not committed (highest precedence) |
| pf.toml | Project root, can be committed |
# setup script executed in subshell after initialization
setup = "npm install && cp {{ repo_path }}/.env {{ worktree_path }}/.env"The following variable substitutions are supported in setup.
| Variable | Description | Example |
|----------|-------------|---------|
| {{ branch }} | Branch name | feature/auth |
| {{ worktree_path }} | Absolute path to worktree | /path/to/repo/.git/pf/worktrees/repo@feat |
| {{ repo_path }} | Absolute path to main repo | /path/to/repo |
