The Repowise PR bot is a GitHub App that runs the same intelligence layers as the CLI and dashboard against every pull request, and posts one comment. 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 triage and Full AI review add a model and are part of Pro, billed from credits. Private repos need the Pro plan too. See review modes.
Install it from the GitHub App page: github.com/apps/repowise-bot. Pick individual repos or a whole org. There is also a marketing overview and you can manage installs from the dashboard.
Install
Install the GitHub App. Open github.com/apps/repowise-bot, choose the repos (or the whole org), and approve.
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.
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.
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.
Read the report. Each comment links back to the live snapshot on
repowise.dev for the full graph, hotspots, and marker drill-downs.
Permissions requested: Contents: Read · Pull requests: Write · Metadata: Read · Issues: Write (GitHub treats PR comments as issue comments). The optional merge gate and findings on lines additionally need Checks: Write; existing installs re-consent when you turn either on. The one-click coverage setup needs Contents: Read and write and Workflows: Read and write.
Review modes
Each repository picks one mode in the dashboard (Settings → PR bot). The mode
is set only there: the bot ignores it 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 triage | When a signal fires, a model picks what matters from it and writes the comment. | Pro | About 2 cents per push that comments |
| Full AI review | A model reads the whole diff and the code around it on every PR, and a second pass checks each point against the lines it cites before it is posted. Each point is a review thread on its line, and the PR comment links to it. A clean PR gets one line saying so. | Pro | About 10 cents per push |
AI review draws on the same credit balance as chat, docs and MCP. Pro includes $5 of credits a month, about 50 full reviews. Each repository also has a monthly AI spend limit ($5 by default). When the plan does not include AI review, the balance is empty, or the limit is reached, the bot posts only what Signals would and notes the pause on its next comment.
The silence rule
This applies in Signals and AI triage. Most PR bots are noise. This one stays silent unless there is a measurable signal worth interrupting a reviewer for. A green PR with no findings gets no comment at all. Full AI review is the exception: it reviews every PR and posts one line on a clean one. When the bot does comment, improvements are shown alongside problems so cleanup gets credit. It 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 rather than posting a wall of duplicates; if the silence rule fires after an earlier comment, the stale comment is deleted.
What's in the comment
Each section is independent and only renders when it has something to say:
| Section | What it shows |
|---|---|
| At a glance | A one-line factual summary of what the PR touches (files, hotspots, findings, scope), with a per-module drill-down when the PR spans several. |
| Before-you-merge checklist | Every signal restated as an action, as a tickable task list: the tests that depend on the changed files, co-change partners missing from the PR, and the docs that usually track this code. |
| Change map | A deterministic Mermaid diagram of the PR's structural impact: changed files on the left, the code that imports them on the right, with guarding tests and co-change gaps attached. Wide PRs collapse to a module-level flow so the map stays legible. |
| Health delta | Repo-level score change across the three signals (defect · maintainability · performance) plus a per-file table of which files dropped, with the concrete markers behind each drop. |
| Change risk | The just-in-time defect-risk score for the diff (the same signal as repowise risk), banded against this repo's own commit history. |
| Review priority | The changed files ranked by recent bug-fix history. Defects cluster, so these deserve the closest look. |
| Hotspot touches | When the PR modifies a high-churn, high-dependent file, surfaces its primary owner and suggests adding their review. |
| Hidden coupling | Files that historically co-change with a modified file but are absent from this PR, which catches partial refactors before they break the next deploy. |
| Declining-health alert | Files trending down across recent snapshots that also regressed in this PR. |
| Dead code | New unreachable code the PR introduces, plus cleanup celebrated when it removes existing dead code. |
| AI vs human | When the diff mixes AI- and human-authored hunks, the bot attributes the health delta by authorship: "the AI-authored files account for the larger share of the regression." Built on the agent-provenance layer, the one signal a generic linter cannot produce. |
Every signal is deterministic: health scoring is tree-sitter + git + the calibrated marker scorer, and refactoring suggestions are template strings keyed off the marker type. In Signals nothing is generated by a model, so every comment is reproducible. AI triage and Full AI review write their part of the comment with a model, and the pull request page names the model that wrote it.
Findings on lines
With show_findings_on_lines, the bot marks findings on the diff as Check Run
annotations, so they show in Files changed next to the code: high and
critical health findings on lines this PR added, and changed signatures that
code outside the PR still calls. In Full AI review its points on code this PR
wrote are marked too, capped by the review sensitivity; they are posted as
review threads either way. It is off by default.
With the merge gate off, the annotations ride on a neutral check that is
posted only when there is something to mark.
The merge gate
Beyond commenting, the bot can post a GitHub Check Run that a branch
protection rule can require, turning code health into a merge gate. It is
off by default and configured per repo (health_gate.mode):
off: no check is created.advisory: always concludesneutral. The verdict is visible on the PR but never blocks. The safe default to start with.blocking: concludesfailurewhen a rule trips, which a required check will block the merge on.
The rules (evaluated only in blocking mode) let you adopt a clean-as-you-code
policy without forking any constants:
| 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. |
block_on_introduced | The PR introduces any new marker in its added lines. |
block_ai_regression | AI-authored files account for the larger share of the regression (the provenance wedge). |
Or start from a named profile and nudge single knobs: health_gate.profile
accepts warn-only, block-new-file-floor, block-any-decline, or
block-on-ai-regression. The profile seeds the rules; any explicit key set
alongside it wins.
Because the bot already re-scores the repo at both the base and head commits, the gate and the AI-vs-human attribution need no extra index; they read the same per-PR analysis the comment is built from.
CI checks
Beside the review comment, the bot posts a second checks comment with what the tests, the security scan and the docs say about the change. It is on by default and never repeats the review comment: while it is on, doc drift and the "run these tests first" line move out of the review comment and into it.
| Check | What it says |
|---|---|
| Coverage | The share of the pull request's changed lines that its tests ran, the uncovered ranges per file, and the run the report came from. Measured, and only when your CI uploads a coverage report (see coverage setup). |
| Tests | Which tests reach each changed file through the call graph or the import graph of the last indexed commit. Inferred: a test that reaches a file does not mean it runs the changed lines, so this never shows a percentage. A file the index has not seen yet is listed as such, never as untested. |
| Security | New findings on the lines the pull request adds, scanned at its head commit. Secrets are masked. repowise-security-ignore markers and security.patterns in .repowise/config.yaml apply, as they do for repowise security check. Low severity is hidden by default. |
| Doc drift | Docs the pull request edits that make a claim the code refutes, and docs that name a changed file. |
When it posts. With checks_comment_mode: on_signal, the default, the
comment is created only when a check has something to say. Once it exists it is
always updated, to an all-clear line when the findings are gone, so a stale
finding never lingers. always posts on every pull request.
Pass/fail checks
Each check can also post its own GitHub check run, which a branch protection rule can require. All three are off by default and work with or without the checks comment:
| Check run | Fails when |
|---|---|
Repowise / coverage | Patch coverage is below coverage_fail_under (a percent, 0 to 100). With no minimum it reports the figure and concludes neutral; with no report yet it says it is waiting for one. |
Repowise / security | The pull request adds a finding at or above security_fail_on (high by default). |
Repowise / doc drift | A doc the pull request edits carries a claim the code refutes, at high confidence. |
Coverage setup
Measured coverage needs a coverage report from your CI. In the dashboard
(Settings → PR bot → a repo's settings → CI 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 comment, 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:
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.
CI checks settings
| Key | Default | Meaning |
|---|---|---|
checks_comment | true | Post the checks comment. Off: doc drift and the tests line stay in the review comment. |
checks_comment_mode | on_signal | on_signal or always, as above. |
show_tests_check | true | The Tests and Coverage rows. |
show_security_check | true | The Security row. |
show_doc_drift | true | The Doc drift row (the same key as the review comment's doc drift). |
security_shows_at | med | Lowest severity shown: high, med or low. |
coverage_check_run | false | Post Repowise / coverage. |
coverage_fail_under | none | Patch coverage percent below which it fails. Empty reports only. |
security_check_run | false | Post Repowise / security. |
security_fail_on | high | Lowest severity that fails it: high, med or low. |
doc_drift_check_run | false | Post Repowise / doc drift. |
inline_threads | true | Full AI review posts each point as a review thread on its line. Off keeps the points in the review comment. |
All of them can be set in .repowise/bot.yaml under bot: or on the dashboard.
Configuration
Two ways to configure, with the dashboard taking precedence:
1. In-repo .repowise/bot.yaml (committed, versioned with the code):
bot:
enabled: true
comment_mode: on_signal # or "always" to post a clean bill of health
verbosity: standard # compact | standard | detailed
show_dead_code: true
show_change_risk: true
apply_labels: false # opt in to repowise:* PR labels (default off)
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: false
block_ai_regression: false
checks_comment: true # the separate CI checks comment
coverage_check_run: true # Repowise / coverage
coverage_fail_under: 80 # percent of changed lines; omit to report onlyYou can toggle any section (show_summary, show_health, show_hotspots,
show_ownership, show_coupling, show_dead_code, show_refactoring,
show_declining_alert, show_change_risk, show_review_priority,
show_checklist, show_change_map), set the comment threshold, choose the
verbosity, and ignore paths.
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.
2. The hosted dashboard (Settings → PR bot). The dashboard config overlays
the YAML: any field set there wins, fields it omits keep the YAML/default value,
and ignore_paths is unioned (the dashboard can only add scope, never silently
un-ignore a path the repo author chose to skip). A malformed value is skipped,
never wedging the analyzer.
Pricing
Signals is free forever for public / OSS repos: no model, no tokens, no PR cap. AI triage and Full AI review are part of Pro and billed from credits (see 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.
How it relates to the rest of Repowise
Same engine, different surface. The bot reads the code-health,
git-intelligence, and
dependency-graph layers, and every
comment links to the live dashboard snapshot, where the same data is queryable
by humans (the web UI) and by AI agents (the MCP tools,
including get_health for a pre-PR self-check).