@apisurf/rig
v1.0.3
Published
CLI that sets up the workspace for a task: git worktrees, diffs, and LLM prompt files
Readme
rig
A small CLI that sets up the rig for a piece of work: it manages git worktrees, captures diffs, and assembles LLM prompt files from reusable guides and local working docs.
Overview
rig provides a few focused commands:
wt— create, list, cd into, remove, and copy files into git worktreespath— pick from your repositories and print their absolute pathsdiff— write a diff intoDEV/diffs/for an LLM to readfiles— list the files a saved diff touches, with line countsfolders— show the folders a saved diff touches, as a treeprompt— build a prompt file that links selected guides and local docsinit— create the directories referenced by your configclean— remove theDEV/working tree
Installation
Prerequisites
- Node.js 20.12 or newer —
inkand@inquirer/promptsboth require it - Git
- pnpm, for local development only
Install from npm
pnpm add -g @apisurf/rig
# or: npm install -g @apisurf/rig
# Verify
rig --versionLocal development
# From the repo root
pnpm install
pnpm build
pnpm link --global
# Verify
rig --versionConfiguration
Create a configuration file at ~/.apisurf/rig.config.json:
{
"worktreesPath": "/path/to/worktrees",
"guidesPath": "/path/to/guides",
"repoParents": ["/path/to/repos", "/path/to/more/repos"]
}Configuration Options
| Field | Type | Needed by | Description |
| --------------- | -------- | ---------------------- | ------------------------------------------------------------------- |
| worktreesPath | string | every wt subcommand | Root directory where rig creates and manages git worktrees |
| guidesPath | string | optional, for prompt | Folder of guide files (two-level: <guidesPath>/<folder>/*.md) |
| repoParents | string[] | path | Folders whose direct children are the repositories rig path lists |
wt, path, init, and prompt read the config file and exit with an error when it is
missing. diff, files, folders, and clean need no config at all. Without
guidesPath, prompt still runs — it just offers only the local diffs and
plans.
Run rig init to create any configured directories that don't yet exist.
Guides folder layout
guidesPath is scanned two levels deep — each top-level folder groups related guides:
guides/
├── backend/
│ ├── api-setup.md
│ └── database.md
└── frontend/
└── components.md- Folders whose name starts with
_are ignored. - A
README.mdinside a guide folder is skipped.
Commands
rig wt
Git worktree management. Worktrees are organized under <worktreesPath>/<repo>/<branch>.
rig wt new <source_branch> <dest_branch> # new worktree branched from origin/<source_branch>
rig wt new-remote <branch_name> # new worktree tracking an existing remote branch
rig wt ls # list worktrees for a selected project
rig wt cd # print a cd command: eval "$(rig wt cd)"
rig wt cp <paths...> # copy files/folders into a selected worktree
rig wt rm [branches...] # remove worktree(s) (interactive if no args)
rig wt rmall # remove all worktrees for the current repoThe repo name comes from git remote get-url origin, so wt needs the current
project to have an origin remote. new-remote fetches origin before adding
the worktree.
ls, cd, cp, and an argument-less rm start by asking which project — the
list is every directory under worktreesPath that currently holds at least one
worktree, not just the repo you are standing in. rm with branch names, and
rmall, act on the current repo only.
cp preserves each path's location relative to the repo root, creating parent
directories in the destination as needed; a path outside the repo is refused.
When git refuses to remove a worktree because the directory still has leftover
files, rm offers to delete the directory outright. Answering yes destroys
everything under it, including untracked and ignored files such as .env and
build output; the default is no.
rig path (alias rig pp)
Pick from your repositories and print the absolute path of each one selected,
one per line. The list is every git repository sitting directly inside a
repoParents folder — nested repositories and plain folders are left out.
Search and select in one go: type to filter, ↑/↓ to move, and Tab to toggle the highlighted repository — which also clears the search, ready for the next one. Enter finishes; Esc cancels and prints nothing.
rig path # prints e.g. /path/to/repos/my-project
cd "$(rig pp)" # jump into the single repository selectedThe picker is drawn on stderr, so stdout carries nothing but the paths.
rig diff
Write a diff into DEV/diffs/ for an agent to read. It takes no arguments — it
asks what you want to diff, then what to diff it against:
rig diff| You pick | It runs | It writes |
| ---------------- | ------------------- | ------------------------------- |
| Staged <> HEAD | git diff --cached | DEV/diffs/diff-staged-NNN.txt |
| HEAD <> branch | git diff <branch> | DEV/diffs/diff-branch-NNN.txt |
| HEAD <> commit | git diff <hash> | DEV/diffs/diff-commit-NNN.txt |
Both lists are searchable, so nothing has to be typed exactly — branches are
your local ones, commits the last 50, shown as hash subject. NNN counts up
from the files already in DEV/diffs/, so an earlier diff is never overwritten.
An empty diff writes nothing and says so.
rig files [diffFile]
Read a saved diff back and list the files it touches, with per-file line counts
and a total. With no argument, you pick one from DEV/diffs/ — newest first,
and searchable. A path is taken as given, relative to the working directory or
absolute.
rig files
rig files DEV/diffs/diff-branch-002.txtsrc/commands/diff.ts +48 -12
src/config.ts +6 -0
2 files changed, +54 -12rig folders [diffFile]
The same diff, reduced to the folders it touches and drawn as a tree. Each
folder carries the number of changed files and the line counts for everything
beneath it, so . holds the totals for the whole diff. The diff file is chosen
the same way as for rig files.
rig folders
rig folders DEV/diffs/diff-branch-002.txt. 4 files +71 -12
├── docs 1 file +9 -0
└── src 3 files +62 -12
└── commands 2 files +56 -12
3 folders, 4 files changed, +71 -12rig prompt (alias rig p)
Build a prompt file under DEV/ that links selected guides and local docs.
rig promptWhat it does:
- Loads guides from your configured
guidesPath(if set), diffs fromDEV/diffs/, and plans fromDEV/plans/. - Asks you to multi-select from each of those three, in that order, via a TUI. Any list that is empty is skipped; cancelling out of a step abandons the whole run.
- Copies the selected guides into
DEV/doc/and writes a numberedDEV/prompt-NNN.mdlinking everything relative to itself. Diffs and plans are linked where they already are, not copied.
Each selection step searches and selects in one go: type to fuzzy-filter,
↑/↓ to move, Tab to toggle the highlighted item — which also clears the
search, ready for the next one — Enter to confirm (an empty selection is
allowed), Esc to cancel.
The prompt ends with a TASK_DESCRIPTION_HERE placeholder — that is the one
part you write yourself before handing the file over. It opens in Sublime Text
if subl is on your PATH, and is left on disk either way.
A guide whose filename is already in DEV/doc/ is reused rather than
overwritten, and reported as (reused). That is what lets you run prompt
repeatedly through a task without a guide you have since edited being reverted
to the shared copy.
DEV/diffs/ and DEV/plans/ are scanned one level deep only, skipping entries
that start with . or _; guides follow the two-level layout above. With
nothing in any of the three, it exits non-zero rather than writing an empty
prompt.
rig init
Create the directories referenced by your config (worktreesPath, guidesPath),
reporting each as created or exists. A config with neither path set is
reported as nothing to do. The config file itself is not created for you —
init exits with an error when it is missing, naming the shape to write.
rig clean
Remove the entire DEV/ tree in the current project.
Development
pnpm install
# Run from source without building
pnpm dev <command> [args]
pnpm typecheck # tsc --noEmit
pnpm lint # oxlint (lint:fix applies what it can)
pnpm fmt # oxfmt (fmt:check to verify without writing)
# Build to dist/
pnpm buildBefore you push
Nothing runs these for you — there is no CI on push — so run them locally:
make check # lint, typecheck, test (if present), fmt:check, buildlint fails on anything in oxlint's correctness category. The maintainability
limits below are reported as warnings and do not fail the command, so read
its output rather than trusting the exit code.
What the linter enforces
Beyond oxlint's correctness and suspicious categories, four structural
limits are set in .oxlintrc.json:
| Rule | Limit | What it catches |
| ---------------------- | ------------- | ------------------------------------------------------ |
| complexity | 15 (modified) | a function with too many branches to hold in your head |
| max-depth | 4 | nesting that wants an early return or a helper |
| max-params | 5 | an argument list that wants to be an object |
| max-nested-callbacks | 3 | callback pyramids |
The thresholds come from this codebase's own distribution: the median function
scores 3 and the 95th percentile is around 14, so a hit is a real outlier rather
than ordinary branching. Splitting the function is almost always the right fix —
promptCommand is the worked example, now a sequence of named steps. If
splitting genuinely does not apply, suppress the one rule and say why:
/* oxlint-disable complexity -- exhaustive protocol switch, flat by nature */Releasing
@apisurf/rig is a single package. Releases run from the Makefile; make on its own prints the
steps.
make changeset # describe the change: patch, minor or major
make version # apply it: bump versions, write CHANGELOGs
git add -A && git commit -m "Version 1.0.1"
make release # check, build, publish to npm, tag
git push --follow-tagsChangesets runs with commit: false, so make version leaves the bumps and
CHANGELOG entries in the working tree for you to review. Commit them first:
make release refuses a dirty tree, because changeset publish tags whatever
commit is checked out.
make release asks for an npm granular access token (or reads NPM_TOKEN)
and keeps it in memory; .release.npmrc holds only the ${NPM_TOKEN}
reference. The token needs read and write on the whole @apisurf scope — one
limited to selected packages cannot create new ones — and Bypass 2FA checked,
or the registry rejects the publish with EOTP.
Publishing is per package and not atomic, while the git tags are written either
way. Confirm on npm rather than trusting the tags; re-running make release
publishes only what is missing. make pack shows what would ship without
publishing anything.
FAQ
Q: What is a "guide" in rig?
A: A guide is a markdown file containing conventions, instructions, or context that the prompt command links into a generated prompt so an LLM follows it. Guides live in folders within your guidesPath directory.
Q: Do I need both config paths?
A: No. worktreesPath is only needed for wt; guidesPath is only needed to surface guides in prompt; repoParents is only needed for path. Set whichever commands you use.
License
ISC
Need help? Check the configuration at ~/.apisurf/rig.config.json or run rig --help for command usage.
