# Configuration

Provefab reads `provefab.toml` from its home directory, `~/.provefab` by default. The `PROVEFAB_HOME` variable moves that directory. A commented example sits at the repository root: [`provefab.example.toml`](https://provefab.com/docs/example-config.md).

`provefab doctor` checks configuration, tools and logins. Run it after every change. A successful check does not establish native sandbox compatibility; stages also verify the runtime, profile and policy before launch (see [native execution policy](#repossandbox-native-execution-policy)).

## Writing the file with commands

`provefab init` writes `provefab.toml` when there is none: the `[jev]` section and a catalog with the models of the worker CLIs on your `PATH` (`claude` gives `claude-sonnet` and `claude-opus`, `codex` gives `codex-gpt`). It adds no repository, and never changes an existing file. `--dry-run` prints the file instead. Before starting tasks, remove or replace every `worker = "claude-code"` model block in an existing or init-generated catalog: all Claude stages currently refuse. The example uses Codex for its standard/frontier tiers; add a Pi model from another provider for cross-provider review. Successful `doctor` or login checks do not establish stage support.

`provefab repos add <owner/name>` appends one `[[repos]]` block at the end of the file and keeps the rest as you wrote it: `slug`, `label = "provefab"`, `base` (the repository's default branch) and `gates`, detected from the files at the root of the default branch:

| At the root | `gates` |
|---|---|
| `Cargo.toml` | `cargo fmt -- --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test` |
| `package.json` | the install from the lock file (`pnpm install --frozen-lockfile` with `pnpm-lock.yaml`, `yarn install --frozen-lockfile` with `yarn.lock`, `npm ci` with `package-lock.json`, `npm install --no-package-lock` otherwise, so no lock file is written into the task's changes), then `<pm> run lint`, `<pm> run typecheck`, `<pm> test`, for the scripts that exist; `<pm>` is the same package manager |
| `pyproject.toml`, or `setup.py` or `setup.cfg` with a `tests` directory | `ruff check .` when ruff is configured (`[tool.ruff]` or `ruff.toml`), then `pytest` |
| `go.mod` | `go vet ./...`, `go test ./...` |

It reads these files through `gh` without cloning, or from your clone with `--path <checkout>`: its `origin` must be that GitHub repository, and the files are read from its committed default branch, not from your working tree. A repository with none of these, with several, or with a `package.json` without any of the three scripts (npm's placeholder `test` script does not count) is refused: write its block by hand. `repos add` never writes `local_path`, so Provefab keeps its own clone; set `local_path` by hand to use yours. It never writes a tracker, a risk policy or post-merge checks either: add them by hand. A task's worktree starts without installed dependencies: provision required dependencies in trusted locations before running gates. The native policy denies network downloads during checks; older `repos add` notes suggesting installation inside gates do not grant network access. `--dry-run` prints the block instead of writing it. The commands' exit codes are in [Operations](https://provefab.com/docs/operations.md#exit-codes).

## Secrets

No secret goes in `provefab.toml`. On macOS, Provefab keeps its secrets in the Keychain. On Linux it keeps them in `~/.provefab/credentials.toml`: created with mode 600 (readable and writable by your account only), and refused by `run`, `doctor`, `login` and every command that reads a secret when its mode lets group or others read it (`chmod 600 ~/.provefab/credentials.toml` fixes it). The file is protected by its permissions, not encrypted. Environment variables take precedence on both systems.

| Secret | Where |
|---|---|
| TypeSafe (Jev) key | `TYPESAFE_API_KEY` variable, otherwise `provefab login jev` (macOS Keychain item `provefab-typesafe`, or `typesafe` in `credentials.toml`); on macOS `security add-generic-password -s provefab-typesafe -a provefab -w` also works |
| Claude subscription login | `provefab login claude` (directory `~/.provefab/claude`) |
| Anthropic API key | `provefab login claude --api-key`: stored in the Keychain (`provefab-anthropic`) or as `anthropic` in `credentials.toml`; Claude Code asks Provefab for it through `apiKeyHelper` (directory `~/.provefab/claude-api`). If you move the `provefab` binary, run this login again |
| Codex (ChatGPT) subscription login | `provefab login codex` (directory `~/.provefab/codex`); on Linux with a device code, entered in a browser on another machine |
| OpenAI API key | `provefab login codex --api-key` (directory `~/.provefab/codex-api`) |
| GitHub | `gh auth login` (Provefab uses `gh` and `git` with your permissions) |
| Jira Cloud | `provefab login jira --site <site>`: e-mail and API token in the Keychain (`provefab-jira`) or under `[jira."<site>"]` in `credentials.toml`; or `PROVEFAB_JIRA_EMAIL` and `PROVEFAB_JIRA_TOKEN` |
| Linear | `provefab login linear`: personal API key in the Keychain (`provefab-linear`) or as `linear` in `credentials.toml`; or `PROVEFAB_LINEAR_KEY` |

**Supported logins.** Native execution accepts only the verified authentication modes below. All Claude profiles temporarily refuse before version/auth probes because implicit host context loading has no verified boundary. Select Codex or Pi. Codex workspace/managed plans also refuse before a stage. A successful login does not make an unsupported mode usable. Check your provider's terms for your intended use separately.

## `[jev]`

| Field | Default | Role |
|---|---|---|
| `model` | required | The **full** Jev version, for example `jev-1.13.0`. `jev-latest` is refused, so that a new release never changes routing silently. `jev-1.13` (without the patch number) is rejected by the API; `provefab doctor` reports it. |
| `underspecified_threshold` | `0.7` | Above this probability that an issue is too vague, Provefab asks a question instead of coding. |
| `loop_threshold` | `0.8` | Above this, an agent judged stuck or repetitive is stopped (checked every 15 tool calls). |

Provefab still works without a Jev key: every stage runs on the `standard` tier. There is then no automatic question, no loop detector and no failure triage.

## `[[models]]`: the catalog

| Field | Default | Role |
|---|---|---|
| `id` | required | Unique name, used in logs and PR text. |
| `worker` | required | `claude-code` (the unmodified `claude` binary), `codex` (the unmodified `codex` binary) or `pi`. |
| `model` | required | Model name for that worker: a Claude Code alias (`sonnet`, `opus`, `haiku`), a Codex model (`gpt-5.5`), or a Pi `--model` pattern. |
| `provider` | `""` | Pi only: the `--provider`. |
| `tier` | required | `fast`, `standard` or `frontier`. |
| `max_concurrency` | `1` | How many stages this model may run at once. |
| `auth` | `subscription` | Claude Code and Codex only: `subscription` (your plan's login) or `api_key` (your own API key, set with `provefab login <worker> --api-key`). Pi reads its provider's own credentials and always counts as an API key. |
| `price_id` | none | The price entry to use, as `provider/model` (for example `anthropic/claude-sonnet-5-5`), when the automatic match is wrong. |
| `price_in`, `price_out` | none | USD per million input and output tokens. Set both to override the fetched prices. |
| `price_cache_read`, `price_cache_write` | the input price | USD per million cached tokens read and written, with `price_in` and `price_out`. |
| `quota_weight` | relative price | Subscription models only: how much of your plan one token uses, compared with the cheapest model of the same vendor in the catalog (1.0). It orders that vendor's models only. |

**Subscription or API key, per model.** For a worker that supports both modes, currently Codex, you can switch one line and run `provefab service install`. You can also list the same model twice in the same tier, once per supported sign-in mode: when the subscription hits its usage limit, that entry pauses and the API-key entry takes the next stages. Both still count as the same model family for cross-review. All Claude stages currently refuse native execution. The daily budget (`max_stage_runs_per_day`) caps API spend too.

### How Provefab picks a model

1. **The tier.** Jev rates each issue once, at intake:
   - Implementation takes the tier that matches the difficulty, one tier up when Jev is unsure.
   - Planning takes the implementation tier when Jev rates the planning simple (`plan_depth` below 1.5), and one tier above otherwise. An architectural scope always plans on `frontier`.
   - Review takes the implementation tier when a subtle mistake would cost little (`review_risk` below 1.5), `frontier` when it would cost a lot (3.0 or more), and one tier above otherwise.
   - Without a Jev key, every stage runs on `standard`.
2. **The model inside the tier**, following `[routing] prefer`:
   - subscription models first, then API-key models, the cheapest first;
   - among one vendor's subscriptions, the lowest `quota_weight` first; between vendors (Claude and Codex, say), file order decides, since a weight only compares models of the same vendor;
   - a model with no known price comes after the priced ones (subscription or API key);
   - ties keep file order.
3. **The rules that come first:**
   - a stage never runs below its tier (an empty tier falls back to the nearest configured one, stronger first);
   - review avoids the implementer's model family whenever the catalog allows it, even when that family is cheaper;
   - a paused model (usage limit) is skipped;
   - an implementation that fails again after a retry moves one tier up.

`provefab log <id>` shows the tiers and, for each stage, the model and why it was chosen.

## `[routing]`

| Field | Default | Role |
|---|---|---|
| `prefer` | `subscription` | `subscription`: your plans first (already paid), API keys when they are paused. `api_key`: API keys first. `cheapest`: one list in which subscriptions count as free. |
| `prices_url` | models.dev | Where prices are fetched from, for a mirror. |
| `litellm_url` | LiteLLM's price list | The fallback source, for a mirror. |

**Prices.** Provefab fetches model prices from [models.dev](https://models.dev) at most once a day, with LiteLLM's price list as fallback, and keeps them in `~/.provefab/prices.json`. Offline, it uses that cache, or the prices built into the binary, and tries again an hour later. A price set in `provefab.toml` always wins. Claude Code aliases (`sonnet`, `opus`, `haiku`) match the newest model of that family. `provefab doctor` shows the price each model uses. The request sends no data about you or your code.

**Tip:** list supported models from at least two providers, for example Codex and a Pi model from another provider, so that reviews are cross-checked by a different model family.

## `[[repos]]`: watched repositories

`provefab repos add <owner/name>` writes this block for you (see [Writing the file with commands](#writing-the-file-with-commands)).

| Field | Default | Role |
|---|---|---|
| `slug` | required | `owner/name` on GitHub, where the code and the pull requests are. |
| `local_path` | none | Your local clone. **Optional**: without it, Provefab clones the repository into `~/.provefab/repos/<owner>/<name>`. Either way it runs `git fetch` before every pass and starts from `origin/<base>`. |
| `label` | `provefab` | The label that triggers Provefab. Only people who can triage the repository can set it: it is the authorization. The derived labels (`<label>:in-pr`, `:needs-info`, `:failed`, `:merged`) are created at startup on GitHub and Linear. The risk labels (`<label>:risk-<category>`, including `:risk-unknown`) are created at startup too. Jira labels are free text and need no creation. |
| `base` | `main` | Branch work starts from, and pull requests target. |
| `poll_interval` | `3m` | How often the repository is polled. |
| `trust_pi_project` | `false` | Pi only: load the repository's Pi config (avoid for a repository you do not control). |
| `gates` | required, at least one | Commands run in the worktree after every implementation. All must pass. They decide, not the agents. |
| `post_merge_checks` | `[]` | Optional commands run against the exact merged base commit after a Provefab-created PR merges. On failure, Provefab validates a revert against the current base and opens a human-reviewed revert PR only if the reverted tree passes. It never merges the revert automatically. Enabling it later does not check old merges. Command strings may appear in GitHub comments: never put secrets on the command line, use the environment. |

Provefab Pro reads an optional `[repos.merge]` table for guarded auto-merge: `auto` (off by default), `max_lines` (400), `require_test_change` (on: only a change that adds or changes tests merges by itself), `exclude` (paths kept for a human, `.github/**` by default) `allow_public` (off: a public repository is never merged automatically, since anyone can write its issues) and `allow_risk` (risk categories that may still auto-merge; `unknown` never can). This binary ignores it, says so in `provefab doctor` and in each PR comment, and never merges. The old keys `auto_merge` and `auto_merge_max_lines` are refused with a message naming `[repos.merge]`.

For gates that work well:
- Put in what your CI requires: format, lint, tests.
- Provision dependencies before the task and use offline commands, such as `cargo test --offline` or a test script with its dependencies already available. Detected install commands are suggestions, not permission for downloads. Do not use shared writable caches to work around refusal.
- Declare validation scripts, test configuration, fixtures, lockfiles and supporting dependencies in `protected_paths`. This may prevent a task from changing those tests; choose the protected validation harness deliberately.
- Fast commands speed up every loop.
- If the repository has slow tests, keep a representative subset and let your GitHub CI do the rest.
- **Never share a build directory between tasks** (for example one `CARGO_TARGET_DIR` for every worktree). Cargo can then reuse binaries built from another task's code, and the checks pass or fail on the wrong code. Each worktree builds in its own `target/`, which the agent already warmed during implementation. To go faster safely, use a content-addressed cache such as `sccache` (`RUSTC_WRAPPER=sccache`).

## `[repos.sandbox]`: native execution policy

Every stage and gate receives this repository's policy, including reproduction, risk checks, post-merge checks and revert verification. Unknown keys in this table are errors.

| Field | Default | Role |
|---|---|---|
| `runtime` | `"srt"` | SRT executable for gates and Pi Bash. Provision it outside the worktree; an absolute path or trusted PATH lookup is accepted. Tested version: 0.0.79, minimum 0.0.79. Missing or unsupported runtime fails before execution. |
| `allow_local_network` | `false` | Gate-only request. `true` currently refuses on every platform because loopback-only binding is unverified. SRT can allow listening on non-loopback interfaces; no wider permission is inferred. Workers always keep strict network denial. |
| `protected_paths` | `[]` | Existing literal paths, relative to the validation worktree or absolute externally. These paths cannot be written by native commands or Codex patches. Pi direct file tools are disabled; file operations use sandboxed Bash. Claude stages refuse. Missing paths, wildcards, `..`, mutable symlink aliases and declarations encompassing the worktree root are refused. |
| `deny_read` | `[]` | Additional literal absolute paths that commands and direct read/search tools must not read. Existing ancestors are resolved even for absent secret leaves. |

```toml
[repos.sandbox]
runtime = "/opt/provefab-runtime/srt"
protected_paths = ["tests", "scripts/check.sh", "Cargo.lock", "/opt/validators/project"]
deny_read = ["/srv/private/service-token"]
```

SRT must be separately provisioned in a trusted directory using the [sandbox-runtime installation instructions](https://github.com/anthropics/sandbox-runtime). Its dependencies must also remain outside writable roots. Network is denied during checks, including package downloads and local listeners; provision dependencies before a run. The core gates and real Codex commands have been exercised on macOS 26.6.2 (Apple Silicon) with Codex 0.162.1 and SRT 0.0.79. Pi 1.0.3's installed Bash tool factory was exercised through the actual extension and SRT without a Pi model call. Linux enforcement is unverified; unavailable enforcement fails closed. Keep `PROVEFAB_HOME` short on macOS: SRT's private temporary socket path can exceed the OS limit with long home or session paths and refuse execution.

An empty `protected_paths` list provides no immutable-validation guarantee. Declare every script, test configuration, lockfile and dependency defining success, or keep them outside writable roots. Protecting one entrypoint does not protect its dependency closure. Command and interpreter resolution also matters: PATH entries and aliases through writable roots are excluded. Trusted plugin, guard and authentication inputs inspect every expanded component before canonicalization, including the nearest existing ancestor of absent secret leaves. Stable external aliases remain usable. A protected file missing from a review or reverted tree causes a validation error. NoTools maintenance resolves relative declarations against the real repository while its model runs in a separate empty directory.

All Claude stages temporarily refuse, including clean personal profiles and NoTools, before any host version/auth probe or stage preparation. The implicit host context boundary is unverified: user memory and rules can load outside the command sandbox. Select Codex or Pi in the model catalog. Do not delete user authentication or state to make a run proceed. Support can reopen only after exact-source controls compatible with authentication and Provefab hooks, and a successful supported-stage run, are verified. Codex requires exactly 0.162.1, a dedicated untrusted project profile and only Provefab's guard hook among effective enabled/trusted hooks. The dedicated config supports basic model/auth fields, projects, hooks and notices only; nonempty mcp_servers, notify, custom providers, profiles and unsupported keys refuse before host startup. Native plugins and external tool routes are disabled by CLI overrides. Pi exposes only sandboxed Bash for file operations, plus structured answers when needed; its direct file/search tools are disabled because pre-operation guards cannot prevent symlink races. Read-only profiles keep no worktree write permission and PR reviews keep their read-only shell allowlist. Codex image viewing is disabled pending boundary verification. See [Security](https://provefab.com/docs/security.md).

Earlier validation observed Claude 2.1.299 refusing `~/.claude/state`. That historical host-state result did not establish safety for clean profiles. The current refusal applies universally, independently of installed version, authentication or host state.

Codex authentication must use `cli_auth_credentials_store = "file"`, which Provefab also pins at launch. Supported subscription plan claims are `free`, `go`, `plus`, `pro`, `prolite` and `promax`; file API-key logins are supported too. Workspace/managed, unknown or malformed authentication and keyring/auto/ephemeral stores refuse before startup. Provefab also refuses any `/etc/codex/config.toml`, `/etc/codex/managed_config.toml`, `/etc/codex/requirements.toml`, dedicated-profile `cloud-config-bundle-cache.json` or forced `com.openai.codex` managed config/requirements preference. Even empty or broken policy files refuse. Keep account membership and host policy stable during a run; preflight cannot seal later administrator or account changes. No source or login data is removed automatically. Newer Codex configuration loaders require review before support.

Codex and Pi use explicit trusted stage prompts. Pi disables implicit AGENTS/CLAUDE and system-prompt file discovery, retaining the explicit stage file. Codex disables project documents, bundled skills and cloud skills, and fixes project skill discovery to the checked working directory. Dedicated-profile `AGENTS.md`/`AGENTS.override.md`, `HOME/.agents/skills`, `/etc/codex/skills`, and working-directory `.codex/skills`/`.agents/skills` refuse before host startup, including broken links. The dedicated `skills` directory may be absent, empty, or contain only a real non-symlink `.system` directory; Provefab does not inspect its contents. Custom or aliased caches refuse. Relevant roots and resolved aliases remain protected from worker writes even when absent, so a running worker cannot introduce an automatic skill source. Unrelated ancestor `HOME/.codex/skills` is not a source when `CODEX_HOME` differs. App-server starts in the same checked directory. Custom implicit context remains unsupported until its host reads have a verified enforcement boundary.

## `[repos.risk]`: risk-aware policy

Provefab classifies each round's changed files by path into risk categories. A risky change gets stricter handling: extra checks, a frontier reviewer from another provider when you have one, a visible Risk section in the PR and a label on the issue. The policy raises scrutiny; it does not prove a change is harmless. It works with no configuration, and `[repos.risk]` is optional. Path matching only: file contents are not read.

**Built-in categories** (a path matching any pattern puts the change in the category):

| Category | Patterns |
|---|---|
| `ci` | `.github/**`, `.gitlab-ci.yml`, `.circleci/**` |
| `dependencies` | `**/Cargo.toml`, `**/Cargo.lock`, `**/package.json`, `**/*lock*.json`, `**/pnpm-lock.yaml`, `**/yarn.lock`, `**/go.mod`, `**/go.sum`, `**/requirements*.txt`, `**/pyproject.toml`, `**/Gemfile*` |
| `migrations` | `**/migrations/**`, `**/*.sql` |
| `infrastructure` | `**/Dockerfile*`, `**/*.tf`, `k8s/**`, `helm/**`, `**/docker-compose*.yml` |
| `secrets-config` | `**/.env*`, `**/*.pem`, `**/*.key`, `**/*secret*` |
| `rules` | `.provefab/rules.md` (see [Repository rules](https://provefab.com/docs/rules.md)) |

Provefab never commits `.provefab/` in a task's pull request, so the `rules` category marks a change to the rules made by other means; disable it like any built-in.

**Patterns.** `/`-separated, anchored at the repository root, case-sensitive. `**` matches any number of segments (including none), `*` any run of characters inside one segment, everything else literally. `.gitlab-ci.yml` matches only the root file; write `**/<name>` for any directory. For a rename, both the old and the new path count.

| Field | Default | Role |
|---|---|---|
| `disable` | `[]` | Built-in categories to turn off, by name. |
| `[repos.risk.categories.<name>]` | none | Adds a category, or extends a built-in of the same name. |
| `paths` | required for a new category | Patterns of the category. For a built-in, they are added to its patterns. |
| `checks` | `[]` | Commands run in the worktree, after your `gates`, when the category is detected. For a built-in, they replace its checks. A command already in `gates` runs once. Same timeout and rerun rules as gates; a failing check is an ordinary gate failure: the round goes back to implementation and counts toward the same attempt limit as other gates. |
| `reviewer_tier` | `frontier` | `frontier` or `standard`: the tier of the review when the category is detected. `frontier` applies only when a frontier model from another provider than the implementer's is configured; otherwise the usual reviewer from another provider stays (see [Usage](https://provefab.com/docs/usage.md#risk-aware-changes)). |

```toml
[[repos]]
slug = "acme/api"
gates = ["cargo test"]

[repos.risk]
disable = ["secrets-config"]

[repos.risk.categories.auth]
paths = ["src/auth/**", "src/billing/**"]
checks = ["cargo test --test auth"]

[repos.risk.categories.migrations]
paths = ["db/schema/**"]
checks = ["./scripts/check-migration.sh"]
```

Category names use lowercase letters, digits and `-`. `unknown` and `none` are reserved: Provefab uses `unknown` when the changed files cannot be computed, and Provefab Pro's calibration report uses `none` for changes with no risk category.

**Validation errors** (an invalid `[repos.risk]` is refused when the configuration loads, so Provefab does not start; `provefab doctor` and `provefab run` report `<slug>: [repos.risk]: <message>`):

- `unknown category in disable: <name>`: `disable` names something that is not built-in.
- `invalid category name: <name>`: empty, `unknown`, `none`, or not lowercase letters, digits and `-`.
- `<name>: empty path`, `<name>: empty check`: a blank entry.
- `<name>: path "<p>" must be relative, without "./", a trailing "/" or empty segments`: a pattern that can never match a changed path (`/src/**`, `./src/**`, `src/`, `src//auth`).
- `<name>: no paths`: a new category without `paths`.
- `<name>: reviewer_tier must be standard or frontier`.
- `<name> is disabled`: the same built-in is in `disable` and in `categories`.
- An unknown key under `[repos.risk]` or a category table is refused too.

`provefab doctor` prints one `risk <slug>` line per repository: the category count and names, and how many checks were added. It ends with `; warning: risky changes implemented on <providers> keep a standard reviewer (no frontier model on another provider)` when, for one or more providers in your catalog, no frontier model is on another provider. Providers are listed in catalog order and comma-separated (for example `claude-code`, `codex`, `pi:<provider>`). See [Usage](https://provefab.com/docs/usage.md#risk-aware-changes) for what happens to a risky change.

## `[repos.tracker]`: Jira or Linear issues

Absent, the repository's issues are its GitHub issues. Setup, labels and limits: [Jira and Linear](https://provefab.com/docs/trackers.md).

| Field | Default | Role |
|---|---|---|
| `kind` | `github` | `github`, `jira` or `linear`. |
| `site` | none | Jira only, required: the site's host name, such as `acme.atlassian.net`: dot-separated labels (at least two) of letters, digits and hyphens, not an IP address (no `https://`, no path). |
| `project` | none | Jira and Linear, required: the project or team key, `[A-Z][A-Z0-9_]*`, as in `ENG-123`. |

Refused at load: an unknown `kind` or key (credentials never go in this file), `site` outside Jira, `project` with `github`, and, for Jira, a `label` containing whitespace.

Switch a repository's `kind` (or its `project`) only when the repository has no task history that could collide: `provefab run` and `provefab add` refuse to start while a task in progress (including one whose pull request is still watched), or an update still to send, belongs to the previous tracker. A new ticket whose number matches an earlier task of the repository is skipped with a log line. See [Jira and Linear](https://provefab.com/docs/trackers.md#limits).

## `[limits]`

| Field | Default | Role |
|---|---|---|
| `stage_timeout` | `30m` | Longest an agent stage (plan, implement, review) may run. |
| `gate_timeout` | `20m` | Longest one check command may run. |
| `max_turns` | `plan 40, implement 150, review 40` | Turn limit per stage. |
| `review_rounds` | `2` | Correction rounds the review may ask for before a new pass. |
| `max_auto_passes` | `3` | Automatic new passes per issue before Provefab asks you. |
| `max_stage_runs_per_day` | `60` | Worker runs over a rolling 24 hours, across all repositories. |
| `max_drive_steps` | `200` | Most steps one task takes in one go (a safety net). |
| `retry_delays` | `["5m", "15m", "45m"]` | Waits after a transient failure (network, GitHub). After the last one, Provefab asks you. |

## Changing the configuration

- **Service running:** edit the file, then run `provefab service install --workers N`. It reloads the service and takes the current `PATH`.
- **New tool installed** (`claude`, `codex`, `cargo`...): same command, so the service finds it.
- **New repository:** `provefab repos add <owner/name>`, or add a `[[repos]]` block by hand; then run `provefab doctor`, then `provefab run --dry-run` to see how its issues would be handled.
