Agent- and human-friendly Git multitasking, powered by worktrees
npm install pf
pfHuman- and agent-friendly Git multitasking. Powered by worktrees.
by @colinhacks
``bash`
$ npm i -g pf
worksThere 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 or create a new one with pf new ..git/pf/worktrees
- An ephemeral worktree is created for this branch (in ) and opened in a fresh subshell.pf close
- 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 . 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.
This approach has some nice properties.
- π₯οΈ Tab-local workspaces β Normally a git checkout/git switch changes your active branch for all terminals. With workshells, you can functionality open branches in the current tab only.pf open
- π³ 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 a branch even with uncommitted changes. When you exit the subshell, things will be exactly the same as they were. βοΈgit switch
- πͺΎ Consistent with branch semantics β As with regular , pf close won'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.
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.
`bash`
$ npm i -g pf
Then clone a repo (any repo works):
`bash`
$ git clone git@github.com:colinhacks/zod.git
$ cd zod
After cloning, the main branch is checked out. Let's say we want to start work on a new feature:
`bash
$ 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:
`bash
$ pwd
/Users/colinmcd94/Documents/repos/zod/.git/pf/worktrees/zod@feat-1
$ git branch --show-current
feat-1
`
Now let's make some changes. (You can also open the repo in an IDE, start an agent run, etc.)
`bash`
$ touch a.txt
Now let's try to close the workshell.
`bash`
$ pf close
β Uncommitted changes found. Commit, stash, or reset your changes first.
Or use --force/-f to discard changes
pf close -f
We aren't able to close because we have uncommitted changes. Let's commit them.
`bash`
$ 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.
`bash
$ 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'
`
`sh
pf v0.x.y - Human- and agent-friendly Git multitasking
Usage: pf
Commands:
new [branch] Create a branch and open it [--from
open
close Exit current workshell
ls List orphaned worktrees
status Show current branch
rm
config Show or create config file
Options:
--help, -h Show help
--version, -v Show version
`
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.
`sh`
$ pf ls
ββββββββββ¬ββββββββββββ¬ββββββββββββββββ
β branch β status β created β
ββββββββββΌββββββββββββΌββββββββββββββββ€
β main * β clean β - β
β feat-1 β 1 changed β 5 minutes ago β
ββββββββββ΄ββββββββββββ΄ββββββββββββββββ
You can open any existing Git branch in a workshell.
`sh
$ pf open feat-1
β feat-1 (existing worktree)
Type 'pf close' to return.
`
`sh`
$ pf status
branch: main (root)
worktree: /path/to/zod
status: clean
Remove the worktree for a branch (the branch itself is kept):
`sh
$ pf rm feat-1
β Pruned worktree for feat-1
`
This closes the current workshell and auto-prunes the associated worktree. Your changes survive in your branch commits.
`sh
$ 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.
`sh`
$ pf close --force
To print or create a config file:
`sh
$ pf config
β Config file: /path/to/repo/.git/pf.toml
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
setup = "npm install"
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
`
If no config exists, you'll be prompted to create one.
`sh
$ 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.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 |
`toml`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` |