Metadata-Version: 2.4
Name: hyper-rig
Version: 0.53.0
Summary: rig — the dev-environment umbrella driver: set up a repo from a committed rig.yaml by applying agent-tools content (skills, hooks, CI gates, MCP).
Author: Alex Mextner
License: MIT
Project-URL: Homepage, https://git.hyperide.ai/ultrabricks/rig-cli
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml
Requires-Dist: textual>=0.66
Requires-Dist: rich>=13
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: tomli; python_version < "3.11" and extra == "test"
Dynamic: license-file

# rig

**One tool. One config. The whole dev culture of the coding-agent era — installed.**

`rig` is the single front door to an entire ecosystem of agent-native tooling. From one
committed, declarative `rig.yaml` it sets up a repository — and a developer's machine — wiring
in the **skills**, **agent-hooks**, global **git-hook dispatcher**, **CI gates**, and **MCP
servers** that keep a team's engineering discipline intact when most of the code is written by
agents. One command, the same guardrails, every time, on every machine.

In the coding-agent era the bottleneck isn't writing code — it's keeping a hundred parallel
agent sessions *on-culture*: tests first, secrets never committed, review before merge, an
auto-mode that's actually safe. `rig` installs and reconciles that culture from the portable
catalog in [`agent-tools`](https://git.hyperide.ai/ultrabricks/agent-tools) — the **WHAT** (the
content) to rig's **HOW** (apply it, reconcile it, prove it).

It's a peer to the rest of the ecosystem — [`tg-cli`](https://git.hyperide.ai/ultrabricks/tg-cli),
[`review-cli`](https://git.hyperide.ai/ultrabricks/review-cli),
[`draw-cli`](https://github.com/alex-mextner/draw-cli),
[`3d-cli`](https://github.com/alex-mextner/3d-cli),
[`task-cli`](https://git.hyperide.ai/ultrabricks/task-cli) — composable, agent-native CLIs that
share one config-and-skills backbone. `agent-tools` is the **WHAT** (portable skills, guards,
CI gates, MCP); `rig` is the **HOW** — it reads your `rig.yaml`, converges the repo and the
machine to it (idempotently, with backups), and surfaces drift in both directions.

![rig apply converges the repo to rig.yaml; rig status reports drift both ways](./docs/img/reconcile.svg)

## One development culture, from one control plane

Rig treats development setup and engineering policy as one declarative system rather than a pile of unrelated dotfiles. A global machine layer can establish defaults; committed `rig.yaml` files make repository-specific differences reviewable and reproducible. The same engine previews, applies, verifies, and reports drift.

That control plane already spans coding-agent skills and hooks, git hooks, CI gates, MCP servers, harness permissions/auto-mode, GitHub/repository settings, project-tool integrations, model/tool maintenance, and now JS/TS lint/format policy through Oxlint/Oxfmt + anti-slop. The intent is that humans and coding agents encounter the same constraints and preferred practices instead of each harness or repository inventing its own culture.

Where the surrounding tool exposes a preventative boundary, Rig installs a guardrail; where it cannot, CI/status/verification can still detect drift or unsafe state. The next cross-domain enforcement/advise model is tracked in [#225](https://git.hyperide.ai/ultrabricks/rig-cli/issues/225), with consistent better-practice recommendations in [#229](https://git.hyperide.ai/ultrabricks/rig-cli/issues/229).

This is useful for a team, but also for one developer with many repos and several agents: one policy source reduces setup drift, makes a new checkout predictable, and makes agent behavior less dependent on whichever harness happened to start the session. A dedicated onboarding/attestation command is tracked in [#228](https://git.hyperide.ai/ultrabricks/rig-cli/issues/228).

Today Rig already has machine-wide global defaults plus per-repository overrides. The broader “change once everywhere” layer is explicit roadmap work rather than a hidden promise: fleet reconciliation [#222](https://git.hyperide.ai/ultrabricks/rig-cli/issues/222), repository/stack/tag targeting [#227](https://git.hyperide.ai/ultrabricks/rig-cli/issues/227) and [#233](https://git.hyperide.ai/ultrabricks/rig-cli/issues/233), shareable team policy packs [#223](https://git.hyperide.ai/ultrabricks/rig-cli/issues/223), and cross-domain `rig rules` / explain [#224](https://git.hyperide.ai/ultrabricks/rig-cli/issues/224). The goal is to change lint, CI, hooks, agent capabilities, skills, MCP and other development policy globally—or only for the relevant stacks/projects—with one previewable operation.

## Install

Pick one — each ends with `rig` on your PATH:

```bash
# 1. uv — recommended; macOS, Linux and Windows (incl. Cygwin / Git Bash)
uv tool install hyper-rig

# 2. pipx
pipx install hyper-rig

# 3. one-liner — clones to ~/.local/share/rig-cli, links rig into ~/.local/bin, registers the agent skill
curl -fsSL https://git.hyperide.ai/ultrabricks/rig-cli/raw/branch/main/install.sh | bash

# 4. from a clone — to hack on rig; `git pull` updates the installed tool
git clone https://git.hyperide.ai/ultrabricks/rig-cli && cd rig-cli && ./install.sh
```

- After **1** or **2**, run `rig install-skill` once so coding agents discover `rig` (3 and 4 do it for you).
- **Unreleased `main`:** `uv tool install git+https://git.hyperide.ai/ultrabricks/rig-cli` (or the same with `pipx install`).
- **Update:** `uv tool upgrade hyper-rig` · `pipx upgrade hyper-rig` · re-run the one-liner · `git pull`.
- **Installed before the rename** (as `rig-cli`, from git)? Remove that first — `uv tool uninstall rig-cli` or `pipx uninstall rig-cli` — or two installs both claim `rig`.
- **Run without installing:** `uvx --from git+https://git.hyperide.ai/ultrabricks/rig-cli rig doctor`,
  or from a checkout `uv run bin/rig …` / `python3 bin/rig …`.

> **On PyPI as `hyper-rig`** (the command is still `rig`). `rig-cli` on PyPI is an unrelated project — `pipx install rig-cli` would install that. The canonical repo is
> **git.hyperide.ai/ultrabricks/rig-cli**; `github.com/alex-mextner/rig-cli` is a frozen, archived mirror.

`textual` (the `rig init` setup wizard) and `rich` (the `rig stats` report) are **core
dependencies** — every install above brings them, so `rig init` from a terminal launches the
wizard with **no extra install step and no "go install textual" prompt**.

### Windows (Cygwin, Git Bash, PowerShell)

Use **uv** (option 1; get uv with `winget install astral-sh.uv`). `./install.sh` and the one-liner
detect Cygwin/MSYS and do the same thing for you (`uv tool install --editable` from a clone,
from git otherwise).

- uv puts a native `rig.exe` into `%USERPROFILE%\.local\bin`, which is already on PATH in every
  Windows shell. The POSIX symlink install can't work there: Python is a native Windows program, it
  can't run a `#!/usr/bin/env python3` script, and Cygwin's own `~/.local/bin` is not on PATH.
- rig's home is your **Windows profile** (`%USERPROFILE%`), not Cygwin's `~`: the global config is
  `%USERPROFILE%\.config\rig\config.yaml`, skills go to `%USERPROFILE%\.agents\skills`.
- `rig install-skill` links the skill into `~/.claude/skills` as a **directory junction** when
  Windows refuses a symlink. `rig apply` makes many more symlinks — turn on **Developer Mode**
  (Windows 10: Settings → Update & Security → For developers; Windows 11: Settings → System →
  For developers) so Windows lets you create them without admin rights.
- mintty (the Cygwin / Git Bash terminal) isn't a Windows console, so `rig init` prints the
  non-destructive preview instead of the wizard: use `rig init --yes`, or run `rig init` from
  Windows Terminal / PowerShell for the TUI.
- Keep an agent-tools checkout where rig looks for one (`~/work/agent-tools`, `~/xp/agent-tools`,
  `~/agent-tools`), or point at it: `rig config set --global agent_tools_source <path> --commit`.

### Forgejo (`fj`) — log in with one click

The ecosystem lives on Forgejo at **git.hyperide.ai**; its CLI is `fj`
([forgejo-cli](https://codeberg.org/forgejo-contrib/forgejo-cli)). Put the host in the global
config once — `rig config set --global fj.host git.hyperide.ai --commit`, or keep it in your
`.rig-profile` — and `rig apply`:

- **installs fj** — `brew install forgejo-cli` on macOS; on Linux and Windows the sha256-pinned
  release binary into rig's own dir `~/.local/share/rig/fj/<version>/` (not on PATH — see below);
- **adds `fj ship`** — the **same ship gate as `gh ship`**: `fj` on PATH is a thin rig wrapper in
  `~/.local/bin` whose `fj ship <PR>` runs exactly what the `gh ship` alias runs (the repo's
  `.claude/scripts/pr-ship.sh`, else agent-tools' `ci/ship/ship.sh`, else exit 127), and whose every
  other `fj …` runs the real fj with the arguments, stdin and exit code untouched;
- **turns `fj auth login` into a browser login** — every Forgejo instance ships built-in public OAuth
  apps; rig lists one for the host in fj's `client_ids`, so there is no token to mint and no app for
  an admin to register. fj refreshes the token itself;
- **does the same for git** — with Git Credential Manager (bundled with Git for Windows) rig adds its
  OAuth settings for `https://<host>`: the first `git push` opens the browser once;
- **drops `--host`** — `FJ_FALLBACK_HOST=https://<host>` in every shell (the `env` area on
  macOS/Linux, a user environment variable on Windows). Inside a clone fj still follows its remote.

Then, once per machine:

```bash
fj auth login      # browser opens → Authorize
```

A repo whose `rig.yaml` names another instance (`fj: { host: … }`) gets that host set up too when
you `rig apply` in it. Headless machines: `fj auth add-token` with a token from
`https://<host>/user/settings/applications`. `rig status` shows anything not yet set up. Full
reference: [docs/config-schema.md](docs/config-schema.md#fj).

**Where the real fj lives** (`fj` itself is always the wrapper in `bin_dir`, `~/.local/bin`):
Linux — `${XDG_DATA_HOME:-~/.local/share}/rig/fj/<version>/fj`; macOS — Homebrew's, found on PATH
(so `~/.local/bin` must come **before** Homebrew on PATH — `rig status` says so when it doesn't);
Windows — `%USERPROFILE%\.local\share\rig\fj\<version>\fj.exe`, with two wrappers in
`~/.local/bin`: `fj` (sh) for Cygwin / Git Bash and `fj.cmd` for cmd / PowerShell, where `fj ship`
runs through the same `sh` gh uses for its aliases (Git for Windows'). No `fj.exe` stays in
`~/.local/bin`: cmd, PowerShell and Python would pick it over `fj.cmd`. An fj that an older rig put
into `~/.local/bin` is recognised by its pinned sha256 and moved; any other file there is left alone
(and reported). `fj.install: false` leaves the `fj` command to you — no wrapper, so no `fj ship`.

## Commands

| Command | One-line |
| --- | --- |
| `rig init` | **First-run onboarding.** Scaffold `rig.yaml` and **preview** the agent-tools catalog it would wire in (with opt-out) — the front door for a repo/machine with no config yet. init never applies on its own: a bare `rig init` (no TUI/flags) writes **nothing** (pure preview); `rig init --yes` writes `rig.yaml` (config only); **`rig apply commit` is what applies it** (or `rig init --yes --apply` to scaffold + apply in one step). |
| `rig apply` | **Declarative reconcile** (kubectl-style): read `rig.yaml`, compute the diff vs the repo's state, converge, idempotently. **Preview-by-default:** a bare `rig apply` is an alias for `rig apply info` — it prints the plan and **mutates nothing**; **`rig apply commit`** actually executes it (with per-phase progress and a `✓ applied N (C changed, M unchanged)` completion line). The steady-state command you re-run on every machine; hand-edits that drift from the config are surfaced by `rig status`. `--dry-run` previews; `--only skills,ci` scopes; `-v` lists already-in-sync no-ops; a bare `rig apply --yes` executes (automation back-compat). |
| `rig status` | Detect + report **drift in both directions**, grouped by GLOBAL/REPO layer and by every area rig reconciles (skills, all configured agent-hook targets, CI, MCP, symlinks, repo settings, auto-mode, tmux, model cron). |
| `rig doctor` | Detect + (offer to) install every tool rig/agent-tools need, across brew / apt / dnf / pacman / zypper. `--yes` installs non-interactively. |
| `rig export` | Write a starter `rig.yaml` from detected defaults without a TUI (recommends **auto-mode on**). |
| `rig setup` | **The interactive configuration wizard.** In a terminal it shows what is enabled across every reconciled area, lets you change any option (with an inline hint per option) in the local `rig.yaml` AND the global `~/.config/rig/config.yaml`, then applies. Non-interactive (piped / no TTY) it prints usage for `init`/`apply`/`config get\|set`. |
| `rig config get\|set` | **The headless counterpart to the wizard.** `get` reads one nested key. `set <dot.path> <value>` is **preview-by-default**: it validates the prospective config entirely in memory, shows the config change + resulting reconcile plan, and writes nothing. Add **`--commit`** to write the change and execute that same validated plan; `--no-apply` is the explicit legacy write-only mode. `--global` targets `~/.config/rig/config.yaml`. |
| `rig env list\|set\|del` | **Machine-wide `env.vars` keys** (search API keys, `COLORTERM`, …). Always writes `~/.config/rig/config.yaml` (the same path as `rig config set --global env.vars.KEY`). `set`/`del` write + reconcile by default so `~/.config/rig/env/rig.env.sh` updates; omit VALUE to read stdin (keeps the secret out of argv/history). `list` **masks** values (`***` + last 4); `--show` prints them in full. **Both files are plaintext** — this is not a secret store. |
| `rig profile init\|sync\|status` | **Carry your global config between machines** through a **private** git repo (`<forge>/<login>/.rig-profile`) holding `~/.config/rig/config.yaml`. `init [URL]` clones it (or seeds an empty repo with this machine's file and pushes) and records `profile.repo`; without a URL it uses `profile.repo`, else `https://<host>/<fj login>/.rig-profile` when `fj whoami` knows the login. `sync` is two-way: only local changed → commit + push; only remote changed → pull (the local file is backed up first); both changed → **refused**, nothing written — pick a side with `--take local\|remote`. `status [--fetch]` says which side changed, writing nothing (exit `3` when not in sync). Once a profile is set up, `rig apply commit` syncs it first on every machine (non-fatal; `profile.auto_sync: false` turns that off). Pushes warn about `env.vars` secrets and are refused to a repo the forge reports as public. See [below](#rig-profile--carry-your-global-config-between-machines). |
| `rig config-web` | **A machine-wide web console** — one browser tab per rig-managed repo (discovered from the repository registry) plus a Global tab for `~/.config/rig/config.yaml` alone. Each tab renders every area with its live effective value, tagged with the layer an edit lands in, next to a **drift panel** (the same two-way engine `rig status` uses). An edit routes to the **owning layer** and is written by the **same engine** as `rig config set`/the wizard (fail-closed validation) — then the page offers an **interactive apply**: a plan preview (mirrors `rig apply info`, each action tagged with what kind of change it is — creates a file, installs a hook, changes a permission, etc.), confirm-or-skip individual actions, then live per-phase progress as the **same shared engine** (`plan.build` + `run_plan`) `rig apply`/`rig init` use actually applies them. A stale preview (config changed since you looked) is refused rather than silently applied; only one apply runs at a time. Lifecycle is the shared `agenttools-service` manager: `run` (foreground) / `start` (background daemon) / `status` / `stop` / `enable` (install launchd-macOS / systemd-`--user`-Linux autostart + start) / `disable`. Binds `127.0.0.1` only, with a same-origin (CSRF) + Host (DNS-rebinding) guard on every mutating/compute-triggering request. A bare `rig config-web` prints help, never launches. `--port`, `-C <repo>`. **The lifecycle verbs need the `agenttools-service` lib** (an agent-tools nested lib, not on PyPI): `uv pip install --python <rig's interpreter> -e <agent-tools>/lib/agenttools_daemon -e <agent-tools>/lib/agenttools_service` (the `--python` target makes the libs land where rig imports them; the error message prints the exact interpreter path). Without it, `rig --help` and every other command still work; a lifecycle verb fails closed with that install hint. |
| `rig evolve` | **Project evolution portal.** Serve a local browser UI with a git activity histogram, proportional file treemap, clickable selection, and provider health. The first slice is file-level and read-only; symbol/LSP/provider overlays build on the same API. Lifecycle uses the shared `agenttools-service` verbs: `run` / `start` / `status` / `stop` / `enable` / `disable`. A bare `rig evolve` prints help, never launches. `--port`, `-C <repo>`. |
| `rig install-skill` | Register the `rig` agent skill so skills-directory harnesses auto-discover it (currently Claude Code and Codex). |
| `rig stats show` | **Tool-adoption and escape-hatch analytics.** Parse the session logs of every agent harness on the machine and report how often each tool is invoked, bucketed into baseline / ours / review-cycles / external-advertised / other — so you can see whether the rig + agent-tools ecosystem is actually being used vs the built-in baseline. Also reads `~/.config/agent-tools/overrides.log` and hatch-relevant `ship-audit.jsonl` bypass lines, grouping escape hatches by hook and splitting **broken-gate** vs **lazy**. `` `--format json|tui|web` ``, breakdowns by repo/harness, a daily trend (the `json` output additionally exposes the weekly series). |
| `rig worktree create` | **Standardized agent-worktree creation.** Idempotently registers `.worktrees/` in the repo's `.git/info/exclude` (never the committed `.gitignore`), THEN creates a linked git worktree at `<repo>/.worktrees/<name>` — the one convention rig converges every repo on, replacing the several ad hoc locations (Claude Code's own `.claude/worktrees/`, a bare in-repo `.worktrees/`, a sibling-of-repo `.worktrees/`) that had grown up around the ecosystem — so a freshly created worktree never dirties `git status` in the primary checkout, and a broken exclude file is caught before any worktree exists. `--branch` overrides the branch name (default: `<name>`); `--from <ref>` overrides the base ref (default: `HEAD`). In a bun repo with `bun.enabled`, it then runs `bun install --frozen-lockfile` in the new tree, as the repo's own bunfig configures it (bounded at 15 min; only the global config turns it on, `bun.worktree_install: false` turns it off globally or per repo), and `rig status` flags broken worktree installs — a `node_modules` symlink, broken links into the store — and notes off-store ones (see `docs/config-schema.md` `bun`). |
| `rig worktree remove` | **The inverse of `create`.** Force-removes the linked worktree at `<repo>/.worktrees/<name>` (`git worktree remove --force`), then deletes the branch it had checked out (`git branch -D`) — read from `git worktree list --porcelain -z`, not assumed to equal `<name>`, since `create --branch` can diverge from the directory name. `git worktree remove` alone leaves the branch behind, so skipping this step would make a bare retry of `create` with the same name fail with "a branch already exists". Refuses outright (no worktree removed) if the branch can't be reliably identified first, if `.worktrees/` is a symlink pointing outside the repo, or if `.worktrees/<name>` ITSELF is a symlink (a real worktree root is never a symlink, so this refusal never blocks legitimate use). A genuinely detached-HEAD worktree (no branch to find) is removed cleanly with no second step. Also recovers a worktree whose directory was deleted by hand (`rm -rf` instead of `git worktree remove`) but is still registered with a live branch. Requires **git >= 2.36**. |
| `rig worktree gc` | **Classify and clean up worktree sprawl.** Lists every worktree `git` knows about for a repo — wherever it physically lives, not just the standardized `.worktrees/<name>` — and classifies each as `live` (a running `claude`/`codex`/`opencode` process has it as its cwd — checked FIRST, absolutely, before anything else), `prunable` (its directory is gone but git still has it registered), `dirty` (uncommitted changes — never auto-removed), `merged`/`closed` (its PR resolved via `gh pr list`), `no-pr-stale` (clean, no PR, older than `--older-than-days`, default 14), or `active` (an open PR, or recent activity). Report-only by default; `--yes` actually removes `merged`/`closed`/`prunable` (plus the branch, mirroring `remove`'s two-step contract); `no-pr-stale` additionally needs `--include-stale`. `--dry-run` always forces a report even with `--yes`, mirroring `rig apply --dry-run`. `--repo <path>` targets one repo; omitted, it fans out over every repo the machine-local repository registry (`rig config-web`'s same discovery) already knows about. `rig status` reports a cheap (no disk-size scan) stale-worktree count using the same classifier. **Caveat:** a merged/closed worktree is only auto-removed once its branch's commits are unreachable from no surviving ref — with squash/rebase-merge + `fetch --prune` (a common GitHub setup) that's never true, so `merged` worktrees classify `dirty` and are kept instead; see the module's "Known limitations" for the full trade-off. |
| `rig daily` | **Merged-PR "what shipped" report**, ready to paste into a Slack Daily channel. Source of truth is `gh pr list --state merged` — never an LLM call, never a ticket status alone. Grouped into Security / Infra-CI / Performance / Product-UX / Other, one plain-language fact per line, ticket/PR reference last in parentheses. Default repos: `hyperide/hyper-saas`, `hyperide/hyper-ext-e2e` (override with repeatable `--repo` or `~/.config/rig/daily.yaml`'s `repos:`). Tracks a PER-REPO watermark at `~/.config/rig/daily-state.json` so a plain `rig daily` run never repeats a PR — each repo's watermark only advances when THAT repo's own fetch succeeded and returned a complete page, so one repo's outage or a newly-added repo never borrows another repo's cursor; if every configured repo fails, the command exits non-zero instead of a misleading empty report. `--since` (relative `24h`/`7d` or an ISO-8601 timestamp) and `--dry-run` are always read-only. `rig daily install-skill` registers the `daily` agent skill the same way `rig install-skill` does. |
| `rig report` | **HTML completion report** from git history since the merge-base with main — the HyperIDE `/result-report` gather, as a rig command. Writes a self-contained HTML file you can open (`file://` path printed on stdout) plus a text summary of commits and files. Does not publish to GitHub Pages. `rig report [TASK_ID]`, `--title`, `--out PATH`, `--base REF`, `-C`. |
| `rig usage` | **Claude token/cost usage across accounts.** Aggregates real per-message token usage from `~/.claude/projects` and every `~/.claude-accounts/account-*/projects` (the accounts managed by the separate `claude-rotate` tool), by model, account, and token type (input/output/cache-write/cache-read). Cost is a HYPOTHETICAL estimate at published Claude API list prices — this is a Claude.ai subscription, not pay-per-token billing, so it is never a real bill; a model ID not in the priced table is reported separately as "unpriced", never guessed at. Bare `rig usage` reports the current week AND current month; `--period day\|week\|month` narrows to one window. `--json` emits the stable, versioned contract (`schema`, `generated_at`, `disclaimer`, `accounts_scanned`, `periods`) that a separate, independently-built tg-cli command invokes on a schedule: `rig usage --json --period week` at end-of-week, `rig usage --json --period month` at end-of-month. Read-only; parsing is streaming/line-by-line with mtime-based file pruning, so a scheduled run only re-parses the files actually touched in the requested window, not the whole history. |

Not rig subcommands, but provisioned by `rig apply`: **`gh ship <PR>`** (a gh alias, see
[`ship_delegator`](docs/config-schema.md#ship_delegator)) and **`fj ship <PR>`** (the `fj` wrapper
the [`fj`](#forgejo-fj--log-in-with-one-click) block installs) — one command under two names: both
run the same dispatch to the repo's ship delegator / agent-tools' `ship.sh`, which picks GitHub or
Forgejo by the origin's host (its Forgejo provider: agent-tools#793).

The Codex updater that used to be `rig codex update` moved to harness-cli as `harness codex update` (same options); rig reconciles the dev environment and no longer updates agent-harness binaries.

### Quick start — `init` then `apply`

There are two commands, and they are **not** the same thing: `rig init` is first-run
onboarding (no config yet → scaffold one + **preview** the catalog it would wire in);
`rig apply` is the steady-state reconcile (config exists → converge the disk to it), and it too
is **preview-by-default** — a bare `rig apply` prints the plan and applies nothing; `rig apply
commit` is what actually executes. You run `init` once to scaffold + review the plan, then `rig
apply commit` to apply (and re-apply forever after; `rig apply` alone to re-preview). The default
rig.yaml `init` writes provisions **auto-mode** (the agent
runs autonomously with minimum babysitting) — recommended on by default, *and safe because the
agent-hook guards are applied alongside it.*

**`init` does NOT apply by default — that is deliberate.** A bare `rig init` with no TUI and no
flags writes **nothing** and applies **nothing**; it prints a non-destructive PREVIEW of the plan
and how to proceed (it should never "do a bunch of things" with no instruction). `rig init --yes`
scaffolds `rig.yaml` (config only — still nothing applied), then you run `rig apply commit`. To do
both in one step, use `rig init --yes --apply` (the explicit one-shot).

**How `init` decides its mode (TTY + flags).** A bare `rig init` runs the interactive TUI wizard
(with Export-config-only vs Apply buttons) **whenever there is a TTY** — `textual` ships WITH rig
as a core dependency, so the wizard is always available; no install step. With no TTY (piped / CI
/ agent), or with `--no-tui` / `RIG_NO_TUI=1`, it falls back to the non-destructive PREVIEW instead
of hanging on a wizard nothing can drive. Any explicit signal (`--yes` / `--config … --yes` /
`--apply`) is non-interactive. (`rig apply` is never interactive — it has no wizard; `rig apply
commit` executes headlessly.)

```bash
rig doctor                                    # check deps; rig doctor --yes to install
rig init                                       # no config yet: scaffold rig.yaml + PREVIEW the plan
rig apply                                      # PREVIEW what apply would do (mutates nothing)
rig apply commit                               # execute it (and re-apply on every machine, identically)
rig init --yes --apply                         # or scaffold + apply in one step (the explicit one-shot)
rig status                                     # later: has the repo drifted from rig.yaml?
rig setup                                      # interactive wizard: see + change every area, then apply
```

To edit the config before applying: `rig export -o rig.yaml`, tweak it, then `rig apply commit`.

**`rig setup` — the interactive config wizard.** In a terminal it shows what is enabled across
every reconciled area (the `rig status` rows), lets you toggle/change any option in the local
`rig.yaml` AND the global `~/.config/rig/config.yaml` — each option with an inline hint of how
and why — then applies the change on the spot. Run from a non-TTY (a pipe/redirect) it prints
usage for the core commands instead of a half-wizard. For scripted single-value edits use its
headless counterpart `rig config get <dot.path>` / `rig config set <dot.path> <value>` — a
dot-path editor whose writes are previewed by default. Add `--commit` to write + reconcile;
`--global` targets the global config and `--no-apply` is explicit write-only mode.

Headless / agent path (no TUI):

```bash
rig init --yes                                 # scaffold rig.yaml (config only; nothing applied)
rig apply commit                               # apply it; re-apply identically on every machine
# or, the explicit one-shot:
rig init --yes --apply                         # scaffold rig.yaml AND apply in one step
```

## `rig stats` — is the ecosystem actually being adopted?

`rig apply` installs the tooling; `rig stats` tells you whether anyone is *using* it. It
reads the on-disk session logs of every agent harness on the machine and counts how often
each tool is invoked, sorting every invocation into five buckets:

- **baseline** — the harness built-ins (`Bash`, `Read`, `Write`, `Edit`/`MultiEdit`,
  `Grep`, `Glob`, `NotebookEdit`, `Task`/`Agent`, `WebFetch`/`WebSearch`). The yardstick.
- **ours** — the agent-tools ecosystem only: the CLIs `rig` / `tg` / `draw` / `3d` /
  `task` / `dev` / `pm` / `research` / `harness` / `qa` / `stt` (detected **inside** a shell
  command — a `Bash` call
  running `tg …` is pulled out of the baseline shell count and re-labelled `tg (cli)`),
  plus our skills except `review`. Not the review MCP.
- **review-cycles** — the review CLI inside Bash (re-labelled `review (cli)`),
  `mcp__review__*`, and `skill:review`. The adoption ratio (`ours / (ours+baseline)`)
  excludes this bucket, so review volume does not inflate agent-tools adoption.
- **external-advertised** — the third-party tooling we ship/recommend: MCP servers (serena,
  sverklo, context7, playwright, …) via the `mcp__<server>__<tool>` prefix, plus external
  skills (agent-browser, superpowers, h-*, debate-swarm, …).
- **other** — everything else.

```bash
rig stats show                                  # default: rich terminal UI (tui)
rig stats show --format json                    # canonical machine-readable data
rig stats show --format web                     # self-contained local HTML dashboard
rig stats show --since 2026-06-01 --until 2026-06-15   # window + period comparison
rig stats show --harness claude-code --repo /path/to/repo   # filter by harness / repo
```

**Harnesses parsed:** Claude Code (`~/.claude/projects/<enc>/<session>.jsonl` — the richest
source), Codex (`~/.codex/sessions/.../rollout-*.jsonl`, or
`$RIG_CODEX_HOME/sessions/.../rollout-*.jsonl`), Gemini
(`~/.gemini/tmp/<hash>/chats/session-*.json`), omp
(`~/.omp/agent/sessions/<enc>/**/*.jsonl`, or `$PI_CODING_AGENT_DIR/sessions/...`), and opencode
(`~/.local/share/opencode/storage/`). The supported-harness list is data-driven: each
parser self-registers, and a harness whose logs aren't on the machine is reported as
"not found" rather than failing. Adding a harness is one file in `riglib/stats/sources/`.
Parsed session files are cached per-source under `~/.cache/rig/stats/<harness>.json`,
keyed by each file's `(mtime, size)`: a closed session (the overwhelming majority) is never
re-parsed once cached, so a repeat `rig stats show` only pays for genuinely new/changed
session activity — the cache is purely an optimization and is transparent to every filter
(`--repo`/`--harness`/`--since`); delete the directory any time to force a full rescan.

**Outputs:** `json` is the canonical shape every other renderer draws from; `tui` (default)
is a rich table-and-bar-chart report that degrades to plain text if `rich` isn't installed;
`web` serves a self-contained HTML page (inline SVG charts, no CDN, no JS deps) on a local
port (`--web-port`, default auto). All three break the counts down **by repo** and **by
harness** and render a **daily** trend; the `json` document additionally exposes the
**weekly** series. `--since` yields a before/after period comparison: the selected window
against the equally-long window immediately before it.

**Escape hatches:** the same command also reads the agent-tools hatch audit
(`~/.config/agent-tools/overrides.log` — primary; field `hatch` is the hook id) and
hatch-relevant lines in `~/.config/agent-tools/ship-audit.jsonl` whose `decision`
contains `bypass:` (mapped to `ship-external-review` or `ship-review-quorum`). Events
are grouped by hook and split **broken-gate** (the gate fired wrong — e.g.
`ship-review-quorum` with `models: 0` / quota / `2 of 3`, or `orchestrator-stays-thin`
on a dispatched leaf worker) from **lazy** (the agent reached for a hatch instead of
the sanctioned alternative). The same `ts`+hook in both files counts once (overrides.log
wins); duplicate rows inside overrides.log are not collapsed. Missing files are an
honest zero, never a crash. JSON exposes a first-class `"hatches"` key; tui/plain
always print an `Escape hatches` section (`Escape hatches: 0`
when empty). `--home` and `--since`/`--until` apply the same way they do for tool
adoption. Telegram history is not parsed separately — overrides.log is the tg-ctl
hatch-question audit sink.

## Config — `rig.yaml`

**`rig.yaml` is committed by default.** It is the reproducible source of truth: commit it,
and `rig apply` reproduces the same install on any machine and in any agent session.

The config **cascades by location** (no scope flag):

1. **Global** — `~/.config/rig/config.yaml` (machine-wide defaults you carry across repos).
2. **Per-repo** — `./rig.yaml` (overrides the global layer; committed).

Dicts merge recursively (per-repo wins); lists/scalars replace wholesale. See
[`docs/config-schema.md`](docs/config-schema.md) for every key. A worked example is
[`rig.yaml`](./rig.yaml) at the repo root (this repo dogfoods its own config).

### Global git settings — `git:`

The global config can provision `git config --global` too, so a fresh machine gets your identity
and git defaults from the same file as everything else:

```yaml
git:
  config:
    user.name: Alex Ultra
    user.email: someone@example.com
    pull.rebase: true
    init.defaultBranch: main
```

`rig apply commit` sets each key that is missing or different (bools as `true`/`false`), records the
prior value of anything it overwrote, and never unsets a key you set by hand; `rig status` reports
each drifting key. The block is **global-only** — a repo `rig.yaml` that declares `git:` is rejected,
because keys like `core.sshCommand` run code. Details:
[`docs/config-schema.md#git`](docs/config-schema.md#git).

### `rig profile` — carry your global config between machines

Your global config lives in one file, so it can travel through one **private** git repo:

```bash
fj repo create .rig-profile --private          # once, on the forge (rig does not create repos)
rig profile init https://git.hyperide.ai/<login>/.rig-profile   # machine A: seeds the empty repo
rig profile init https://git.hyperide.ai/<login>/.rig-profile   # machine B: adopts it
rig profile sync                                # after a change on either side
rig profile status --fetch                      # which side changed? (writes nothing)
```

- **Two-way, never a silent merge.** rig compares the local file, the repo's copy, and the snapshot
  of the last sync. Only local changed → commit + push. Only the repo changed → pull, after backing
  the local file up as `config.yaml.rig-bak-<stamp>`. Both changed → refused with both paths printed;
  merge by hand into the local file and `rig profile sync --take local`, or keep the repo's with
  `--take remote`. Both files are validated before they cross, so a broken config is never pushed or
  adopted.
- **The repo must be private.** `env.vars` (`rig env`) often holds API keys and the file is plaintext.
  Every push of a config with `env.vars` warns (naming the keys), and a push is refused when the forge
  reports the repo as public to an anonymous API request.
- **Commits use this machine's git identity** — provision it with the `git:` block above. With none,
  the push fails and names `git.config.user.name` / `user.email`.
- **Every machine with a profile keeps it in sync:** `rig apply commit` syncs before it builds the
  plan; a failure warns and applies the local file, and a pull is flagged as a warning (that plan
  comes from a config you did not preview). A preview never syncs. `profile.auto_sync: false`
  (`rig profile init --no-auto-sync`) turns this off — for every machine, since the setting travels
  in the synced file.
- The checkout is `~/.config/rig/profile/repo/` (rig-owned) and the snapshot `~/.config/rig/profile/state.json`
  (never committed). `init` records `profile.repo` through the same writer as `rig config set --global`,
  which does not keep YAML comments. The agent-tools `protect-main` pre-push hook is switched off in
  the profile checkout only (`git config hooks.skipGlobal protect-main` there — a one-file config repo
  has no PR flow); every other repo keeps it, and the secret scan still runs on profile commits.

### Autonomous mode — global agent operating policy

`mode.name: autonomous` belongs in the global config (`~/.config/rig/config.yaml`). It declares
how an agent should keep working before it asks for help: review/fix iterations until a clean
state, review quorum for decisions, escalation through the configured framework skill, parallel
worktree comparison before escalation, allowlisted development-tool flows, and limit-aware
parallelism caps.

```yaml
mode:
  name: autonomous
  autonomous:
    review_fix: { enabled: true, max_iterations: 5, until: clean }
    decisions:
      review_quorum: { enabled: true, min_iterations: 2, min_models: 3 }
    escalation:
      framework_skill: decision-request-discipline
      require_parallel_worktree_comparison: true
    parallel_worktree_comparison: { enabled: true, candidates: 2 }
    development_tools:
      allow: [Bash(dev:*), Bash(review:*), Bash(task:*)]
    parallelism: { max_agents: 4, max_worktrees: 4, reserve_slots: 1, limit_aware: true }
```

`rig apply --dry-run` surfaces that policy as plan notes, and the development-tool allow rules
flow into the existing additive `permissions.allow` merge for supported harnesses. Raw
development-tool allow rules are currently applied only to Claude Code's verified permission-rule
dialect; unsupported harnesses get a plan note and the rules are skipped. `framework_skill` is a
named behavioral skill for agents to follow during escalation, not a callable interface invoked by
`rig`.

### Auto-mode — provisioned by the reconciler

A `harness:` block tells `rig apply` to write the agent harness's auto/permission setting,
so autonomy is part of the reproducible config — not a manual per-machine toggle:

```yaml
harness:
  enabled: true
  kind: claude-code          # skills-dir: claude-code|codex · native: opencode|omp · instruction-file: pi|commandcode (codex also reads AGENTS.md)
  auto_mode: true            # RECOMMENDED: writes permissions.defaultMode=auto (user scope)
  hook_bridge: { enabled: true }   # wire the agents-hooks/v1 → harness dispatcher (default ON)
```

For **claude-code**, `auto_mode: true` writes `permissions.defaultMode=auto` to the **user**
settings (`~/.claude/settings.json`) — Claude Code honors `auto` only at user scope (it ignores
it in a repo's project settings), so auto-mode is a **per-machine** setting: declare the
`harness:` block in the **global** config (`~/.config/rig/config.yaml`), not per repo. `auto`
(a safety-classifier preview) auto-approves but a classifier blocks anything that escalates
beyond your request, touches unrecognized infrastructure, or looks prompt-injected — strictly
safer than `bypassPermissions` (which skips every check; pin `mode: bypassPermissions` to opt
into full bypass at project scope, e.g. inside a container). `rig apply` merges only that one
key (everything else is preserved), idempotently with a backup on conflict, and `rig status`
flags drift. Defense-in-depth: the agent-hook guards `rig` installs in the same pass
(`block-secrets-write`, `block-no-verify`, `enforce-timeout-on-bash`, `block-raw-process-env`,
`block-raw-pr-merge`, **`block-reset-hard`**) catch dangerous tool calls before the side
effect, complementing the classifier.

**Those guards only fire because of the hook bridge.** Harnesses run hooks declared in their own
native config/plugin surfaces, not the descriptor files `agent_hooks` installs, so a bridge is
required to make the descriptors actually execute (agent-tools#18). The same `harness` block
therefore registers the matching bridge: Claude Code gets `cc_hook_bridge` in `settings.json`,
Codex gets `codex_hook_bridge` in `~/.codex/config.toml` (or
`$RIG_CODEX_HOME/config.toml`), and opencode gets
`opencode_hook_bridge/plugin.js` symlinked into the repo-local
`.opencode/plugins/zz-agent-tools-hook-bridge.js` ordered plugin path. Without that bridge the
guards above would be inert files. Set `hook_bridge: { enabled: false }` to opt out.
If `agent_hooks.target` points at a custom descriptor directory, the bridge remains registered
with that descriptor-dir override; opencode uses a small managed wrapper plugin for this case.
Because that plugin path is machine-local, rig also adds it to the repo's `.git/info/exclude`; when
upgrading from the prior global opencode bridge path, rig removes the old managed global symlink
if it still points at an agent-tools opencode bridge plugin.
See [`docs/config-schema.md`](docs/config-schema.md) for the full `harness` schema and the
per-harness event coverage.

### Model-freshness schedule — a daily cron, provisioned by the reconciler

A `models:` block tells rig to provision a **daily cron that runs the agent-tools
model-freshness checker** (`lib/checker/model_freshness.py`) — which polls provider
model-list endpoints and proposes version bumps to the model board. On **`rig init` AND
`rig apply`**, rig checks whether the schedule is installed and installs it if missing
(idempotent):

```yaml
models:
  enabled: true
  schedule: { time: "12:00" }    # daily at noon (default)
```

Cross-platform: **macOS → launchd** (a `~/Library/LaunchAgents/ai.hyperide.model-freshness.plist`
loaded via `launchctl`), **Linux → crontab** (a sentinel-fenced managed line). `rig status`
reports whether the schedule is installed or drifted; `rig doctor` flags a missing scheduler
binary. See [`docs/config-schema.md`](docs/config-schema.md#models) for the full schema.

### Drift — surfaced both ways, never silently reconciled

`rig status` reports two directions:

- **config→disk** — declared in `rig.yaml` but missing/modified on disk. `rig apply`
  converges these.
- **disk→config** — installed on disk but not declared (orphan / hand-added). These are
  **reported, not deleted** — you decide whether to adopt them into the config or remove
  them.

The status headline is grouped by reconciled area under the GLOBAL machine-wide layer and, when
you are inside a git repository, the REPO layer from `./rig.yaml`. Outside a git repository,
`rig status` ignores any auto-discovered local `rig.yaml`, shows only GLOBAL areas, and prints
that the repo layer / `rig.yaml` is N/A; it does not tell you to commit a repo config where no
repo exists. An explicit `--config` can still declare GLOBAL areas in that mode, but repo-scoped
areas remain N/A until you run status inside a git repository.

## How rig consumes agent-tools (the integration seam)

`rig` never vendors agent-tools content. At runtime it locates an agent-tools checkout —
`agent_tools_source` in config, else `$RIG_AGENT_TOOLS_SOURCE`, else `~/xp/agent-tools` /
`~/work/agent-tools` / `~/agent-tools` — and **scans it live** into a catalog
(`riglib/catalog.py`):

| agent-tools path | becomes |
| --- | --- |
| `skills/universal/<name>/SKILL.md` | a `skills` item (group `universal`) |
| `skills/by-type/<kind>/<name>/SKILL.md` | a `skills` item (group `by-type/<kind>`) |
| `agent-hooks/<name>/<name>.<point>.json` | an `agent_hooks` item |
| `ci/<name>/{workflow.yml,*.sh}` | a `ci` item |
| `git-hooks/global-dispatcher/` | the `git_hooks` dispatcher item |
| `mcp/<name>/` | an `mcp` item |

The catalog drives config validation (unknown item names fail closed), the wizard's
description panes, and the install actions. Update agent-tools, and `rig` picks up new
items on the next scan — no code change in `rig`.

### Universal skills vs. a project's `AGENTS.md`

`rig` is the **universal skill layer**. Cross-project, always-apply MANDATORY skills (for
example `visual-proof-cycle` or `task-completion-selfcheck`) are provisioned by `rig` from
the agent-tools catalog and meant to reach **every project and every user** through the
SessionStart blurb, the rig-installed skills, and each skill's own trigger `description`.
That layer is their single source of truth.

A project's `AGENTS.md` (or a repo-level `CLAUDE.md`) is for **project-specific guidance
only** — how *this* repo builds, its layout, its local conventions. **Never duplicate a
universal mandatory skill into an individual `AGENTS.md`:** it pins a stale copy to one repo,
hides the real source, and goes stale the moment the skill changes. The universal layer is
the one place that carries these mandates — let it, and keep `AGENTS.md` project-specific.

## Architecture

```
riglib/
  cli.py            argparse + subcommand dispatch (lazy imports)
  catalog.py        scan an agent-tools checkout → item registry  ← the integration seam
  config.py         cascade loader + fail-closed schema validation
  detect.py         env/project + OS/package-manager detection
  plan.py           (config + catalog) → ordered InstallPlan       ← shared by init & apply
  schedule.py       pure planning of the model-freshness cron artifact (launchd/crontab)
  drift.py          two-way drift detection
  doctor.py         dependency diagnosis + bootstrap across package managers
  state.py          SetupState ⇄ rig.yaml (the single serializer)
  install.py        install-skill (agent discovery)
  logging.py        opt-in JSONL structured logging (stdlib)
  actions/          stdlib-only install actions (the executor)
    runner.py         run_plan: copy_skill / install_agent_hook / install_dispatcher /
                      install_ci / register_mcp / apply_harness / provision_schedule —
                      idempotent, backup-noted
    fsutil.py         conflict-policy + idempotency + backup helpers
  stats/            tool-adoption analytics (`rig stats show`) — a 3-stage pipeline
    sources/          one pluggable parser per harness (@register); CC / codex / gemini /
                      omp / opencode → a normalized ToolInvocation stream
    taxonomy.py       the data-driven baseline / ours / external-advertised / other rules
    aggregate.py      pure reductions → counts / breakdowns / day+week trend series
    render/           json (canonical) / tui (rich, lazy) / web (http.server + inline SVG)
  tui/app.py        the textual wizard — a thin front-end over the same engine
```

`setup` and `apply` share **one** plan builder and **one** executor; the TUI just wraps
the executor with a progress view. One code path, two front-ends — the wizard can't drift
from `apply`.

## Development

```bash
uv venv && . .venv/bin/activate
uv pip install -e '.[test]'             # core deps (pyyaml, textual, rich) + pytest
python -m pytest -q                     # unit suite
bash tests/smoke.sh                     # end-to-end smoke (needs an agent-tools checkout)
bash tests/smoke.sh --fast              # the seconds-cheap pre-commit subset
scripts/install-smoke-precommit.sh      # wire the fast smoke into .git/hooks (once per clone)
python docs/gen_svgs.py                 # regenerate the diagrams
```

Run `scripts/install-smoke-precommit.sh` once after cloning to gate your commits on the fast
smoke locally — a commit that breaks the real `rig status` flow is then blocked before push,
not just in CI.

## How rig compares

Most setup tools fall into three buckets. **Dotfile managers** (chezmoi, yadm) version a
*person's* config across machines — `~/.gitconfig`, shell rc, secrets. **Scaffolders**
(cookiecutter) stamp a project once from a template and walk away. **Config-as-code**
(Projen, Nix home-manager) regenerate managed files from a typed/declarative source and
keep them in sync.

`rig` is config-as-code, but aimed at a different target: **a repository's agent
guardrails** — skills, agent-hooks, the global git-hook dispatcher, CI gates, and MCP
registrations — sourced live from the [`agent-tools`](https://git.hyperide.ai/ultrabricks/agent-tools)
umbrella. It is **declarative + idempotent** (one `rig.yaml`, re-apply identically on any
machine), it **detects drift in both directions** (config→disk *and* orphan disk→config,
reported not silently overwritten), and it **bootstraps the dependencies** those guards
need across brew/apt/dnf/pacman/zypper.

| Tool | Target | Declarative config | Idempotent re-apply | Bidirectional drift | Agent skills / hooks / CI gates | Dep bootstrap |
|---|---|---|---|---|---|---|
| **rig** | a repo's agent guardrails | ✓ (`rig.yaml`) | ✓ | ✓ (both ways, reported) | ✓ | ✓ (multi-PM) |
| chezmoi | personal dotfiles | ✓ | ✓ | ~ (diff vs source) | — | — |
| yadm | personal dotfiles | ~ (git + alt files) | ✓ | ~ (git status) | — | — |
| cookiecutter | new project from template | — (prompts once) | — (one-shot) | — | — | — |
| Projen | project build/CI config | ✓ (typed JS) | ✓ (synth) | — (overwrites) | — | — |
| Nix home-manager | a user's whole env | ✓ (Nix) | ✓ | ~ (rebuild) | — | ✓ (Nix store) |

`~` = partial. Dotfile managers and home-manager are *per-user*; cookiecutter is *one-shot*;
Projen reconciles build config but overwrites rather than reporting drift and knows nothing
of agent skills/hooks. `rig` is the only one of these whose unit of work is a repo's
agent-facing guardrails — and the only one that surfaces hand-added orphans instead of
clobbering them.

<!-- rig:ecosystem-block -->

## Ecosystem

Part of the [HyperIDE.ai](https://hyperide.ai) agent toolchain. Sibling tools that already solve adjacent problems — check before building your own:

- **[rig-cli](https://git.hyperide.ai/ultrabricks/rig-cli)** — sets up a repo (and a dev machine) from a committed rig.yaml: skills, agent-hooks, git-hook dispatcher, CI gates, MCP, and the agent harness's auto/permission mode
- **[tg-cli](https://git.hyperide.ai/ultrabricks/tg-cli)** — simple Telegram CLI to send messages, photos & files, and a two-way agent bridge (reports, Q -> buttons, voice/rich)
- **[review-cli](https://git.hyperide.ai/ultrabricks/review-cli)** — multi-model read-only code review from one command: diff review, cited quorum, brainstorm, visual review, and interactive spec-review tooling
- **[agent-tools](https://git.hyperide.ai/ultrabricks/agent-tools)** — the shared catalog rig applies: portable agent skills, agent-hooks, the global git-hook dispatcher, CI gates, and MCP servers
- **[draw-cli](https://github.com/alex-mextner/draw-cli)** — text-to-image via Hugging Face
- **[3d-cli](https://github.com/alex-mextner/3d-cli)** — scriptable CLI for the full 3D FDM lifecycle: modeling, mesh repair, slicing, and print monitoring
- **[task-cli](https://git.hyperide.ai/ultrabricks/task-cli)** — enforced ticket-system CLI for agents (GitHub Issues / Linear): acceptance criteria, motivation, and user-impact gates before work starts
- **[dev-cli](https://git.hyperide.ai/ultrabricks/dev-cli)** — project-scoped dev/e2e process runner: start/list/stop dev servers and e2e jobs
- **[hyperide.ai](https://hyperide.ai)** — Figma replacement inside VS Code — edit React components directly through AST/LSP without AI hallucinations, token waste, or context-window limits

_This section is kept in sync by `rig apply` from a canonical registry — edit it in [rig-cli](https://git.hyperide.ai/ultrabricks/rig-cli)'s `riglib/ecosystem_readme.py`, not here; a hand-edit here does not survive the next apply that reconciles this block._

<!-- /rig:ecosystem-block -->

Each CLI registers a skill into your agent harnesses (`<tool> install-skill`) so agents know it exists — see Install.

A machine opts into provisioning these tools with a global `tools:` block (see the config), and `rig apply` runs each tool's own `install.sh`. It also keeps them **fresh**: if a tool's repo ships a `scripts/deploy.sh`, `rig apply` runs it (a safe fast-forward-only `git pull`) on every apply — even when the tool is already installed — so a provisioned checkout doesn't silently drift behind origin. Freshness is opt-in per tool (no `deploy.sh` → skipped) and non-fatal (an offline/dirty/diverged tree is a warning, never an apply error). Both `install.sh` and `deploy.sh` run under the `RIG_TOOL_INSTALL_TIMEOUT_S` budget (default 300s) so a hung script can't wedge apply; raise it for a slow network fetch.

## License

MIT — see [LICENSE](LICENSE).
