Skip to content

Verbs

VerbWhat
buildBuild the project
testRun the tests
run / devRun the project
formatFormat the code
lintLint the code
typecheckType-check the code
verifybuildtestrun in sequence, then check the artifacts were built together (see below)
coverageRun tests with coverage; --min gate; --open report (in-process cobertura→HTML for .NET)
killKill dev processes by project/pattern/--port (config kill.match wins)
add / uninstall (remove, rm) / outdated (od) [project] / upgradePackage 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 / dependenciesList 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 / xGlobal tool install / one-shot tool run (dnx, pnpm dlx, …)
publishdotnet publish with rid/output/self-contained from flags or .rig.json publish.*
doctorEnvironment 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)
cdFuzzy project navigation (prints the dir; pair with a shell wrapper)
watch <verb> / rig w rWatch modifier via the pre-parse pipeline (verb prefixes work too: rig cove)
initScaffold a .rig.json
infoShow 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)
configManage .rig.json (get / set / show / path / edit)
defaultShow or set the default project for run/publish (interactive picker)
setupInstall shell integration — cd wrapper + tab completion (zsh/bash/fish/PowerShell); --aliases also adds the short verb aliases
aliasInstall short verb aliases — rr run, rb build, rt test, rcd cd, … (details)
uiInteractive bubbletea menu over the dev verbs (capability-gated)
customAny commands entry in .rig.json becomes a subcommand — shell string, argv, or a cross-platform Tengo script
scriptsIn 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 PATH is a warning: the first one answers rig, and it may not be the one you upgraded. Running a build by path (a source build, a -dev launcher) is normal, so it says what typing rig would 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 setup block 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 --dev block is called out by name: it's the state behind "I ran setup and rig cd still does nothing".
  • aliases — which of rr, rb, rt, … are actually live, from the same block rig alias install writes.

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:

sh
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 itself

Sequencing 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/dist for Go, dist/build/out/.next for Node, per-project bin/<config>/<tfm> for .NET, target/<profile> for Cargo. Node also gets node_modules checked against its lockfile.
  • With an artifacts block in .rig.json, the artifacts rig cannot infer — generated resources, multi-artifact builds, an out/ 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.cc

Notes on the guarantees, because a check that exits zero while being wrong is worse than no check:

  • Staleness is a failure, not a warningverify exits 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-only reports and stops; the full verify rebuilds 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. Set verify.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 skipped

That 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 skipped

The 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 --:

sh
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=host

Reading 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 it

A 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 be build).
  • Cargo has no dlx one-shot runner, and deps falls back to the plain cargo outdated output rather than the rich table.
  • Node clean runs only when the package defines a clean script.

The full per-ecosystem matrix lives in docs/ECOSYSTEM-MATRIX.md.

Git & worktree verbs

VerbWhat
copy / cpDetached copy of the repo tree to a new folder; --git keeps .git history
worktree / wtParallel-dev sibling worktrees: new / list / open / rm (the menu/list show age, newest-first). Direct branch management is left to git/gh
prune / tidyOne 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
sh
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 history

See 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.

VerbWhat
stack setupSet 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 initWrite 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 statusEach 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 wireWrite 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 doctorCheck the engine and manifest; --fix installs what's missing
sh
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-timeout

Importing 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:

  1. Best tier wins, and it stops there — exact, then prefix, then substring (rig cd also accepts subsequences). One exact hit is never ambiguous just because looser matches exist: Tweed.App is that project, not it plus Tweed.App.Tests. Names match in full, slash-short (@scope/pkgpkg), and dot-short (Acme.DesktopDesktop) form.
  2. Ties break by proximity to your working directory — the project you're standing in wins, then the nearest common ancestor.
  3. 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 prune would remove it), and the exact exclude line 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.App

Opening 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:

sh
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 pressing i on 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.

Released under the MIT License.