Verbs
| Verb | What |
|---|---|
build | Build the project |
test | Run the tests |
run / dev | Run the project |
format | Format the code |
lint | Lint the code |
typecheck | Type-check the code |
verify | build → test → run in sequence, then check the artifacts were built together (see below) |
coverage | Run tests with coverage; --min gate; --open report (in-process cobertura→HTML for .NET) |
kill | Kill dev processes by project/pattern/--port (config kill.match wins) |
add / uninstall (remove, rm) / outdated (od) [project] / upgrade | Package management, native per ecosystem. On .NET, outdated reviews every project in the repo (respecting exclude), grouped by project, so a stale package in any in-repo dependency surfaces; name a [project] to scope it, like run |
deps / dependencies | List dependencies with current + latest versions (-u updates only, --vulnerable, --json); whole-repo (per-project) on .NET |
install (restore) / ci / clean / rebuild (rb) | Restore/clean/rebuild (rebuild scopes bin/obj removal on .NET) |
global / dlx / x | Global tool install / one-shot tool run (dnx, pnpm dlx, …) |
publish | dotnet publish with rid/output/self-contained from flags or .rig.json publish.* |
doctor | Environment checklist (SDK pins via nearest global.json), headed by a Setup group: which rig is running and from where, the family on PATH, and what's registered in your shell (see below) |
cd | Fuzzy project navigation (prints the dir; pair with a shell wrapper) |
watch <verb> / rig w r | Watch modifier via the pre-parse pipeline (verb prefixes work too: rig cove) |
init | Scaffold a .rig.json |
info | Show what rig discovered (root, primary ecosystem, .rig.json, per-ecosystem dev commands, packages) — plus a Warnings section for anything wrong with the config |
explain [verb] | Show what a verb resolves to — command, directory, environment, source — without running it (see below) |
config | Manage .rig.json (get / set / show / path / edit) |
default | Show or set the default project for run/publish (interactive picker) |
setup | Install shell integration — cd wrapper + tab completion (zsh/bash/fish/PowerShell); --aliases also adds the short verb aliases |
alias | Install short verb aliases — rr run, rb build, rt test, rcd cd, … (details) |
ui | Interactive bubbletea menu over the dev verbs (capability-gated) |
| custom | Any commands entry in .rig.json becomes a subcommand — shell string, argv, or a cross-platform Tengo script |
| scripts | In a Node repo, every package.json script becomes a verb |
What rig itself has installed
rig doctor opens with a Setup group about rig rather than your project:
Setup
✓ rig 1.4.2 · /Users/john/.local/bin/rig
✓ family shiprig, changerig, clauderig · /Users/john/.local/bin
! shell not installed in ~/.zshrc — `rig cd` can't change your directory
and tab completion is off; run `rig setup zsh`
✓ aliases 4 of 11 installed: rr, rb, rt, rcd · ~/.zshrc- rig — the running binary's version and path. Two copies on
PATHis a warning: the first one answersrig, and it may not be the one you upgraded. Running a build by path (a source build, a-devlauncher) is normal, so it says what typingrigwould run instead of faulting it. - family — which sibling rigs are installed and where. Never a warning: each one's completion is loaded only when present, so installing one later needs a new shell, not a re-run of
rig setup. - shell — whether the
rig setupblock is in your startup file, and whether it's the one this rig would write. Absent or stale is a warning, because both of the things it provides fail silently. Having only a--devblock is called out by name: it's the state behind "I ran setup andrig cdstill does nothing". - aliases — which of
rr,rb,rt, … are actually live, from the same blockrig alias installwrites.
The group runs everywhere, including a directory with no project — that is exactly where you stand when asking why rcd does nothing. Nothing here is touched by rig doctor --fix: writing to your startup file is rig setup's job, and it asks.
Proving a result: verify
build, test and run each answer their own question honestly, and the answers can still be collectively wrong — because nothing checks that the artifacts in play were produced together. A test binary two hours older than the resources it loads passes its own build check and then crashes in code nobody touched.
rig verify does two jobs:
rig verify # build → test → run, stopping at the first failure
rig verify --stale-only # report disagreement, run nothing
rig verify --no-run # build and test only
rig verify --run-timeout 30s # how long "it starts" is given to prove itselfSequencing makes "I checked" mean one thing instead of three. Each step is the same command the standalone verb runs — verify reuses build/test/run rather than reproducing how they resolve a target, so the two can never disagree about what ran.
Agreement is the valuable half. Sequencing alone doesn't solve the problem, it hides it, by rebuilding everything every time — fine for a Go service, unusable where a build takes minutes to hours, which is exactly where stale artifacts survive longest. So verify compares modification times instead:
- With no configuration, the generic check: is anything under the source tree newer than the newest build output? (Source means files a build actually consumes — editing a README doesn't count.) Output locations follow the ecosystem:
bin/distfor Go,dist/build/out/.nextfor Node, per-projectbin/<config>/<tfm>for .NET,target/<profile>for Cargo. Node also getsnode_moduleschecked against its lockfile. - With an
artifactsblock in.rig.json, the artifacts rig cannot infer — generated resources, multi-artifact builds, anout/tree beside the repo (configuration).
✓ build output up to date with main.go
✗ browser out/App.app/Contents/Resources/en.pak is 2h older than src/strings.grd (and 1 more file)
✗ unit-tests out/unit_tests is 2h older than src/renderer.ccNotes on the guarantees, because a check that exits zero while being wrong is worse than no check:
- Staleness is a failure, not a warning —
verifyexits non-zero, so it can gate CI or a pre-push hook. A warning in a long log is what got missed. - Checks that could not run are reported as skipped, never counted as passes; when nothing could be checked, the summary says so instead of printing a green line.
- Nothing is rebuilt implicitly.
--stale-onlyreports and stops; the fullverifyrebuilds by construction. - A directory artifact is judged by its OLDEST file. An app bundle whose newest file is minutes old can still hold a resource the build never refreshed — that bundle looks fresh and loads stale data.
- The run step passes by staying alive. A server or a desktop app never exits, so "still running after
--run-timeout" (default 10s) is the answer to "does it start". An exit with a non-zero status before then is a failure. Setverify.run: false(or pass--no-run) where launching isn't wanted. - The run step is cleaned up completely. It runs in its own process group, so the timeout takes down everything it started —
go run's compiled binary, a dev server's child processes — rather than reporting "it starts" and leaving one behind holding the port.
Across the whole workspace (--all)
rig build --all (and test, format, lint, typecheck, clean) runs the verb in every workspace package in dependency order; --filter <glob> narrows the set. On a terminal you get the live dashboard — one row per package — and off one (CI, piped, --quiet) it streams each package in turn.
A package that doesn't define the verb is skipped, not failed. A Node package with no typecheck script has nothing to type-check, so it is reported as skipped and the run carries on:
· @acme/auth (node)
– @acme/docs (node) — skipped: no "typecheck" script
✓ 34 ok – 1 skippedThat is decided from the package's own manifest before anything runs, so it never hides a real result: a script that runs and exits non-zero still fails the run, still names the package, and still exits non-zero. When no package defines the verb at all, the run says so rather than reporting success.
A failure fails the run without ending it. Every package still runs, and the closing line counts what happened — so one CI log names every broken package instead of only the first:
· @acme/auth (node)
✗ @acme/core (node): exit status 1
· @acme/web (node)
✓ 33 ok ✗ 1 failed – 1 skippedThe error names them too (build failed in 2 packages: @acme/core, @acme/api), since a log long enough to need this is long enough that scrolling back for the ✗ lines is the tedious part. The dashboard has always worked this way; the plain path now matches it, so a run reports the same thing whether or not stdout is a terminal.
Passing flags to the underlying tool
rig owns a small set of flags per verb (--all, --filter, --watch, -i, plus the global --dry-run / --quiet / --no-env / --root). Anything else is rejected rather than guessed at, so a typo like --dry-runn is caught instead of being handed to your package manager. To reach the tool underneath, put the flag after --:
rig build -- --target=host # → pnpm run build --target=host
rig test -- --reporter=dot # → npm run test --reporter=dot
rig dlx prettier -- --write . # → pnpm dlx prettier --write .Everything after -- is forwarded verbatim and never interpreted — it is not read as a project name or a test-class query. Forget it and rig says so, naming the flag and quoting your own command line back with the -- already in place:
ERROR
Unknown flag: --target.
rig build doesn't take --target. To pass it to the underlying command, put it after --:
rig build -- --target=hostReading a verb before you run it
rig explain <verb> prints what the verb resolves to and stops there:
$ rig explain markers
Verb
name: markers
source: custom command · /repo/.rig.json
Command
runs: grep -rho 'sheepish-[a-z-]*' src | sort -u
shell: portable · rig's in-process POSIX shell, same on every OS
dir: /repo
Environment
API_TOKEN=abc · .env / .env.local
SHEEPISH_ROOT=src · .rig.json env
the rest is inherited from the current environment
nothing ran — `rig markers` runs itA custom command can be valid JSON wrapping a valid shell line that quietly does the wrong thing — a character class missing 0-9, a grep -h upstream of a filter that matches on filenames — and it exits 0 while printing something plausible. Nothing can validate that for you, but a resolved command you can read takes seconds to check.
Bare rig explain lists every verb the repo resolves — the ecosystem's dev loop, your commands, the package.json scripts and the script directories — each with the command it becomes.
The resolution comes from the same code the verb runs through, not a second implementation, so what you read is what executes. Verbs that decide part of their command while running (coverage, rebuild, publish, upgrade, outdated) are not guessed at: explain says so, and for the ones whose --dry-run prints an exact command it points there, since that goes through the real path. A few verbs have no such contract — info has no underlying command, and outdated runs its scans without echoing them — so explain says only that it cannot show a guaranteed answer. The same is true for an argument that selects a project or a test filter — rig test MyClass --dry-run rather than rig explain test MyClass.
Ecosystem coverage
The same verb runs the native tool for your stack. A few combinations have no native equivalent and degrade gracefully — with a clear message — rather than failing:
- .NET has no separate
typecheck(it would just bebuild). - Cargo has no
dlxone-shot runner, anddepsfalls back to the plaincargo outdatedoutput rather than the rich table. - Node
cleanruns only when the package defines acleanscript.
The full per-ecosystem matrix lives in docs/ECOSYSTEM-MATRIX.md.
Git & worktree verbs
| Verb | What |
|---|---|
copy / cp | Detached copy of the repo tree to a new folder; --git keeps .git history |
worktree / wt | Parallel-dev sibling worktrees: new / list / open / rm (the menu/list show age, newest-first). Direct branch management is left to git/gh |
prune / tidy | One sweep that reaps merged + gone-upstream worktrees and branches (worktrees first). --worktrees / --branches scope it; at the confirm prompt w/b/a retarget in place. -n previews, -y skips the prompt; off a terminal it refuses without -y. --keep-gone keeps gone-upstream items |
rig worktree new feat/x # sibling checkout off mainline (prints the path)
rig worktree new feat/x --open # …and open a review window for this run
rig worktree new feat/x --repo ~/Git/other # act on another repo without cd'ing there
rig worktree list # this repo's worktrees, newest-first (alias: ls)
rig copy ../scratch --git # detached copy that keeps historySee claudeRig — worktree discipline for how the guard makes worktrees + PRs the default under Claude Code, and Configuration for the worktree.autoOpen / worktree.openCmd keys.
Fused forks: stack
Some projects only make sense together — a library, a second library, and the thing that uses both — and when you maintain forks of all three, iterating means publishing a package to see a change land. A stackspace fuses those repos into one history, each under its own directory, so a change spans them in a single commit and the build compiles against source rather than packages.
Nothing downstream learns about it. Each repo keeps its own remote, and work leaves one project at a time: propose produces an ordinary pull request to a fork you contribute to, and push fast-forwards a repo you own with its history.
| Verb | What |
|---|---|
stack setup | Set up a freshly cloned stackspace: install the fusion engine, import the members, write the build overlay, print the status. The one command a new clone needs; safe to run again |
stack init | Write the manifest, or import the repos it names into this history (and a generated README.md, unless one is there that rig does not own); a private upstream is reached with your gh login, and an import whose fetch brought nothing back is refused rather than recorded |
stack add [upstream] | Add a repo to this stackspace and import it; asks when not given |
stack rm <repo> | Remove a repo — manifest entry, directory and overlay redirects; refuses while it holds unsent or uncommitted work, or files git ignores (--force overrides); --keep-tree keeps the directory, and needs a clean one even with --force |
stack seed <dir> | Export the root files as a small repo (conventionally rigstack-<name>, suggested outside enclosing repositories); stack init on a clone of it rebuilds every member at its recorded cursor, or from the fork branch it was last proposed to while that branch still exists; refuses while a member holds unsent commits (--force) |
stack status | Each repo's cursor against its upstream, whether it holds work that has not left — all of which propose would send — and, listed under it, each stack-pr-* topic in flight with the fork branch its pull request is on, not proposed yet, whether the branch has moved since, and whether a pull left it behind |
stack pull [repo] | Merge new upstream commits into a repo's directory (all repos by default); private upstreams through your gh login, as with init |
stack propose [repo] [new-branch] | Put that repo's changes on your fork as a PR-ready branch — all of them, the prefix's whole divergence from upstream; --from <branch> sends only what a topic branch of the stackspace adds (a bare name resolves to the conventional stack-pr-<name>), so a second fix can be its own pull request (needs trackBranch, which it then keeps current); --dry-run shows the commit and branch that would go and touches no remote; both report any package the branch references that this stackspace provides from source rather than a feed, since a plain checkout of the branch cannot restore those |
stack push [repo] | Fast-forward a repo you own with this stackspace's commits, history intact; inferred when only one is yours; --dry-run shows the target, branch and commits that would go and touches no remote |
stack wire | Write the build overlay so members resolve each other from source |
stack pack [repo] | Build a member's publishable packages from inside the stackspace, where the build overlay makes cross-member references resolve from source (every member with no argument); --out chooses the directory, default dist/; refuses while the overlay is missing, since without it the packages are the ones a bare checkout would produce |
stack doctor | Check the engine and manifest; --fix installs what's missing |
rig stack init # writes rig.stack.jsonc to fill in
rig stack init # again: imports each repo it names
rig stack setup # a fresh clone: engine, members, overlay, status
rig stack status # who has moved upstream, and where each topic's PR is
rig stack pull pty-core # take that movement
rig stack propose pty-core read-timeout -m "Fix the read timeout" # → stack/read-timeoutImporting and pulling are done by josh, the git history-filtering proxy; propose uses no engine at all, since the directory's tree is already what upstream wants. rig fetches a verified josh-proxy on first use where one is published for your platform and pinned version, and builds it from source otherwise.
Stackspaces is the full guide: setting one up, wiring the build so the consumer compiles against source, sending and updating pull requests, and the rules that will otherwise catch you out. Manifest keys are in Configuration.
Prefix matching
Verbs prefix-match, so rig cove runs coverage and rig w r is watch run. The watch modifier rides the same pre-parse pipeline, so it composes with any verb.
Discovered verbs
In a Node repo, every package.json script becomes a verb. In a Go repo, any main package under scripts/ or cmd/ is surfaced as a bare rig <name> verb — these are exact-match only (excluded from prefix-matching) and never shadow a built-in. rig run offers those cmd/* binaries directly instead of falling through to a doomed go run ..
How a project name resolves
rig run <name>, rig build <name>, rig cd <name> and a configured defaultProject all resolve through the same rules, in this order:
- Best tier wins, and it stops there — exact, then prefix, then substring (
rig cdalso accepts subsequences). One exact hit is never ambiguous just because looser matches exist:Tweed.Appis that project, not it plusTweed.App.Tests. Names match in full, slash-short (@scope/pkg→pkg), and dot-short (Acme.Desktop→Desktop) form. - Ties break by proximity to your working directory — the project you're standing in wins, then the nearest common ancestor.
- Anything still tied is ambiguous, and rig says so — a picker on a terminal; off one, the candidate paths, what each copy is (a nested worktree, and whether
rig prunewould remove it), and the exactexcludeline to paste into.rig.json.
A bare rig run obeys the same rules as rig run <name>. A defaultProject matching two checkouts fails exactly as the explicit command would rather than launching whichever was found first, and when it resolves cleanly rig echoes what it picked:
· defaultProject Tweed.App → ui/src/Tweed.AppOpening the picker (-i / --interactive)
At a stackspace root where targets live only in subdirectories, a bare rig run (or build/test/format/lint/typecheck/clean/rebuild) opens a picker — no flag needed. run lists the runnable packages and the repo's surfaced scripts; the other verbs list packages only. When one obvious target would run directly, pass -i/--interactive to force the picker anyway:
rig run # picker only when there's no single target
rig run -i # always pick, even with one obvious target
rig build --interactive # same, for the --all-capable verbs
rig rebuild -i # rebuild a chosen package, or "All packages"rebuild carries its own picker (it sequences clean → build, so it has no single command to ride the shared one): rig rebuild <project> scopes the rebuild to one package, and the picker's All packages rebuilds each in dependency order.
Off a TTY there's no picker, so -i reports a helpful error and points you at rig <verb> <project>.
Picker controls (exclude / include)
When rig run (or the rig ui project menu) lists several projects, you can curate the set live:
x— exclude the highlighted project from future discovery. In a crowded directory (≥3 siblings) it asks whether to hide just that project or the whole<dir>/*.i— show/hide excluded projects; while shown they appear struck-through, and pressingion one re-includes it.
Exclusions are written to .rig.json's exclude globs and match against the project's full name, short name, and repo-relative path.