# PR bot

A GitHub App that reviews each pull request with one short comment, a Repowise review check on every push, optional pass/fail checks and threads on the lines that need them. Signals mode is free for OSS.

The **Repowise PR bot** is a GitHub App that runs the same intelligence layers
as the CLI and dashboard against every pull request. It posts **one short
comment**, with a [**checks section**](https://docs.repowise.dev/hosted/bot#checks) when coverage, security or docs
have something to say, optional **pass/fail checks** a branch rule can require,
and **review threads** on the lines that need them. Every push gets a
[**Repowise review check**](https://docs.repowise.dev/hosted/bot#the-repowise-review-check) with the commit it
reviewed and its finding counts. In **Signals**, the default mode, the comment
is deterministic and posts only when there is something worth saying: no model,
no per-PR tokens, and **free forever for public / OSS repos**. **AI review**
adds a model on every pull request that changes code and is part of Pro, billed
from credits. Private repos need the Pro plan too. See
[review modes](https://docs.repowise.dev/hosted/bot#review-modes).

> **Note**
>
> Install it from the GitHub App page:
> [**github.com/apps/repowise-bot**](https://github.com/apps/repowise-bot/installations/new).
> Pick individual repos or a whole org. There is also a
> [marketing overview](https://repowise.dev/bot) and you can manage installs from
> the [dashboard](https://www.repowise.dev/settings/bot).

## Install

1. **Install the GitHub App.** Open
   [github.com/apps/repowise-bot](https://github.com/apps/repowise-bot/installations/new),
   choose the repos (or the whole org), and approve.

2. **Connect the install to your account.** GitHub sends you back to
   `repowise.dev/bot/installed`, which shows a "Finishing setup" state while it
   waits for GitHub's webhook to register the installation. It polls for that on
   its own, up to eight times at 1.5-second intervals, and tells you if
   registration is taking longer than expected. Two cases from there:

   - **Personal account install.** Claimed automatically as soon as you are
     signed in. Nothing to click. If the claim fails the page says so and offers
     a retry button.
   - **Organization install.** An organization admin has to confirm it. The page
     shows a "Confirm organization access" button, which needs a GitHub sign-in
     fresh enough to carry a provider token; without one the button is disabled
     and the page asks you to sign in with GitHub again.

   If you are not signed in at all, comments still appear on pull requests. You
   just cannot manage the install or claim the indexed repos until you sign in.

3. **Indexing starts.** Once the install is connected, the default branch of each
   covered repo is indexed through the same pipeline the hosted dashboard uses,
   typically 3 to 10 minutes per repo. Until a repo's first snapshot exists the
   page lists it as `indexing`.

4. **Open a pull request.** On `opened` / `synchronize` / `reopened`, the bot
   re-parses the changed files, computes the health delta between base and head,
   and decides whether to comment.

5. **Read the report.** The **View on Repowise** button on each comment opens
   the pull request's page on `repowise.dev` (`/pr/{owner}/{repo}/{number}`),
   with the full analysis, findings and tests to run. Pages for public repos need
   no sign-in. Pages for private repos open for signed-in users with access to
   the repo.

**Permissions requested:** Contents: Read · Pull requests: Write · Metadata:
Read · Issues: Write (GitHub treats PR comments as issue comments) · Checks:
Write, for the [Repowise review check](https://docs.repowise.dev/hosted/bot#the-repowise-review-check), the
[merge gate](https://docs.repowise.dev/hosted/bot#the-merge-gate), [pass/fail checks](https://docs.repowise.dev/hosted/bot#passfail-checks) and
[annotations on lines](https://docs.repowise.dev/hosted/bot#findings-on-lines). Grant Checks: Write: without it
the bot still comments, but a clean pull request gets no comment, so nothing
on it shows the review ran. The one-click [coverage setup](https://docs.repowise.dev/hosted/bot#coverage-setup) needs Contents: Read
and write and Workflows: Read and write.

## Review modes

The [org policy](https://docs.repowise.dev/hosted/bot#org-policy-and-repo-overrides) picks the mode for every repo
in an installation, and a repo can override it (Settings → PR bot). The mode is
set only on the dashboard: the bot ignores `review_mode` in
`.repowise/bot.yaml`, so a pull request from a fork cannot turn on a paid pass.

| Mode | What it does | Plan | Cost |
|---|---|---|---|
| **Signals** | Comments when a contract breaks, a finding lands, or tests or companion files are missing. No model runs. | Free on public repos | None |
| **AI review** | Reviews every pull request that changes code, on every push. Docs-only and other non-code pull requests get Signals. | Pro | About 9 cents a pull request on average, every push included |

### How AI review works

- **Quick pass on every push.** A model reads the diff with the evidence
  Signals already computed: callers, co-change partners, findings, tests.
- **Fix pushes are incremental.** A later push is read for what changed since
  the last reviewed commit. The review remembers its earlier points, so a point
  is never raised twice, and a point whose lines a push fixed is resolved on
  its thread.
- **A verifier on risky claims.** A claim that a caller breaks, a failure is
  silently swallowed, or a high-priority logic error is checked against the
  code before it posts.
- **A deep review when the change warrants it.** On a risky pull request,
  where change risk, blast radius or hotspot history call for it, a deeper
  review reads the code around the diff. It runs automatically once per pull
  request, and on demand with depth `deep` or `/repowise review deep`.
- **Tests in one thread.** Changed code that no test reaches is listed once,
  in a single "Tests for this change" thread, edited in place as pushes land.
- **Drafts.** A draft pull request gets the quick pass. The deep review is
  considered when it is marked ready for review.
- **On demand.** Comment `/repowise review deep` to run the deep review on the
  current commit.

**Depth** (`review_depth`) sets how far AI review goes:

| Depth | Runs |
|---|---|
| `light` | The quick pass and the verifier. No deep review. |
| `auto` | The default. Adds the deep review when the change warrants it. |
| `deep` | The deep review on every push. |

AI review draws on the same credit balance as chat, docs and MCP, with no
per-seat review fee. Pro includes $5 of credits a month, about 55 reviewed
pull requests, and 100 pull requests cost about $9. Two caps bound the spend:
each pull request has a cap (`ai_pr_cap_cents`, 60 cents by default) and each
repository a monthly AI spend limit ($5 by default). When the plan does not
include AI review, the balance is empty, or the monthly limit is reached, the
bot posts only what Signals would, and the comment and the review check note
the pause. When a pull request's own cap is spent, later pushes get Signals and
the review check says the pull request's AI budget is spent.

## When the bot comments

In **Signals** the bot **stays silent unless there is a measurable signal**
worth interrupting a reviewer for. A pull request with no findings gets no
comment at all. In **AI review** it also comments when the review has a point
to raise. A clean quick pass posts no comment, and a clean deep review posts a
short note that there is nothing to fix. Either way the [Repowise review check](https://docs.repowise.dev/hosted/bot#the-repowise-review-check)
reports every push, so a clean result is visible without a comment. When the
bot does comment, improvements are shown alongside problems so cleanup gets
credit. Signals posts when any of these fire:

- net repo health degrades, or any modified file's score drops;
- a hotspot file (high churn + many dependents) is touched;
- a top co-change partner of a modified file is missing from the PR;
- new dead code is introduced (or removed, which is a win);
- a modified file is on a declining-health trajectory;
- a changed file carries substantial recent bug-fix history;
- the diff's change-risk score lands in this repo's high band.

The bot **edits its existing comment** when new commits land instead of posting
a wall of duplicates. If nothing is left to say after an earlier comment, the
stale comment is deleted.

## What's in the comment

The comment is short and leads with what the reviewer should do. Each part
renders only when it has something to say, and the full analysis is one click
away: the **View on Repowise** button opens the pull request's page
(`/pr/{owner}/{repo}/{number}`), and each review thread has a **Details** link
to its point on that page.

| Part | What it shows |
|---|---|
| **Verdict and review actions** | The evidence that made the bot post, as actions: a broken contract, a finding this PR introduced, a missing companion file, the tests to run. At most three. |
| **Health line** | The repo score and how this PR moved it (`show_health`). |
| **Gate line** | The merge gate's verdict, when the [gate](https://docs.repowise.dev/hosted/bot#the-merge-gate) is on. |
| **Coverage** | One verdict line: patch coverage of the changed lines, the project delta, and the threshold when one is set. The uncovered changed lines are folded under it, per file, each range linked to its lines. Measured, and only when CI uploads a report (see [coverage setup](https://docs.repowise.dev/hosted/bot#coverage-setup)). Nothing renders while CI is still running. |
| **Security** | New findings on the lines the pull request adds, at or above `security_shows_at`. Secrets are masked. |
| **Tests** | Only when there is no coverage report and none is coming: the changed files no test reaches through the call or import graph. Inferred, so it never shows a percentage. |
| **Doc drift** | Opt-in (`doc_drift`). Refuted claims in docs the pull request edits. Documents that only name a changed file stay on the PR page and in the `Repowise / doc drift` check run. |
| **Evidence** | A folded table of what is known and unknown about the change: owners, components, provenance with its coverage. Standard detail only. |
| **Reviewers** | Suggested reviewers (`show_ownership`). Standard detail only. |

Coverage, security, tests and doc drift form the comment's **checks section**
(see [checks](https://docs.repowise.dev/hosted/bot#checks)). In Signals nothing in the comment is written by a
model, so every comment is reproducible. AI review writes its part with a
model, and the pull request page names the model that wrote it.

## Findings on lines

`on_lines` decides what lands on the changed lines themselves:

| Value | What it posts |
|---|---|
| `off` | Nothing on the lines; the findings stay in the comment. |
| `annotations` | Check Run annotations in **Files changed**: high and critical health findings on lines this PR added, and changed signatures that code outside the PR still calls. With the merge gate off they ride on a `neutral` check posted only when there is something to mark. |
| `threads` | The default. The strongest findings as review threads you can reply to: leaked keys, breaking signature changes with outside callers, and AI review points. |
| `both` | Annotations and threads. |

AI review points on code this PR wrote are capped by the review
sensitivity.

## The Repowise review check

Every push gets a GitHub check run named `Repowise review`. It is on by
default, and it tells people and coding agents whether the latest commit has
been reviewed and what the review found:

| State | Conclusion | Title |
|---|---|---|
| Review running | `in_progress` | `Reviewing <short sha>` |
| Waiting for the repo's first index | `in_progress` | `Waiting for the first index` |
| Clean | `success` | `0 findings`, with the depth that ran |
| Findings | `neutral` | The open findings by priority, with the depth that ran, for example `3 findings: 1 P1, 2 P2` |
| Pull request's AI budget spent | `neutral` | The findings count, with a note that this pull request's AI budget is spent |
| Skipped | `neutral` | `Not reviewed: <reason>`, for a `[skip repowise]` title, a bot author, the bot turned off, a plan that does not cover the repo, a pull request too large, or no files to review |
| Superseded by a newer push | `neutral` | Superseded, with the newer commit |
| Did not finish | `neutral` | Comment `/repowise recheck` to run it again |

The title ends with the depth that ran, shown as a tier label: `(T1)` for the
quick pass, up to `(T3)` for the deep review. A run paused for credits or the
plan is a normal clean or findings run with a note line in its summary. The
summary names the reviewed commit and the base it was compared against.
The check concludes `failure` only when the [merge gate](https://docs.repowise.dev/hosted/bot#the-merge-gate) is on
and blocking. Keep it out of required checks unless the gate is on.

**For coding agents.** The latest commit is reviewed when the
`Repowise review` check on the head SHA is `completed`. Read the check's
finding counts instead of comparing comment timestamps. When the bot posts a
comment, its hidden `repowise-bot` marker also carries the reviewed head SHA.
A clean pull request gets no comment, so on an install without Checks: Write
there is nothing to read: grant it.

To turn the check off for a repo, set `review_check: false`.

## Commands

Comment on a pull request:

| Command | Does |
|---|---|
| `/repowise recheck` | Runs the review again on the current commit. |
| `/repowise review deep` | Runs the AI deep review on the current commit (AI review only). |
| `/repowise remember [path:<glob>] <text>` | Saves a [review preference](https://docs.repowise.dev/hosted/review-preferences) the bot reads on later reviews. Works in review threads too. |
| `/repowise confirm` | In a review thread: accepts the decision saved from it. Needs write access. |
| `/repowise forget` | In a review thread: undoes what was saved from it. Needs write access. |
| `/repowise help` | Lists the commands. |

Put `[skip repowise]` in a pull request title to skip it.

## Teach the review

Reply on one of the review's findings to tell it what your team does, for
example "we validate this at the API boundary, not here". The review saves
the reply as a decision and applies it to later reviews, and answers on the
thread with what it saved and a link to it.

- **Write access accepts it.** A reply from someone with write, maintain or
  admin access on the repository is accepted right away: "Saved as a decision
  (accepted: octocat has write access)". Anyone else's reply is saved for
  someone to accept.
- **Accept from the thread.** Reply `/repowise confirm` on the thread to accept
  a saved decision, or confirm it on the repository's Decisions page.
- **Undo.** Reply `/repowise forget` on the thread. The decision stops shaping
  reviews, and a finding the reply had quieted can be raised again.

The **Learn from replies** setting, in a repository's bot settings or the org
policy, picks what a reply becomes:

| Option | Does |
|---|---|
| Accept automatically when the replier has write access | The default. Replies from people with write access are accepted, others are saved for someone to accept. |
| Save for someone to accept | Every reply is saved, and nothing shapes reviews until someone accepts it. |
| Don't learn from replies | Replies are not saved. |

What the review learned on a repository is listed under **What it learned from
your team** in that repository's bot settings. Teammates who did not install
the bot can see it there; only the person who installed the bot can change the
setting.

## The merge gate

Beyond commenting, the bot can post a **GitHub Check Run** named
`Repowise / code health` that a branch protection rule can require, turning
code health into a merge gate. It is **off by default** (`health_gate.mode`):

- **`off`**: no check is created.
- **`advisory`**: always concludes `neutral`. The verdict is visible on the PR
  but never blocks. The safe default to start with.
- **`blocking`**: concludes `failure` when a rule trips, which a required check
  will block the merge on.

The rules (evaluated only in `blocking` mode):

| Rule | Blocks when |
|---|---|
| `repo_drop_block` | The repo health score drops by more than this many points (default `0.3`). |
| `min_new_file_score` | A changed file scores below this floor, out of 10. |
| `block_on_introduced` | The PR introduces a finding in its added lines. |
| `block_at` | With `block_on_introduced`, the lowest severity that fails: `critical`, `high`, `medium` or `low`. Unset means any severity. |

Or start from a **named profile** and nudge single knobs: `health_gate.profile`
accepts `warn-only`, `block-new-file-floor` or `block-any-decline`. The
profile seeds the rules; any explicit key set alongside it wins.

**`not_evaluated`** decides what every Repowise check run concludes when it
could not evaluate, for example no health score yet or no coverage report:
`neutral` (the default) passes a required check, `failure` holds the merge
until it can evaluate.

> **Note**
>
> Because the bot already re-scores the repo at both the base and head commits,
> the gate needs no extra index; it reads the
> same per-PR analysis the comment is built from.

## Checks

The comment's checks section carries what the tests, the security scan and the
docs say about the change (see [what's in the comment](https://docs.repowise.dev/hosted/bot#whats-in-the-comment)).
`checks_comment` decides when it renders:

- **`on_signal`** (the default): only when a check has something to say. Once
  shown it is always updated, to an all-clear line when the findings are gone,
  so a stale finding never lingers.
- **`always`**: on every pull request.
- **`off`**: never.

### Pass/fail checks

Each check can also post its own GitHub check run, which a branch protection
rule can require. `check_runs` lists the ones that run. `review` (the
[Repowise review check](https://docs.repowise.dev/hosted/bot#the-repowise-review-check)) is on by default, the
rest are off, and all work with or without the checks section:

| `check_runs` entry | Check run | Fails when |
|---|---|---|
| `review` | `Repowise review` | Only with a blocking [merge gate](https://docs.repowise.dev/hosted/bot#the-merge-gate). Otherwise `success` when clean and `neutral` with findings. |
| `coverage` | `Repowise / coverage` | Patch coverage is below `coverage_fail_under` (a percent, 0 to 100). With no minimum it reports the figure and concludes `neutral`. |
| `security` | `Repowise / security` | The pull request adds a finding at or above `security_fail_on` (`high` by default). |
| `doc_drift` | `Repowise / doc drift` | A doc the pull request edits carries a claim the code refutes, at high confidence. |

A check that could not evaluate concludes as `not_evaluated` says.

### Coverage setup

Measured coverage needs a coverage report from your CI. In the dashboard
(Settings → PR bot → a repo's settings → Checks), **Set up coverage** has
the bot open a **draft pull request** that adds
`.github/workflows/repowise-coverage.yml`: it runs your tests with coverage and
uploads the report with the step below. The bot detects the stack (Python,
Node, Go, Java with Maven or Gradle) and the pull request says how to adjust
the test command. If your
tests already run in another workflow, it explains how to move the upload step
there so they run once. Review it, mark it ready and merge it; the next pull
request carries a coverage line.

The one-click setup needs two extra repository permissions on the PR bot:
**Contents: Read and write** and **Workflows: Read and write**. Existing
installs see "Approve the new permission" in the dashboard, which links to the
installation's settings on GitHub. Everything else (the checks section, check
runs, uploads) works without them.

Or add it yourself. The dashboard shows the generated workflow, and the upload
step alone, to copy. Add the step to the job that runs your tests, after the
step that writes the coverage report, and give the job the `id-token: write`
permission:

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write   # lets the upload prove it comes from this repository
    steps:
      # ... check out, install, and run your tests with coverage ...
      - name: Upload coverage to Repowise
        if: always()
        continue-on-error: true
        env:
          REPORTS: "coverage.xml"   # space-separated paths or globs
          HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
        run: |
          shopt -s globstar nullglob
          files=()
          for pattern in $REPORTS; do for f in $pattern; do [ -f "$f" ] && files+=(-F "report=@$f"); done; done
          if [ ${#files[@]} -eq 0 ]; then echo "::warning::Repowise: no coverage report found at: $REPORTS"; exit 0; fi
          auth=()
          if [ -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
            token=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=repowise" | jq -r '.value // empty' || true)
            [ -n "$token" ] && auth=(-H "Authorization: Bearer $token")
          fi
          curl -sS --fail-with-body --max-time 60 "${auth[@]}" "${files[@]}" \
            -F "repository=$GITHUB_REPOSITORY" -F "head_sha=$HEAD_SHA" -F "pr_number=$PR_NUMBER" \
            -F "event=$GITHUB_EVENT_NAME" -F "run_url=$RUN_URL" -F "workspace=$GITHUB_WORKSPACE" \
            https://api.repowise.dev/ci/coverage \
            || echo "::warning::Repowise: coverage upload failed; see the message above."
```

It is plain `curl` and `jq`, so it needs no extra action. It never fails the
job: a missing report or a failed upload is a warning in the log, and the
server's answer (including how many report paths did not match a file in the
repository) is printed there too.

**`REPORTS`** is a space-separated list of report paths or globs (`**` works),
relative to the repository root. Supported formats: LCOV, Cobertura XML,
JaCoCo XML, Clover XML, Go coverprofile, and the Repowise coverage JSON. The
format is detected from each file, and absolute paths in a report are made
relative to the checkout. Typical values:

| Stack | Test command (example) | `REPORTS` |
|---|---|---|
| Python | `pytest --cov --cov-report=xml` | `coverage.xml` |
| Node | `npx vitest run --coverage --coverage.reporter=lcov` | `coverage/lcov.info` |
| Go | `go test ./... -coverprofile=coverage.out` | `coverage.out` |
| Java (Maven) | `mvn -B verify` with the jacoco-maven-plugin `report` goal | `target/site/jacoco/jacoco.xml` |
| Java (Gradle) | `./gradlew test jacocoTestReport` | `build/reports/jacoco/test/jacocoTestReport.xml` |
| Several or unsure | | `"coverage.xml coverage/lcov.info lcov.info coverage.out **/jacoco.xml **/cobertura*.xml **/clover.xml"` |

**Runners.** GitHub-hosted runners have `curl` and `jq` installed.
Self-hosted runners need curl and jq; without jq the upload has no identity
and is refused.

**Fork pull requests.** The upload proves which repository it comes from with
a short-lived identity token from the workflow, so no secret is needed. A pull
request from a fork gets no such token. Its upload is accepted only when the
repository is public and the upload is for the open pull request's current
head commit; the comment then labels it "uploaded from a fork pull request
without verified identity". Such an upload never replaces a verified one for
the same commit. On a private repository, fork uploads are refused.

### Getting coverage into hosted

Measured coverage reaches a repo's **Tests** page and the hosted MCP tools by
one of two routes. Both read the same report formats your test runner already
writes.

| Route | Where | Gives |
|---|---|---|
| CI upload | The upload step above, from a GitHub Actions job | File and line totals, plus the per-test map when a default-branch push run sends a report that carries it |
| Manual upload | The upload panel on the repo's Tests page | File and line totals, plus the per-test map when the report carries it |

**Formats.** The manual upload takes LCOV, Cobertura XML, Clover XML, the
Repowise coverage JSON, and a coverage.py `.coverage` file. The CI upload takes
the formats listed under `REPORTS` above, plus a `.coverage` file.

**Default-branch pushes fill the Tests page.** Only an upload from a `push` to
the default branch reaches the Tests page and the MCP tools. Uploads from pull
request runs feed the pull request coverage check and nothing else, so run the
coverage job on both events:

```yaml
on:
  push:
    branches: [main]   # your default branch
  pull_request:
```

The job needs `permissions: id-token: write` (and `contents: read`) so the
upload can prove which repository it comes from.

**Per-test map.** Knowing which tests to run for a change needs a report that
records which test covered each line. Produce it with
`pytest --cov=<package> --cov-context=test` (needs pytest-cov), which writes a
`.coverage` file, or as an LCOV report where each block carries a distinct
`TN:` test name. See
[Building a per-test map](https://docs.repowise.dev/intelligence/test-intelligence#building-a-per-test-map).

Send it either way:

- **Manual upload** on the repo's Tests page.
- **CI upload** from a `push` run on the default branch, with `.coverage` in
  `REPORTS` (the step sends it as `-F report=@.coverage`). The workflow the bot
  generates for a Python project sends it on push.

Pull request runs give totals for the pull request check only, even when they
send `.coverage`.

**Where it shows.** The Tests page shows per-file and per-module coverage,
untested hotspots, and the risk-versus-coverage view. Through the hosted MCP,
[`get_risk`](https://docs.repowise.dev/mcp/get-risk) returns `tests_to_run` and
[`get_change_risk`](https://docs.repowise.dev/mcp/get-change-risk) lists impacted tests, both from
the per-test map.

### Checks settings

| Key | Default | Meaning |
|---|---|---|
| `checks_comment` | `on_signal` | When the checks section renders: `off`, `on_signal` or `always`. |
| `coverage` | `show` | Coverage in the comment: `off` or `show`. `check_run` shows it and also runs `Repowise / coverage`. |
| `tests_reach` | `true` | Name the changed files no test reaches when there is no coverage report. |
| `show_security_check` | `true` | Security in the comment. |
| `security_shows_at` | `med` | Lowest severity shown: `high`, `med` or `low`. |
| `doc_drift` | `off` | Doc drift in the comment: `off`, `show`, or a confidence (`high`, `medium`, `low`) at which a refuted claim alone posts the comment. |
| `check_runs` | `[review]` | Check runs: any of `review`, `coverage`, `security`, `doc_drift`. |
| `review_check` | `true` | The `Repowise review` check. `false` turns it off even when `check_runs` lists `review`. |
| `coverage_fail_under` | none | Patch coverage percent below which `Repowise / coverage` fails. Empty reports only. |
| `security_fail_on` | `high` | Lowest severity that fails `Repowise / security`: `high`, `med` or `low`. |
| `not_evaluated` | `neutral` | What a check run concludes when it could not evaluate: `neutral` or `failure`. |

## Configuration

Settings come from layers, lowest first; a higher layer wins for each key it
sets:

1. the bot's defaults;
2. the **org policy** of the installation;
3. `.repowise/bot.yaml` in the repo;
4. the repo's own settings on the dashboard;
5. the keys the org policy **locks**.

Policy keys decide a gate, so a pull request cannot loosen them from its own
branch: `health_gate`, `check_runs`, `coverage_fail_under`, `security_fail_on`,
`not_evaluated`, `review_check`, `ignore_paths` and `apply_labels` are read from
`.repowise/bot.yaml` on the base branch only. Paid keys (`review_mode`,
`review_depth`, `ai_pr_cap_cents`, `ai_monthly_cap_cents`) are read from the
dashboard only. `ignore_paths` is a
union across layers, so a layer can add scope but never un-ignore a path
another layer skips. A malformed value is skipped and the layer below stands.

### Org policy and repo overrides

**Settings → PR bot → an installation** opens its org policy: the settings
every repo it covers starts from. It offers three presets:

| Preset | Sets |
|---|---|
| **Quiet** | Posts only for critical findings. No pass/fail checks, no merge gate. |
| **Standard** | The bot's defaults: posts for high-severity findings, no pass/fail checks, no merge gate. |
| **Strict gate** | Posts from medium severity, `check_runs: [coverage, security]`, a blocking merge gate, and `not_evaluated: failure`. |

No preset changes the review mode, which is a paid choice of its own. The page
shows how many repos follow the preset (override none of its keys), and can
make selected repos follow it: after a confirmation naming the repos and the
settings, it clears their overrides of the preset's keys and leaves the rest.

Each policy row has a **lock**. A locked key wins over the repo's dashboard
settings and its `.repowise/bot.yaml`, so a repo cannot loosen it; the repo's
page shows the row read only. Locking a key the policy does not set yet pins
the value the repos get at that moment. The merge gate locks as one key.

A repo's page (**Customise** on the policy, or **Settings** on a repo row)
shows every setting with its value and where it comes from: **Default**,
**Org default**, **Overridden** or **Locked by org**. A change there overrides
the policy for that repo; **Reset** on a row, or **Reset all to org default**,
removes the override. The dashboard cannot see `.repowise/bot.yaml`, so a value
marked Default or Org default may still be changed by the repo's YAML.

### `.repowise/bot.yaml`

Committed and versioned with the code:

```yaml
bot:
  enabled: true
  comment_mode: on_signal      # or "always" to post a clean bill of health
  verbosity: standard          # compact | standard
  introduced: high             # off | show | critical | high | medium | low
  recent_decline: show         # same values; "show" never posts on its own
  change_risk: signal          # off | signal (PR page) | comment
  on_lines: threads            # off | annotations | threads | both
  apply_labels: false          # opt in to repowise:* PR labels
  ignore_paths:
    - "vendor/**"
    - "**/*.generated.*"
  health_gate:
    mode: advisory             # off | advisory | blocking
    repo_drop_block: 0.3
    min_new_file_score: 7.0
    block_on_introduced: true
    block_at: high
  checks_comment: on_signal    # off | on_signal | always
  check_runs: [review, coverage]  # review | coverage | security | doc_drift
  coverage_fail_under: 80      # percent of changed lines; omit to report only
  not_evaluated: neutral       # neutral | failure
```

The comment's parts each have a switch: `show_health`, `show_checklist`,
`show_coupling`, `show_ownership`, `show_evidence` and `show_declining_alert`.
Pre-v2 key names (`show_introduced` with `introduced_posts_at`,
`inline_threads`, `show_findings_on_lines`, `coverage_check_run`,
`checks_comment_mode` and the rest) still read into the key they became.

### PR labels (opt-in)

With `apply_labels: true` the bot also applies `repowise:*` labels, created on
demand and reconciled on every run: a label is removed once its signal clears,
and labels outside the `repowise:` namespace are never touched.

| Label | Applied when |
| --- | --- |
| `repowise:gate-failed` | A **blocking** gate you configured actually tripped |
| `repowise:risk-high` | Top tercile of this repository's own commit-risk distribution |
| `repowise:missing-tests` | A file with recent fix history changed, and no test that imports it was touched |
| `repowise:size-xl` | Too large for granular analysis |

They are off by default, and the list is short on purpose. A label is permanent,
binary, and public in the pull-request list, so it reads as a verdict on the
author in a way an editable comment does not, which means it has to be reserved
for things that are actually rare. Signals that fire on ordinary work (a repo
score that dipped, a hotspot touched, a moderately-sized diff) stay in the
comment, where they carry their context with them.

## Pricing

Signals is free forever for **public / OSS repos**: no model, no tokens, no PR
cap. **AI review** is part of Pro and billed from credits, about 9 cents a pull
request on average (see [review modes](https://docs.repowise.dev/hosted/bot#review-modes)). **Private repos** require the Pro
plan as well. Private installs still index on connect, so the snapshot is ready
when you upgrade. See the [pricing page](https://repowise.dev/pricing).

## How it relates to the rest of Repowise

Same engine, different surface. The bot reads the [code-health](https://docs.repowise.dev/intelligence/code-health),
[git-intelligence](https://docs.repowise.dev/intelligence/git-history), and
[dependency-graph](https://docs.repowise.dev/intelligence/dependency-graph) layers, and every
comment links to the pull request's page on the dashboard, where the same data is queryable
by humans (the web UI) and by AI agents (the [MCP tools](https://docs.repowise.dev/mcp/overview),
including [`get_health`](https://docs.repowise.dev/mcp/get-health) for a pre-PR self-check).
