prxref
Fast automated AI code review for Bitbucket, GitLab, GitHub, Gitea/Forgejo, and Azure DevOps — Cloud and self-hosted.
prxref reviews pull and merge requests on Bitbucket, GitHub, GitLab, Gitea/Forgejo (including Codeberg), and Azure DevOps in sub-minute review cycles. It parses unified diffs, partitions changes into risk-ranked chunks, gives each worker the dependency pins and out-of-hunk definitions its chunk references when the forge can serve file content, fans out parallel single-shot LLM reviews across a cheap-first model fallback chain, filters findings through deterministic quality gates, and publishes inline comments alongside an executive summary. Give it the spec or ticket a change implements with --spec (a web page, a local file or directory, or a Jira ticket URL) and the review also checks the diff against that spec.
┌──────────────────────┐
│ Pull / MR URL │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ detect_forge │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Forge Adapter │
│ (GitHub / GitLab / │
│ Bitbucket Cloud / │
│ BB Server / ADO) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Unified Diff │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Risk-Ranked Chunking │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Parallel LLM Workers │
│ (Fallback Chain) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Quality Gate │
│ Line Align / Dedup │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Post Inline Comments │
│ + Summary │
└──────────────────────┘
Deterministic Checks and Quality Passes
Not every finding comes from a model, and no finding posts unfiltered. prxref
computes one class of finding directly from the parsed diff — the
release-shaped-PR check — and then runs every finding, model-authored or not,
through the team severity map (only when the review rules declare one) and
spec grounding, two passes that relabel a severity and drop nothing, and then
through eighteen more deterministic passes: the example-echo check, location validation, package.json claim
checks, line alignment, anchor snapping (verifying a finding's line against
the code it quotes in the head file), thread dedup, settled-thread
suppression, stable finding ids (always on; see
docs/env-vars.md), severity
consistency, the removal-claim check, the hedge gate, the rule-scope check
(clearing a rule label no scoped team-rules section covers, or whose rule names another kind of defect), finding grouping (opt-in
with PRXREF_GROUP_FINDINGS), the per-rule cap (on by default when review
rules are loaded; PRXREF_MAX_FINDINGS_PER_RULE), Also-at location
verification, the quality gate, sweep
dedup, and the containment note. A filtered finding is never discarded
silently — it is kept with a drop_reason for the run log, and visible in a
--no-post dry run or under --format json.
The CI wiring check (#66, on by default; --ci-wiring off or
PRXREF_CI_WIRING=off turns it off) flags a verification script, shebang,
test, or make/npm check target the PR adds that no CI configuration
file invokes. Two more
deterministic checks are opt-in: the PR metadata rules (#70,
metadata_rules = ".prxref/metadata.toml" naming a separate TOML rules
file with branch_patterns, commit_reference and area_globs; see
config-file.md), which check the branch name, the
commit subjects and the touched areas with zero model calls and report
a violation in a PR metadata section of the summary, never as a finding,
so it is never posted inline and never moves the verdict or
PRXREF_FAIL_ON; and the
execution-evidence drop (#69, --evidence-file), which drops a finding
claiming a header is missing when an exit-0 evidence item shows that header
as a Name: value line for the resource the finding names (a missing
directive of a present header is kept), and raises a
warning (at most 10 per run) at each path:line a failing evidence item
reports in a changed file, citing the command and its exit code.
The passes, the checks, every drop_reason string, and which of them have a
knob: docs/quality.md.
PR Size Advisory
A team that keeps PRs small can set PRXREF_SIZE_WARN_LINES (lines added plus
removed) and/or PRXREF_SIZE_WARN_FILES (files changed). A PR above either
threshold gets one line at the top of its summary, such as This PR changes 812 lines in 24 files, above the team guideline of 500 lines and 20 files. Consider splitting it. The line names only the limits that were exceeded. Both
thresholds are unset by default, which turns the advisory off; 0 is a real
threshold that flags any change at all. The counts come from the parsed diff and
skip the common ecosystems' lockfiles (package-lock.json, uv.lock,
Cargo.lock, go.sum, …), generated files (*.snap, __snapshots__/, *.min.js, *.map,
*.generated.*, *.auto.*), and any path matching PRXREF_SIZE_IGNORE_GLOBS,
which adds to those built-ins and never replaces them. A binary file counts as one
file and zero lines, so the line count is a lower bound when a forge omits a
file's hunks. The advisory is not a finding: it never changes the verdict or the
exit code, and with --no-post or PRXREF_POST_MODE=inline it appears only in
the run record, under --format json as size_advisory, and as a
size advisory: line in the CLI output. See
docs/env-vars.md for the glob syntax. A PR-metadata
violation (#70) prints the same way, as one pr metadata: line each.
Quickstart
Run reviews instantly without local installation using uvx, or install the CLI globally:
# Run one-shot review via uvx
uvx prxref review --pr-url https://github.com/org/repo/pull/123
# Or install tool globally
uv tool install prxref
prxref review --pr-url https://github.com/org/repo/pull/123
# Or install straight from source (works before the first PyPI release)
uv tool install git+https://github.com/sblattj/prxref
In CI, keep the pipeline to credentials and the endpoint, and commit a .prxref.toml at the repository root for everything else (see docs/config-file.md and the example); prxref config check validates it. On a lane that gates merges, read that file from the target branch, as its security section shows.
To work on prxref itself (development setup, tests, and lint), see CONTRIBUTING.md.
Review Any Forge
Pass any PR or MR URL directly. Forge type, repository namespace, and pull request ID are detected automatically:
# Bitbucket Cloud
prxref review --pr-url https://bitbucket.org/workspace/repo/pull-requests/42
# Bitbucket Server / Data Center (self-hosted, any host, with or without a deployment context path)
prxref review --pr-url https://bitbucket.corp.example/projects/PLAT/repos/api/pull-requests/42
# ...including a plain-HTTP deployment, e.g. the standalone install's default port
prxref review --pr-url http://bitbucket.internal:7990/projects/PLAT/repos/api/pull-requests/42
# GitHub & GitHub Enterprise
prxref review --pr-url https://github.com/owner/repository/pull/108
# GitLab & Self-Hosted GitLab (including nested subgroups)
prxref review --pr-url https://gitlab.com/group/subgroup/project/-/merge_requests/15
# Gitea & Forgejo, on any host (including Codeberg)
prxref review --pr-url https://codeberg.org/owner/repository/pulls/7
prxref review --pr-url https://git.example.com/owner/repository/pulls/7
# Azure DevOps Services (dev.azure.com or the legacy *.visualstudio.com host)
prxref review --pr-url https://dev.azure.com/organization/project/_git/repository/pullrequest/42
prxref review --pr-url https://organization.visualstudio.com/project/_git/repository/pullrequest/42
# Azure DevOps Server (on-prem; the URL names the collection and the project)
prxref review --pr-url https://ado.corp.example/tfs/DefaultCollection/project/_git/repository/pullrequest/42
Supported hosts. Every forge is supported on any host. GitHub Enterprise Server and self-hosted GitLab share one adapter each with their SaaS products, which speak the same REST API at a different base URL. Bitbucket does not: Server / Data Center speaks /rest/api/1.0 against different resource shapes, so it is a separate adapter selected automatically from the URL — PRXREF_BITBUCKET_SERVER_TOKEN for Data Center, PRXREF_BITBUCKET_TOKEN for Cloud. Gitea and Forgejo share one adapter on any host, Codeberg included, authenticated by PRXREF_GITEA_TOKEN; a public repository can be reviewed with no token. Azure DevOps Services and Server share one adapter. It has no diff endpoint to call, so it rebuilds the PR's diff from the changed files; a public project can be reviewed with no token at all. Posting to Azure DevOps is not yet verified against a live server, and Azure DevOps Server is untested. See docs/forges.md.
LLM Configuration
prxref operates without direct cloud provider SDK keys (no Anthropic API keys). It ships with no default endpoint and no default model chain: point it at any OpenAI-compatible /chat/completions server (OpenRouter, Together, Groq, vLLM, Ollama, a self-hosted gateway), install the optional litellm extra, or run the claude or kiro-cli CLI you are already logged in to. PRXREF_LLM_MODELS is required on every backend and PRXREF_LLM_BASE_URL on openai-compat; leaving a required one unset exits 2 with an error naming the variable.
# Default backend: plain HTTP to any OpenAI-compatible endpoint
export PRXREF_LLM_BACKEND=openai-compat # aliases: ferry, http
export PRXREF_LLM_BASE_URL="https://openrouter.ai/api/v1"
export PRXREF_LLM_API_KEY="$OPENROUTER_API_KEY"
export PRXREF_LLM_MODELS="z-ai/glm-5.3-flash"
export PRXREF_LLM_REASONING_EFFORT=low
export PRXREF_LLM_MAX_TOKENS=4096 # raise this if you raise the effort
# Optional: in-process litellm extra
# pip install 'prxref[litellm]'
export PRXREF_LLM_BACKEND=litellm
export PRXREF_LLM_MODELS="openrouter/meta-llama/llama-3.3-70b-instruct,bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0"
# Optional: your own logged-in Claude Code CLI, on your own machine
export PRXREF_LLM_BACKEND=claude-cli
export PRXREF_LLM_MODELS="sonnet"
export PRXREF_LLM_TIMEOUT=120 # each call includes CLI start-up
claude-cli and kiro-cli run the CLI already installed and logged in on your machine, on your subscription and for your own use only. Do not use them for a team, a shared webhook, or CI; use an API key through openai-compat or litellm there. See Subscription CLI backends.
On a reasoning model the hidden reasoning trace draws from the same completion budget as the answer, so turning PRXREF_LLM_REASONING_EFFORT up makes truncation more likely. A reply that hits the budget before it is usable is retried once at double the budget (capped at 16384), which costs one extra call and happens only in that case. If the retry is cut off too, the chunk is counted as failed and the posted summary names the reason and the variable to raise; see Reasoning models and the token budget.
On openai-compat and litellm, temperature 0.0 and a sampling seed are sent on every call — PRXREF_LLM_SEED when set, else one random seed per process shared by the whole run (issue #56), and none at all with PRXREF_LLM_SEED=off, for providers such as Bedrock that reject it — but neither makes a review bit-reproducible — provider fingerprints, load-balanced backends, and gateways that ignore seed all still vary the model's output. The CLI backends send neither, and the run record's sampling field shows both as null. Everything downstream of the model is deterministic: findings are ordered by (file, line, title) and the caps break ties by content, and the run record's sampling field reports which knobs were in force. See Determinism.
See docs/llm.md for architecture, failover behavior, and backend setup, and docs/env-vars.md for tuning the confidence floor and finding caps to your team.
The model chain, the effort, the token budget and the other non-secret settings above can also be committed in the repository's .prxref.toml (llm_models = ["z-ai/glm-5.3-flash"]); the backend, the endpoint and the key stay in the environment. See docs/config-file.md; prxref config check validates the file.
Forge Authentication
Configure the authentication token matching your forge:
| Forge | Environment Variable | Notes |
|---|---|---|
| Bitbucket Cloud | PRXREF_BITBUCKET_TOKEN |
Bearer token (workspace or repo access token) |
| Bitbucket Cloud (Basic) | PRXREF_BITBUCKET_USER + PRXREF_BITBUCKET_APP_PASSWORD |
App password fallback |
| Bitbucket Server / DC | PRXREF_BITBUCKET_SERVER_TOKEN |
HTTP access token (falls back to PRXREF_BITBUCKET_TOKEN) |
| Bitbucket Server (Basic) | PRXREF_BITBUCKET_SERVER_USER + PRXREF_BITBUCKET_SERVER_PASSWORD |
Basic-auth fallback |
| GitHub | PRXREF_GITHUB_TOKEN |
Personal Access Token (PAT) or GitHub App token |
| GitHub Enterprise | PRXREF_GITHUB_ENTERPRISE_TOKEN |
Used when host is not github.com (falls back to PRXREF_GITHUB_TOKEN) |
| GitLab | PRXREF_GITLAB_TOKEN |
Personal, project, or group access token (PRIVATE-TOKEN) |
| Gitea / Forgejo | PRXREF_GITEA_TOKEN |
Access token: read:repository to review, write:repository plus write:issue to post. With none set, public repositories are read anonymously |
| Azure DevOps | PRXREF_AZURE_DEVOPS_TOKEN |
Personal access token: Code (Read) to review, Code (Read & write) to post |
| Azure DevOps (Pipelines) | SYSTEM_ACCESSTOKEN |
The job token, used when no PAT is set; map it into the step with env: SYSTEM_ACCESSTOKEN: $(System.AccessToken). With neither set, public projects are read anonymously |
See docs/env-vars.md for the full configuration reference, docs/forges.md for forge specifics, docs/quality.md for the deterministic checks and every drop reason, and docs/systemic-sweep.md for the whole-PR sweep's digest classes.
When the token can read a PR but not comment on it (a fork PR under a plain pull_request workflow, or a read-only pipeline token), prxref still delivers the review through the CI job itself — GitHub annotations and job summary, Azure Pipelines log issues, a GitLab Code Quality report, or the log — and records the failed posts in the run record; see When prxref cannot post (PRXREF_FALLBACK, on by default).
Webhook Server
Run prxref as a persistent daemon to handle webhook events from GitHub, Bitbucket, GitLab, Gitea/Forgejo, and Azure DevOps:
prxref serve --port 8080 --host 0.0.0.0
The service exposes:
POST /webhook— verifies HMAC or token signatures per forge (for Gitea and Forgejo, theX-Forgejo-SignatureorX-Gitea-SignatureHMAC againstPRXREF_GITEA_WEBHOOK_SECRET, checked before the GitHub-compatible headers those forges also send; for Azure DevOps service hooks, the Basic-auth password againstPRXREF_AZURE_DEVOPS_WEBHOOK_SECRET), enqueues incoming PR events, and responds immediately with202 Accepted. A background worker processes reviews serially. Registering each forge's webhook: docs/deploy.md.GET /health— liveness probe returning{"ok": true}.
Review Against a Spec or Ticket
Give prxref the spec or ticket a change implements, and the review also checks the diff against it. A source is a public web page, a local file, a local directory (up to 20 .md, .markdown, .txt or .adoc files directly inside it), or a Jira ticket URL:
prxref review --pr-url https://github.com/org/repo/pull/123 \
--spec https://jira.example.com/browse/PROJ-42 \
--spec https://spec.example.com/client-guidelines.html \
--spec /etc/prxref/specs/
prxref fetches each source and keeps its RFC 2119 statements (MUST, SHOULD, MAY), version pins and naming rules; a Jira ticket's summary and description are kept line by line, up to 6,000 characters, and ranked first. It ranks the rest against the diff and adds that bounded digest (PRXREF_SPEC_DIGEST_TOKENS) to every chunk worker's prompt and to the whole-PR sweep's, with no extra model call. A finding whose only basis is one of those constraints is a 🔍 spec finding, and it quotes the constraint as Spec: "…". PRXREF_SPEC_SOURCES sets the sources for every run, the webhook daemon included; --spec replaces that list for one run.
- Jira. A public ticket needs no configuration. For a private one set
PRXREF_JIRA_BASE_URL,PRXREF_JIRA_EMAILandPRXREF_JIRA_API_TOKEN: credentials only go toPRXREF_JIRA_BASE_URL, and every other fetch is anonymous. - Advisory. 🔍 spec findings are advisory: they never change the verdict, and
PRXREF_FAIL_ON=errorignores them.PRXREF_FAIL_ON=anyis the opt-in gate. - Best-effort. A source that cannot be fetched never fails the review. The summary gains a grounding note that counts the constraints injected and names each failed source by its position and kind (
source 2 (url)), never by its path or URL. When the digest ends up with no constraint at all, the review runs as if no spec had been given, and aspecfinding the model emits anyway is relabelledwarning.
Every setting: docs/env-vars.md. How grounding meets the quality passes: docs/quality.md. Spec sources in CI and on the daemon, fetch time bounds, and what the logs record: docs/deploy.md.
Ticket Context and Scope
Give prxref the ticket a PR is meant to implement, and every finding is marked in, out, or unknown against that ticket's scope:
prxref review --pr-url https://github.com/owner/repository/pull/108 --context-file ticket.md
# or for every run
export PRXREF_TICKET_CONTEXT_FILE=ticket.md
The file is plain text or Markdown: the ticket's title, description, and acceptance criteria, fetched from your tracker by a CI step. It must be a local file, and prxref review reads it before any network call. A URL, a missing file, a directory or other non-regular file, an unreadable file, or a file that is not UTF-8 is a configuration error: the run exits 2, and the message names --context-file, PRXREF_TICKET_CONTEXT_FILE or the .prxref.toml key, whichever supplied the path. A path under the working directory that symlinks out of it is refused the same way. --context-file PATH wins over the variable for one run, and --context-file "" turns it off. Read the file from a trusted checkout or your CI, never from the PR under review, or the PR's author writes the ticket their change is judged against.
A configured ticket is in one of three states:
| File | Prompts | Summary note |
|---|---|---|
| Empty or whitespace only: "this PR has no ticket" | unchanged | No ticket context for this PR — findings were not checked against a ticket's scope. |
| Text without acceptance criteria | ticket and scope ask added | The ticket context has no acceptance criteria — scope was judged from its description alone. |
| Text with acceptance criteria | ticket and scope ask added | none |
Acceptance criteria are recognized by any one of these: a heading or label standing alone on its line (Acceptance criteria, Acceptance test(s), or Definition of done in any case, or AC in capitals, optionally as a # heading, in bold, or with a trailing colon), a Markdown task-list item (- [ ] … or - [x] …), or a Gherkin Given line followed later by a Then line.
What each finding's scope means. in: the finding concerns what the ticket asks for, including code that visibly contradicts one of its acceptance criteria. out: it concerns a change the ticket does not ask for, such as an unrelated refactor or a drive-by edit. unknown: the ticket and the diff do not let the model tell. Without a ticket, or with an empty one, every finding is unknown, and so is any answer from the model other than exactly one of those three words. Scope is advisory only: it never changes a finding's severity or confidence, the verdict, the error cap, or PRXREF_FAIL_ON. The outofscope severity is unrelated and only means minor. How a scope shows on a posted comment is covered in Finding Markers.
How the ticket reaches the model. The ticket text goes into the user prompt of every chunk worker and of the whole-PR sweep, under a ### Ticket context heading. It sits inside a code fence it cannot close, with a line telling the model that it is data, not instructions. The request to add a scope to every finding is prxref's own policy, so it goes into the system prompt instead. While that request is in the prompt, the example finding under ## Output Format in the worker and sweep prompts also carries "scope": "in", because a model that copies the example rather than following the instruction would otherwise never label scope; without a ticket, or with an empty one, the prompts are unchanged. PRXREF_TICKET_CONTEXT_MAX_CHARS (default 6000) caps the text. A longer ticket is cut, and a line after the fence says how many of its characters are shown. Criteria past the cap are not in view, so they do not count toward the state above.
What is recorded. The ticket_context key of --format json and the run's ticket ok trace event hold the path, the SHA-256 of the file's raw bytes, its length in characters, the cap, whether it was truncated, whether it has acceptance criteria, and whether it was empty. The -v line shows the path, the start of the SHA-256, the length, and the active findings' in/out/unknown counts. None of these ever holds the ticket text. The text does appear in the prompt files that --trace-dir writes (chunk0.user.md, sweep.user.md), so treat that directory like the ticket itself.
The webhook daemon ignores the file. One file cannot describe every PR a daemon sees, so prxref serve never reads PRXREF_TICKET_CONTEXT_FILE and logs a warning once at startup when it is set.
Team Review Rules
Give prxref your team's review checklist and every chunk worker and the whole-PR sweep review against it:
prxref review --pr-url https://github.com/acme/widget/pull/42 --rules-file "$RUNNER_TEMP/prxref-rules.md"
--rules-file PATH, orPRXREF_REVIEW_RULESfor every run, names a Markdown or plain-text file. Its body is added to the system prompt of every review unit under a## Team review rulesheading. The chunk workers check their chunk against it, and the sweep applies only the whole-PR and cross-file rules. Unset, nothing changes.- Optional front matter can map your team's severity words onto prxref's tiers in a
severity:block (blocker: error,major: warning,nit: outofscope). A mapped word the model writes anyway is rewritten before every quality pass, so it is never dropped as an invalid severity. Other front-matter keys are ignored, so a skill file works unmodified. PRXREF_REVIEW_RULES_MAX_CHARS(default24000) caps the body, with a warning when it truncates. The run record'sreview_rulescarries the file'ssha256, its character count, and the parsed map, never the rules text. It appears in--format json, the-voutput, and the JSONL trace.- Path-scoped rules.
--scoped-rules PATH(repeatable), orPRXREF_SCOPED_RULESfor every run, adds rules files that reach only part of the PR, next to that one file: each entry is a rules file, or a directory whose*.mdfiles are read one level deep. A file'sapplies_to:front matter (aliasapplyTo, the.github/instructionsspelling) lists the paths it covers, for exampleapplies_to: ["**/*.java", "!**/src/test/**"]. Each chunk worker then gets only the files that match one of its paths, and the sweep gets the union; a file withoutapplies_toreaches every unit.PRXREF_SCOPED_RULES_MAX_CHARS(default24000) caps the scoped text one unit receives. The run record'sscoped_rulesstamps each file'ssha256and which unit received which file, and-vprintsscoped rules: 2 file(s) rules/helm.md=<first 12 hex> rules/java.md=<first 12 hex> cap=24000. See Path-scoped rules. - Per-rule cap (0.15.0). With either kind of rules file loaded, the model is asked to name the rule each finding applies, and one rule may produce at most
PRXREF_MAX_FINDINGS_PER_RULEfindings per review (default2), across files. The rest fold into the best one (highest severity, then confidence), whose comment lists them asAlso at: ….0turns the cap off; without a rules file it does nothing. See docs/quality.md. - A missing, unreadable, or malformed file exits
2before any network call, naming--rules-fileorPRXREF_REVIEW_RULES(--scoped-rulesorPRXREF_SCOPED_RULESfor a scoped file).
Read the rules from a trusted checkout, never from the PR under review. In CI the workspace is usually the PR's own code, so a rules file inside it lets the PR rewrite its own review rules. Copy the file from the target branch or keep it outside the repository. See docs/review-rules.md for the grammar, the CI recipes, and the daemon.
Prompt Template Overrides
Team rules add to prxref's review prompts but cannot take a line out of them. To change the prompts themselves, give prxref a directory of your own templates:
prxref prompts export ~/acme-prompts # the packaged templates, byte for byte
# edit ~/acme-prompts/worker.md, and delete the templates you leave unchanged
prxref review --pr-url https://github.com/acme/widget/pull/42 --prompts-dir ~/acme-prompts
# or for every run, the webhook daemon included
export PRXREF_PROMPTS_DIR=~/acme-prompts
- What can be overridden.
worker.md(each chunk worker's prompt),systemic.md(the whole-PR sweep's prompt) andsummary.md(the posted summary comment). A template missing from the directory keeps the packaged one. The judge prompt ofprxref evalcannot be overridden, so runs with different prompts are always graded by the same judge. - Checked before any network call. A directory or template that fails a check exits
2, naming--prompts-dir,PRXREF_PROMPTS_DIRor the.prxref.tomlkey, whichever supplied it. The checks are stated once, underPRXREF_PROMPTS_DIRin docs/env-vars.md.--prompts-dir DIRwins over the variable for one run, and--prompts-dir ""turns it off. - Stamped on every run. The run record's
prompt_templatesholds the directory and, for each template file present in it (edited or not, so an unchanged exported copy is stamped too), its path, the SHA-256 of its raw bytes and its length in characters, never its text; a template absent from the directory has no entry. It appears in--format json(nullwithout a prompts directory) and the JSONL trace. The-vline readsprompts: DIR summary=<sha256> worker=<sha256>, showing the first 12 characters of each SHA-256, so every review names the exact templates that produced it.
Read the templates from a trusted checkout, never from the PR under review. Whoever controls the directory controls the whole review, so a PR that commits templates into the checkout CI reviews rewrites its own review. Copy the directory from the target branch or keep it outside the repository. See docs/prompt-templates.md for each template's placeholders, the CI recipe, the daemon, and upgrading.
Repository Context
A chunk worker sees its chunk's diff and the definitions it references from the same file. Repository context also shows it the code the change leans on elsewhere: definitions from other files, and the API and database contracts a change points at. It is off by default:
PRXREF_REPO_CONTEXT=repo prxref review --pr-url https://github.com/acme/widget/pull/42 --no-post
# or from a local checkout of the PR head, with no forge at all
PRXREF_REPO_CONTEXT=repo prxref review --diff-file pr.diff --repo-dir ./widget
PRXREF_REPO_CONTEXT |
What each chunk worker also sees |
|---|---|
off (default) |
Nothing from repository context but the in-repo standards block below (#68), which is on by default at every level: with a reader and at least one document the standards globs select, each chunk gets its standards sections alone and the run record's repo_context has mode standards; otherwise no entry, no repository read beyond one listing, no repo_context trace event. The same-file definitions and dependency versions, Java and Kotlin ones included since 0.17.0, do not depend on this setting. |
diff |
Definitions from the PR's other changed files, and, for a type another chunk changes, the lines changed inside it. Java and Kotlin type declarations are found as well as JavaScript, TypeScript and Python ones. With a reader, a definition one file of the chunk references and another file of the same chunk holds outside its hunks is found too, and the in-repo standards block below. |
repo |
Also definitions from files outside the diff, found through imports, Java's same-package file convention, and a search of file names; a ### Contract excerpts block with the OpenAPI operation or schema, JSON Schema, or earlier migration that a changed route, table or name matches; with a file listing, a ### Code elsewhere that reads state this chunk writes block: short excerpts of unchanged code in the same language that reads a map, table or other state the chunk's added lines write to (at most 6 per chunk, ranked last in the budget); and, last, a ### In-repo standards for this chunk block of heading-sliced sections of the repository's own standards documents (docs/standards/**, docs/adr/**, STANDARDS*.md, SECURITY.md, CONTRIBUTING.md, .github/SECURITY.md, .github/CONTRIBUTING.md), ranked per chunk by what the chunk itself names, capped by PRXREF_CONTEXT_STANDARDS_MAX_CHARS (default 6000) and disabled with PRXREF_CONTEXT_STANDARDS_GLOBS=off (#68). |
- Where it reads. Files are read at the PR head through the forge (every forge adapter can read and list files, see docs/forges.md), or from
--repo-dirwhen given. With neither,diffbuilds its entries from the diff's hunk lines, andrepodoes the same with a WARNING. Atrepothe repository's file listing is read once per run; without one, a WARNING says the name search and the contract files outside the PR are off. - What it never reads. A floor that is always on keeps
**/expected.json,**/cases.json,**/case.json,**/prxref-eval/**,**/.env*,**/*.pemand**/*.keyout of every listing, read and repository-context entry.PRXREF_CONTEXT_EXCLUDE_GLOBSadds globs to it, and cannot take a floor path back out. Neither hides a changed file's hunks, which reach the prompt as the diff.PRXREF_CONTEXT_CONTRACT_GLOBSreplaces the built-in set of contract globs. - Budget.
PRXREF_REPO_CONTEXT_MAX_CHARS(default12000) caps each chunk's entries, admitted in rank order until one does not fit, and a closing line counts the rest. Reads of files outside the PR are capped at 16 per chunk and 200 per run. The timeout retry, which follows a chunk call that outlastsPRXREF_LLM_TIMEOUT, drops all of it, the reader block included, to shrink the prompt. - Workers only. The whole-PR sweep prompt is unchanged.
- Recorded.
--format jsonand the run record carryrepo_context: the reader, the listing size, the read counts, and each admitted entry's path, line and reason, never file text. The JSONL trace gains achunk contextevent per chunk, and in text output-vprints arepo context:line. - Context follow-up (opt-in, #22). A worker that cannot see a symbol's definition is told to ask about it at confidence 0.5 or below, so the default floor of 0.6 drops the question. With
PRXREF_CONTEXT_FOLLOWUP=onatrepo, a chunk whose first reply holds such a below-floor question is sent once more, with the whole definitions of up to 3 symbols its questions name, in backticks or as code-shaped plain text (every question gets one before any gets two), appended as a last block,### Definitions referenced by this chunk, looked up for its open questions. The lookup is deterministic, the model calls no tools, and a chunk gets at most one follow-up, none after a timeout retry, while the sweep gets none. A question the second reply confirms is replaced by the confirming finding; one it does not is dropped asnot confirmed by context follow-up; the second reply's other findings are discarded. A follow-up that fails keeps the first reply's findings. It costs up to 8 reads, 4 excerpts and 4000 characters a chunk, and at most 2 more calls. Atoff(the default) nothing changes; atonbelowrepo, or with no reader, one WARNING says it is off for the run. The run record carriescontext_followup. See Context follow-up.
To measure what it changes, run prxref eval once per level and compare the two runs. Every setting: docs/env-vars.md.
Finding Markers
Each severity has one glyph. It is the same in the summary's counts line, the summary's findings list, and the header of every inline comment:
| Marker | Severity | Meaning |
|---|---|---|
| 🟥 | error |
The change breaks at runtime or is a real bug. |
| 🟧 | warning |
A risk or smell the diff introduces or worsens. |
| 🔍 | spec |
The diff contradicts a constraint quoted from a spec source. See Review Against a Spec or Ticket. |
| ⬜ | outofscope |
Minor: misleading naming, a TODO without context, dead code the diff adds. An unrecognised severity also renders ⬜. |
🟦 is not a severity. It marks a finding that the ticket context puts outside the ticket (scope is out), and it goes in front of the severity glyph, never in place of it:
- Summary: those findings are listed after the others, under their own heading, for example
**🟦 Outside the ticket (2)**followed by- 🟦 🟧 `src/app.py:12` — …. If every finding is outside the ticket, the first list readsNo in-ticket findings.. - Inline comments: the header reads, for example,
🤖 🟦 🟧 **[WARNING · OUTSIDE TICKET] …**. - CLI text output (
--no-postor-v): the finding line ends in[scope: out], or[scope: in]for a finding inside the ticket. With finding grouping on, or the per-rule cap active (review rules loaded), a finding that names a rule gets[rule: <rule>]after that tag (see docs/quality.md).
Findings inside the ticket (in) and findings the reviewer could not place (unknown) carry no scope marker, so a run without a ticket context renders exactly the severity glyphs. Scope never changes a finding's severity. It is not counted separately either: the counts line counts every active finding by severity, and the verdict, the error cap, and PRXREF_FAIL_ON ignore scope.
Before 0.14.0, outofscope findings rendered 🟦. They now render ⬜ on every run, and 🟦 means only "outside the ticket".
Every glyph above is the default. PRXREF_SEVERITY_MARKERS (or severity_markers in .prxref.toml) replaces any of them with name=glyph pairs, for example error=🔴,warning=🟡,out_of_ticket=🔷; the names are error, warning, spec, outofscope and out_of_ticket, and the five glyphs must stay distinct. See docs/env-vars.md.
Code Suggestions
With PRXREF_SUGGESTIONS=on (off by default), an inline comment can carry replacement code for the lines it flags. Each forge gets the form it can apply:
| Forge | What the comment carries |
|---|---|
| GitHub (Cloud and Enterprise) | A suggestion block with the Commit suggestion button. A multi-line suggestion is posted as a comment on the whole line range. |
| GitLab (Cloud and self-managed) | A suggestion:-0+N block with the Apply suggestion button, anchored at the first line it replaces. |
| Bitbucket Cloud, Bitbucket Server / Data Center, Gitea / Forgejo, Azure DevOps | A Suggested change label naming the line or lines, then a plain code block to copy. No apply button. |
An empty suggestion deletes the lines: an empty suggestion block on GitHub and GitLab, and the sentence Suggested change: delete line N elsewhere. A suggestion replaces at most 20 lines, all inside one diff hunk. A suggestion that fails a check is dropped and the finding posts as a plain comment. The summary's findings list never shows suggestions.
Incremental Re-review
By default every run re-reviews the whole PR. With PRXREF_INCREMENTAL=on (off by default), a push re-reviews only the files it changed:
- Each summary ends with an invisible marker naming the PR head it reviewed,
<!-- prxref-reviewed-head: <sha> -->. - On the next run, prxref reads that summary back and compares the marked head with the new one. Only the PR's files touched in between are chunked and reviewed, and only their earlier prxref inline comments are pruned. A merge from the base branch or a rebase cannot widen that set past the PR's own files.
- The whole-PR systemic sweep, the size advisory and the deterministic checks still see every file, so cross-file findings are not lost.
- The chunk findings, and so the verdict, cover the re-reviewed files only, plus whatever the sweep and the deterministic checks find anywhere in the PR. Earlier inline comments on the other files still stand, and the summary says so in one line. A push that changed nothing reviewable runs the sweep alone.
- When a review unit fails, the marker keeps the previous head, so the next push reviews those files again.
A run reviews every file on a first review, when the forge cannot read its summary or the read fails, when the summary has no marker, when the PR head is unknown or the forge cannot compare commits, and when the comparison fails (a force-push can make the marked head unknown). It also reviews every file with --full-review, with PRXREF_POST_MODE=inline (which never writes the marker), with PRXREF_FAIL_ON other than never (a gate's verdict must see the whole PR), and on every replay. --full-review and a PRXREF_FAIL_ON gate skip reading the previous summary, but they still record the head they reviewed, so the following push is incremental again (when a review unit of such a run fails, it records no head, and the following push reviews every file). The --format json key incremental says which scope a run took and why. Turning it on costs one extra forge read per run for the previous summary, plus a compare read when the head has moved. See docs/env-vars.md.
CLI Flags
prxref review takes:
--pr-url URL— full web URL of the PR or MR on Bitbucket, GitHub, GitLab, Gitea/Forgejo, or Azure DevOps. Required unless--diff-fileis given.--no-post— dry run; run review analysis and quality passes without writing comments to the forge. In text mode this also prints every active finding's location, title, and body, and every dropped finding with its drop reason.--max-chunks N— override maximum diff chunks evaluated (default8).--timeout SECONDS— override the per-model request deadline (default45.0, orPRXREF_LLM_TIMEOUTwhen set); the flag wins for the current invocation only.--spec URL_OR_PATH— a spec or ticket to review the PR against: a public web URL, a local file or directory, or a Jira ticket URL. Repeatable. When given, the flags replacePRXREF_SPEC_SOURCESentirely rather than adding to it. See Review Against a Spec or Ticket.--evidence-file PATH— an execution-evidence file: command output from running this PR locally, shown to review units as trusted context. Repeatable; JSON or plain text. ReplacesPRXREF_EVIDENCE_FILESfor one run.--rules-file PATH— your team's review rules (Markdown or text, with optional front matter carrying aseverity:map), added to every review prompt. OverridesPRXREF_REVIEW_RULESfor this run, and--rules-file ""turns an environment-configured file off. Read it from a trusted checkout, never from the PR under review. See Team Review Rules.--metadata-rules PATH— the PR-metadata rules file (TOML; format in config-file.md): branch-name, commit-reference and area checks with no LLM call, reported in aPR metadatasummary section and never as findings. OverridesPRXREF_METADATA_RULESfor this run;onuses the flatPRXREF_*keys instead, and""oroffturns the checks off. A bad or missing file exits2before any network call.--scoped-rules PATH— path-scoped team review rules: a rules file, or a directory whose*.mdfiles are read one level deep. Each file reaches only the chunks whose paths match itsapplies_to:globs, and the whole-PR sweep gets the union. Repeatable. When given, the flags replacePRXREF_SCOPED_RULESentirely rather than adding to it, and--scoped-rules ""turns it off for this run. A file that fails its checks exits2before any network call. Read it from a trusted checkout, never from the PR under review. See Path-scoped rules.--context-file PATH— the ticket the PR is meant to implement (plain text or Markdown). Every finding is then marked in, out of, or of unknown ticket scope, and an empty file means "this PR has no ticket". OverridesPRXREF_TICKET_CONTEXT_FILEfor this run, and--context-file ""turns it off. See Ticket Context and Scope.--prompts-dir DIR— a directory ofworker.md,systemic.mdandsummary.mdtemplates that replace the packaged prompts; a template missing from it keeps the packaged one. OverridesPRXREF_PROMPTS_DIRfor this run, and--prompts-dir ""turns it off. A directory that fails its checks exits2before any network call. Read it from a trusted checkout, never from the PR under review. See Prompt Template Overrides.--repo-dir PATH— a local checkout of the repository at the PR head. WithPRXREF_REPO_CONTEXT=repo, repository context reads and lists files there instead of calling the forge (atdiffit only reads there), so a--diff-filereview gets repository context with no network. At anyPRXREF_REPO_CONTEXTlevel,offincluded, a review with no forge file reader (--diff-filewithout--pr-url) also reads its chunk context there: the dependency versions and the same-file definitions a forge review gets. With--pr-urlthe forge still serves chunk context. It is not a replay flag: on its own it neither turns posting off nor adds areplaystamp. A path that is not an existing directory exits2before any network call, even whilePRXREF_REPO_CONTEXTisoff. See Repository Context.--trace-dir DIR— write each review unit's exact prompt halves, raw model response, and metadata toDIR(chunk0.system.md,chunk0.user.md,chunk0.response.json,chunk0.meta.json, and so on for each chunk and for the whole-PRsweep).PRXREF_TRACE_DIRdoes the same for every run; the flag wins when both are set. The files number chunks from 0, while the JSONL trace'schunkevents (PRXREF_TRACE_FILE) number them from 1 inindex, so the event withindexN pairs withchunk{N-1}.*.--full-review— review every file even whenPRXREF_INCREMENTAL=on; the run still records the head it reviewed, so the following push is incremental again. See Incremental Re-review.--ci-wiring {off,on}— turn the CI wiring check (#66) on or off for this run (PRXREF_CI_WIRINGdoes the same;onis the default): flag a check the PR adds — a verify/smoke/check script or flag, a file that gains a shebang, a test file outside the runner's default include, a verify/smoke/check/test target added to the root Makefile orpackage.jsonscripts— that no CI configuration file invokes. Needs the forge's head-sha file reads or--repo-dir.--ci-wiring-globs GLOBS— comma-separated globs selecting the CI configuration files the CI wiring check reads (PRXREF_CI_WIRING_GLOBS; a set value replaces the built-in set).--context-standards-globs GLOBS— comma-separated globs selecting the repository's own standards documents (PRXREF_CONTEXT_STANDARDS_GLOBS; a set value replaces the built-in set, and''oroffreads no standards for this run).--config PATH— the repository config file to read for this run, instead of.prxref.tomlin the working directory. It wins overPRXREF_CONFIG_FILE, and--config offreads none. Paths inside the file resolve against the file's directory. A file that does not exist exits2withconfiguration error: --config: config file not found: PATH, and a file that fails its checks exits2naming the file and the key, both before any network call.--no-config— read no repository config file: neither.prxref.tomlnor the filePRXREF_CONFIG_FILEnames.--configand--no-configare mutually exclusive. Without either, the file comes fromPRXREF_CONFIG_FILE, else.prxref.tomlin the working directory (parent directories are never searched). The file sits below the environment:defaults < .prxref.toml < PRXREF_* < flags.-v, --verbose— output run timing, token counts, cost, and finding breakdowns to stdout, plus one line each for the rules file, the scoped rules files, the prompt templates, the ticket context (with the active findings' scope counts), the spec sources, and the repository context when they are configured. The repository-context line reads, for example,repo context: mode=repo reader=forge listing=120(partial) reads=16 max_reads=200 max_chunk_reads=16 cap_hit=chunk entries=3 omitted=3, wherereadscounts every repository-context fetch (PR diff files included, which spend neither cap),max_readsandmax_chunk_readsare the run-wide and per-chunk read caps in force, andcap_hitnames the cap that refused a read (no,chunk,runorchunk+run); the line is absent whilePRXREF_REPO_CONTEXTisoff. In text mode this also prints finding bodies and dropped findings, same as--no-post. These lines are text output only: with--format json, stdout holds the JSON object alone.--format {text,json}— output format forreview(defaulttext).jsonprints exactly one JSON object to stdout, with these keys in this order:verdict;findings: active first, then dropped, each withfile,line,severity,confidence,scope(in,out, orunknownagainst the ticket context; alwaysunknownwithout one),rule(the rule the finding names;nullwhen it names none, and alwaysnullwith finding grouping off and the per-rule cap inactive),suggestion(the kept replacement text,""deleting the lines;nullwhen there is none, and alwaysnullwithPRXREF_SUGGESTIONS=off),suggestion_end_line(the last line it replaces,0meaninglinealone; always0whensuggestionisnull),locations(on a grouped finding, the other places itsAlso at:list names, in that order, as{"file", "line"}objects; on the best finding of a rule the per-rule cap folded, every location folded into it, across files, even past the five itsAlso at:list shows;nullon every other row, including each member dropped asgrouped into <file>:<line>, which keeps its own rule; see docs/quality.md),anchor_unverified(trueon a model finding whose quoted evidence could not be anchored — no snippet parseable while file-level, a snippet the head file does not hold, or an ambiguous multi-match — which the anchor-snap pass marks without changing its confidence;falseon every other row),id(the finding's stable id,file#rule#12-hex-claim-hash;nullonly on a summary-only or error run, which ends before the id pass),anchor_block(the enclosing function, YAML key or manifest key at the anchor;nullwhen nothing encloses it or the id pass did not run),id_reused_from(run,verdictorthreadsaying where a reused id came from;nullwhen the id was freshly computed),title,body,drop_reason;chunk_count,chunks_reviewed,chunks_failed;failed_chunks: the review units that failed (#72). Always present,[]when every unit completed, andnullwhen the run never reached review; otherwise one{"unit", "kind", "files", "error"}per failed unit in review order:unitis its 1-based position amongchunk_count,kindischunk(with that chunk'sfiles) orsweep(the cross-file sweep,filesempty), anderroris the failure reason. The text summary prints the same files asnot reviewed:aftercoverage:;chunks_over_budget,largest_chunk_tokens,overflow_files,chunk_token_budget: how the diff was chunked.chunk_token_budgetis the per-chunk token budget in force (PRXREF_CHUNK_TOKEN_BUDGET);chunks_over_budgetis how many chunks' estimates (40 tokens per changed line) exceed it,largest_chunk_tokensthe largest chunk's estimate, andoverflow_fileshow many files were placed past thePRXREF_MAX_CHUNKScap into the smallest chunk because no chunk had room. Such chunks get the samePRXREF_LLM_MAX_TOKENSfor their answer as any other. The three counts are0when no chunking ran. When eitherchunks_over_budgetoroverflow_filesis above0, text output adds onechunks:line saying so;elapsed_ms,input_tokens,output_tokens;cost_usd: the run's cost in USD,nullwhen no source could price it (never0for an unknown cost), andcost_estimated:truewhen any part of it came fromPRXREF_PRICE_TABLE. See Cost accounting;posted;review_rules(path,sha256,chars,max_chars,truncated,severity_map),ticket_context(path,sha256,chars,max_chars,truncated,has_acceptance_criteria,empty; never the ticket text),spec_grounding(sources,ok,failed,constraints,digest_sha256), andsize_advisory(changed_lines,changed_files,lines_limit,files_limit,triggered,message). These four are always present andnullwhen their feature is off;prompt_templates: the prompt-template directory in force (dir, plustemplatesholding thepath,sha256andcharsof each template file present in the directory, edited or not; never the template text). Always present, andnullwhen no prompts directory is configured. See Prompt Template Overrides;scoped_rules: the path-scoped rules in force (entriesas configured;files, one row per loaded file in load order with itspath,sha256,chars,max_chars,truncated,severity_mapandapplies_to;max_chars, the per-unit cap; andunits, thepathandcharsof the files each chunk and the sweep received; never the rules text). Always present, andnullwhen no scoped rules are configured. See Path-scoped rules;rule_counts: the per-rule cap's tally, one{"rule", "kind", "total", "kept"}object for each rule (kindrule), or for each normalized title among findings that name no rule (kindtitle), that at least two chunk findings share:totalof them,keptactive, most findings first. A rule or title with one finding gets no row. Always present:[]when the cap ran and nothing repeated, andnullwhen it did not run (no review rules file,PRXREF_MAX_FINDINGS_PER_RULE=0, or a run that ended before the quality passes). See docs/quality.md;rule_scope_cleared: how many findings had theirrulelabel cleared by the rule-scope check (#75) — the label named a scoped section of the loaded team rules (by heading or rule line) whose scope (ascope:line, or a language or artifact noun in its heading) does not cover the finding's path, or the cited rule names another kind of defect (docs, unused code, style, error handling) than the finding's title. The findings themselves stay active and group by title. Always present:0when the check ran and cleared nothing, andnullwhen no loaded rules file declares a section scope and no label was cleared for its kind. See docs/quality.md;repo_context: the repository context in force (mode,max_chars,max_readsandmax_chunk_reads(the run-wide and per-chunk read caps),contract_globsandexclude_globs;reader, which isforge,repo-dirornull;listing,{"paths", "complete"}ornull;reads;chunk_read_cap_hitandrun_read_cap_hit, true when that cap refused a read, andread_cap_hit, their OR; andunits,{"chunks": [...]}holding one{"entries", "omitted", "retry_dropped"}row per chunk in chunk order, whose entries carrypath,line,symbol,kind,reasonandchars; never the file text). Always present, andnullwhenPRXREF_REPO_CONTEXTisoff. See docs/env-vars.md;parse_retries: how many times the run re-sent a request whose reply could not be used as a review (empty, not JSON, not a JSON object, or an object without afindingslist), summed over every chunk and the whole-PR sweep. Always present:0when nothing was re-sent, andnullwhenPRXREF_LLM_PARSE_RETRIESis0. With--trace-dir, each unit that retried hasparse_retriesandfirst_errorin its meta file. See docs/env-vars.md;context_followup: the context follow-up's tally. Always present, andnullwhilePRXREF_CONTEXT_FOLLOWUPisoff; when it ison,active(falsebelowPRXREF_REPO_CONTEXT=repoor without a repository reader), the run'scalls,confirmed,unconfirmedanddiscardedcounts andinput_tokensandoutput_tokens, andchunks, one row per chunk with the names looked up, each excerpt'spath,line,symbol,sourceandchars(never the file text), and why a chunk wasskipped,nullwhen not active. See Context follow-up;suggestions: the code-suggestion tally (#30). Always present, andnullwhilePRXREF_SUGGESTIONSisoff; when it ison,{"kept": n, "cleared": {"<reason>": n, ...}}over the posted findings, oneclearedentry per reason a suggestion was withheld (see docs/llm.md);incremental: the incremental re-review scope (#34). Always present, andnullwhilePRXREF_INCREMENTALisoff; when it ison,{"mode", "reason", "since_sha", "files_total", "files_reviewed", "marker_sha"}:modeisincrementalorfull,reasonsays why a run is full (nullwhen incremental),since_shais the reviewed head the run compared from, andmarker_shathe head its summary is stamped with. See Incremental Re-review;ci_wiring: the CI wiring check's tally (#66). Always present, andnullwhilePRXREF_CI_WIRINGisoff; when it ison(the default),{"candidates", "ci_files", "picked_up_default", "triggered"}, or{"triggered": false, "reason": ...}when the run had no repository reader. See docs/env-vars.md;evidence: the execution-evidence tally (#69). Always present, andnullwhile no evidence file was given; otherwise{"files", "items", "matched_chunks", "max_chars"}. See docs/env-vars.md;stable_ids: the stable-finding-id tally (#71). Always present, andnullonly on a summary-only or error run, which ends before the id pass; otherwise{"assigned", "reused_from_verdict", "reused_from_thread", "collisions"}over every finding of the run. See docs/env-vars.md;degraded: the posts that failed (#48). Always present, andnullwhen every attempted post succeeded or nothing was posted; otherwise{"cause", "failed", "fallback", "annotations"}:causeispermissionwhen a post was refused with HTTP 401 or 403 (a read-only token) anderrorotherwise,failedlistssummaryand/orinline,fallbacklists what the CI fallback emitted instead (github-annotations,github-step-summary,azure-logging,gitlab-codequality,log; empty withPRXREF_FALLBACK=off), andannotationscounts the annotation lines or report entries emitted. Under--format jsonthe stdout annotations and Azure logging commands are skipped, sogithub-annotationsandazure-loggingnever appear infallback. See When prxref cannot post;metadata_rules: the deterministic PR-metadata checks' tally (#70). Always present, andnullwhilePRXREF_METADATA_RULESisoff; when it isonor names a rules file,{branch_pattern, commit_reference, area_globs, violations}, each checkpass,failorskipped: <reason>, andviolationsone{check, title, detail}row per violation (violations are summary notes, neverfindingsrows). See docs/env-vars.md;config_file: the repository config file the run read (#38). Always present, andnullwhen no file was read; otherwise{"path", "sha256", "keys"}: the file as its errors name it (relative to the working directory when inside it), the sha256 of its bytes, and the sorted keys it sets, including any that the environment or a flag then overrode. With-vin text mode, one log lineconfig: <path> (<n> keys)says the same. See docs/config-file.md;sampling: thetemperature,seed, andmodelsthe run had in force (every review result carries it);replay: the replay stamp (base_sha,head_sha,threads,diff_file,description,as_of,as_of_source), on replay runs only.
Replay flags, for evaluation (see Replay Mode (Evaluation)). Any of them turns posting off for the run:
--base-sha SHA/--head-sha SHA— review the pinned rangeBASE...HEADof the--pr-urlrepository (the merge-base diff, as the PR's own diff is), with file context read atHEAD. The two come as a pair, must be full 40- or 64-character hex commit SHAs, must differ, and need--pr-url.--no-threads— hide the PR's existing threads from the prompt and from the thread-dedup passes.--diff-file PATH— review this unified diff (git difforgit format-patchoutput) instead of fetching one;--pr-urlbecomes optional.--as-of TIME— show the reviewer the PR's title and description as they were atTIME: an ISO-8601 time with a UTC offset, such as2026-05-01T09:30:00Zor2026-05-01T11:30:00+02:00. A date alone or a time without an offset exits2rather than being read in the local time zone, and so does a value that is not ISO-8601. Needs--pr-url, and a forge that can read description history (GitHub or Bitbucket Cloud); on any other forge it exits2. Without it, a--pr-urlreplay pins the title and description to the PR's first human review, else to its head commit's date.--description-file PATH— use this file's text as the PR description, with or without--pr-url. It is read like--rules-file: a missing, unreadable or non-regular file, a file that is not UTF-8 or contains NUL bytes, and a path under the working directory that symlinks out of it each exit2. A blank file is an empty description.--no-description— review with an empty PR description.
--as-of, --description-file and --no-description are mutually exclusive: giving two or more exits 2 naming each one given. They are CLI-only, with no environment variable.
The other subcommands: prxref serve [--port N] [--host H] [--config PATH] runs the webhook server (default port 8080, default host 0.0.0.0); prxref trace render FILE [-o OUT] renders a JSONL run trace (PRXREF_TRACE_FILE) to a standalone HTML pipeline view, written next to the trace unless -o/--out names the output; and prxref --version prints the version.
prxref serve --config PATH names a repository config file for every webhook review. The daemon never auto-discovers .prxref.toml, because its working directory is not the repository it reviews: it reads a file only when --config or PRXREF_CONFIG_FILE names one (off reads none), checks it before listening (a missing or invalid file exits 2), and re-reads it on every webhook. serve has no --no-config; leave both unset, or give --config off.
prxref config check [--config PATH | --no-config] [--format {text,json}] checks the repository config file and the environment without reading a pull request or calling a model. It resolves the file exactly as review does, then prints config file: <path> (or config file: none), one <key> = <value> (<source>) line per setting, sorted, whose source is default, file or env PRXREF_<NAME>, and ok. Credentials and webhook secrets print only as <set> or <unset>. --format json prints one object, {"config_file": <path or null>, "values": {"<key>": {"value", "source"}}}. It exits 0 when the configuration is valid, and 2 with the configuration error: ... line a review would print when it is not; with --format json, an error leaves stdout empty. A bare prxref config prints the top-level help and exits 2.
prxref prompts export DIR [--force] writes the packaged worker.md, systemic.md and summary.md prompt templates into DIR, byte for byte, as the starting point for a PRXREF_PROMPTS_DIR override directory, and prints each path it wrote. It creates DIR when it is missing. When any of the three files already exists it overwrites nothing, writes nothing, and exits 2 naming the file; --force overwrites them. The judge prompt of prxref eval is never exported, because it cannot be overridden. How to edit and use the exported templates: Prompt Template Overrides.
prxref eval scores replays against labelled human findings. It never posts, and it adds no environment variable. Its three actions:
prxref eval run --cases PATH --label NAME [--out DIR] [--rules-file PATH] [--scoped-rules PATH] [--prompts-dir DIR] [--resume] [--config PATH | --no-config]replays every case and writes the run toDIR/NAME/:--cases PATH— the labelled cases: acases.jsonfile, or a directory ofcase-*/directories. Required. A bad case exits2, naming--cases, the case id, and the field.--label NAME— the run's name and its directory under--out. Required. An existing label exits2unless--resumeis given.--out DIR— the directory that holds the runs (default./prxref-eval/).scoreandcomparetake it too.--rules-file PATH— team review rules for every case, as forreview: it overridesPRXREF_REVIEW_RULES, and--rules-file ""turns it off.--scoped-rules PATH— path-scoped rules for every case, as forreview: it overridesPRXREF_SCOPED_RULES, and--scoped-rules ""turns it off. Repeatable.--prompts-dir DIR— prompt templates for every case, as forreview: it overridesPRXREF_PROMPTS_DIR, and--prompts-dir ""turns it off.- Each of the three is checked once, before the first case runs: a file or directory that fails its checks exits
2, naming the flag, or the variable when the flag is not given. The run'srun.jsonandscore.jsonrecord the scoped rules in force, asreviewrecords them. --resume— continue an existing--labelrun instead of refusing it.--config PATH/--no-config— the repository config file every case reviews with, as forreview: without either,PRXREF_CONFIG_FILE, else.prxref.tomlin the working directory. The file is resolved and checked once, before the first case runs; a missing or invalid one exits2. The run'srun.jsonconfigreflects it.
prxref eval score --label NAME [--judge-model MODEL] [--out DIR]grades the run against its labels and writesscore.jsonandscore.md:--judge-model MODEL— the model that grades every label without amust_matchpredicate, on the review's own LLM backend. Required when any label lacks one; leaving it out then exits2. A judge model the review itself used logs a warning.
prxref eval compare A B [--out DIR]prints two scored runs side by side, then every label whose credit changed and the cases and labels only one run has.AandBare each a label under--outor a run directory. It warns when the runs are not like for like.
The whole reference, from the case format to every score.json key: docs/evals.md.
Replay Mode (Evaluation)
A replay reviews a pinned, reproducible input instead of a PR as it stands, so one change can be reviewed again later, by another model or another prxref build, and compared. Three invocations cover it:
# A blind replay of a PR at two pinned commits, without its existing discussion
prxref review --pr-url https://github.com/acme/widgets/pull/42 \
--base-sha 0123456789abcdef0123456789abcdef01234567 \
--head-sha 89abcdef0123456789abcdef0123456789abcdef \
--no-threads --format json
# A diff on disk with its ticket and spec corpus; no PR and no forge at all
prxref review --diff-file change.diff --context-file TICKET.md --spec docs/specs --format json
# One eval case (see tests/evals/README.md)
prxref review --diff-file tests/evals/<case>/diff.patch --context-file tests/evals/<case>/ticket.md \
--spec tests/evals/<case>/docs --no-post --format json
-
A replay never posts. Any replay flag turns posting off for the run, with or without
--no-post; when nothing else had already turned it off, the run logsreplay run: posting to the forge is disabled. The replay forges also refuse every write, and a replay never prunes older comments. -
Pinned SHAs:
--base-shaand--head-shacome as a pair, must be full 40- or 64-character hex commit SHAs (resolve a short one withgit rev-parse), are lowercased, must name two different commits, and need--pr-url. The review reads the merge-base diffBASE...HEADand file context atHEAD; the endpoint each forge uses is under "Pinned Commit Range (Replay)" in docs/forges.md. -
--diff-file PATHreviews that file (git difforgit format-patchoutput) instead of fetching a diff. Without--pr-urlnothing is contacted: there are no threads and no file context unless--repo-dirnames a checkout to read it from, and agit format-patchfile supplies the title, description and author. With--pr-urlthe file replaces the PR's diff, and without--head-shaa warning says that file context is still read at the PR's current head. -
The title and description are pinned too. A
--pr-urlreplay shows the PR's title and description as they were at a cutoff, not as they are now, because a description edited after review (a table of the review fixes, say) tells the model what the reviewers found. The cutoff is the first of these that exists:--as-of TIME, when you give it;- the PR's first human review or comment: not by the PR's author, not by a bot account, and not one of prxref's own posts;
- the date of the head commit (
--head-shawhen given, else the PR's current head).
The history is read once, before the review starts, by one call to the forge's history reader; that network read is not recorded in the run trace. GitHub and Bitbucket Cloud can read it, and GitHub needs a token to (see "Description History (Replay)" in docs/forges.md). The title is pinned exactly when the description is. A cutoff before the PR was opened shows the original description.
-
Falling back to the current text. When the history cannot pin them, the replay keeps the PR's current title and description, stamps
"description": "live", and logs a WARNING that startsreplay shows the PR's CURRENT title and descriptionand gives the reason: the forge cannot read description history (Bitbucket Server, GitLab, Gitea/Forgejo and Azure DevOps cannot), the read failed (no GitHub token, a 401 or 403, a transport error), the history has neither a first review nor a head commit date, or it does not reach the cutoff (it is incomplete, or the version then in force was deleted). None of these stops the review. An explicit--as-ofon a forge that cannot read history is the exception: it exits2naming--as-of, because the time you asked for cannot be honoured. -
--description-file PATHand--no-descriptionreplace the description with that file's text or with nothing, read no history, and leave the title as it is now. Both also work without--pr-url, replacing the description agit format-patchfile supplies. At most one of--as-of,--description-fileand--no-descriptionmay be given. -
What a pinned replay does not pin. The PR's current threads still reach the prompt unless you add
--no-threads; a replay at pinned SHAs without--no-threadslogs a warning saying so. With--pr-url,--diff-fileand no--head-sha, file context is read at the PR's current head, with a warning. The title stays the current one under--description-fileand--no-description, and after a fall back to the current text. -
The record. A replay's JSON record gains a
replaystamp, always with all seven keys, and the text summary prints it as areplay:line. A normal run's record has noreplaykey."replay": {"base_sha": null, "head_sha": null, "threads": "hidden", "diff_file": "change.diff", "description": "file", "as_of": null, "as_of_source": null}
threadsis"hidden"under--no-threadsor with no--pr-url, else"shown";diff_fileis the path as you typed it.descriptionis"pinned"(the title and description in force at the cutoff),"live"(the current ones),"file"(--description-file, or--diff-filewithout--pr-url, where any description comes from the file) or"none"(--no-description).as_ofis the cutoff as a UTC time ending inZ, with a fraction of a second only when the source time had one, andas_of_sourceis"flag","first-review"or"head-commit". Both are set whenever a cutoff was chosen, on a"live"stamp too: one whose history did not reach the cutoff, or whose history read failed after--as-ofhad chosen it. Both arenullwhen no cutoff was chosen. Given back as--as-of,as_ofnames the same instant. The text line then endsdescription=pinned as_of=2026-05-01T09:30:00Z (first-review). -
Exit codes. A bad set of replay flags exits
2naming the flag, and it is checked before the PR URL is parsed. A review error inside a replay — an empty pinned range (a head already merged into the base) or a blank diff file — ends the run as anErrorrun: it exits0under the defaultPRXREF_FAIL_ON=never, and1undererrororany, like any review that does not complete. See Exit Codes. -
The replay flags have no environment variable, on purpose, and the webhook server never replays.
Evaluating prxref
prxref eval turns replays into numbers. Give it pull requests that human reviewers have already reviewed, each labelled with the findings they left, and it replays every case through the real pipeline, grades prxref's findings against the labels, and compares two runs, so a new model, prompt template, rules file or setting can be judged across a dataset rather than on one PR:
prxref eval run --cases tests/evals --label base
PRXREF_LLM_MODELS=example-model-b prxref eval run --cases tests/evals --label cand
prxref eval score --label base
prxref eval score --label cand
prxref eval compare base cand
- Cases come as a
cases.jsonfile or as a directory ofcase-*/directories, the layout oftests/evals/. A label is graded deterministically when it carries amust_matchpredicate, and by an LLM judge on the review's own backend (--judge-model) when it does not. - Recall is micro recall over every label, with half credit for a
partialjudge grade, broken down by severity and by category. The score also reports unmatched AI findings per PR, severity agreement, failed chunks, time and cost, and never sums an unknown cost. - Runs go to
./prxref-eval/<label>/by default; addprxref-eval/to your.gitignore. A run never posts, and a case that fails is recorded and scored, never fatal.
The full reference is docs/evals.md.
Exit Codes
prxref is an advisor, never a gate. Its exit code says whether prxref was configured correctly, not whether your code is good.
| Code | Meaning |
|---|---|
0 |
The run finished — including every review error: a network failure, an LLM timeout, bad forge credentials, an unrecognized URL, or a review in which every chunk failed. Diagnostics go to stderr; the pipeline step stays green. With PRXREF_FAIL_ON set to error or any (see below), only two outcomes turn this into 1: a completed review whose active findings trip the policy, and a review that does not complete — it crashes, or it ends with verdict Error (the forge could not be read, the diff could not be parsed or chunked, or every chunk review failed) or verdict Incomplete (some chunk reviews failed, so the review only partially happened; the verdict ladder is Error → Request-Changes → Incomplete → Approved). An empty PR diff is not a failure (verdict Approved, exit 0), and an unrecognized URL stays 0 because nothing was reviewed. |
1 |
Gated review outcome — only when PRXREF_FAIL_ON is set: error exits 1 when the completed review carries an active error-severity finding, any exits 1 on any active finding, and under either value a review that does not complete also exits 1 — it crashes, or it ends with verdict Error (the forge could not be read, the diff could not be parsed or chunked, or every chunk review failed) or verdict Incomplete (some chunk reviews failed). An empty PR diff is not a failure (verdict Approved, exit 0). The reason is printed to stderr. |
2 |
Usage or configuration error — no subcommand, invalid command-line arguments, or a required value missing, malformed, outside its valid range, or outside its key's allowed vocabulary (PRXREF_FAIL_ON accepts only never, error, any). The message names the source that supplied it: the environment variable, or the CLI flag when a flag is what you typed. |
$ prxref review --pr-url https://github.com/org/repo/pull/1 --max-chunks 0
configuration error: --max-chunks: must be a finite number greater than 0, got 0
Replay mode keeps the same split. A bad set of replay flags exits 2 naming the flag: neither --pr-url nor --diff-file, a lone --base-sha or --head-sha, a SHA that is not full 40- or 64-character hex, two equal SHAs, SHAs without --pr-url, an unreadable --diff-file, two or more of --as-of, --description-file and --no-description, an --as-of that is not an ISO-8601 time with a UTC offset (a date alone included), --as-of without --pr-url, or an unreadable --description-file. These are checked before the PR URL is parsed, so they exit 2 even next to an unrecognized URL; pinned SHAs on a forge that cannot fetch a commit range also exit 2, once the forge is known, and so does --as-of on a forge that cannot read description history (Bitbucket Server, GitLab, Gitea/Forgejo, Azure DevOps). A description history that cannot be read is not an error: the replay keeps the current title and description and logs a warning. An empty pinned range or a blank diff file is a review error, unlike an empty PR diff: the run ends as an Error run, which exits 0 under the default PRXREF_FAIL_ON=never and 1 under error or any.
PRXREF_FAIL_ON is the one opt-out of the advisory contract, and its default never is the doctrine above, unchanged. Setting it to error or any turns the reviewer into a merge gate — failing a build on a finding turns a probabilistic reviewer into a gate, and the first false positive teaches a team to bypass the gate, so think hard before you set it. Read the verdict from the posted summary, which also carries a partial-review banner when some chunks did not make it. Do not build a security control on the exit code. The webhook daemon has no exit code and is unaffected.
Metadata
Release files for prxref 0.30.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| prxref-0.30.1.tar.gz | 1.8 MB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| prxref-0.30.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.5 MB
Release files / prxref-0.30.1.tar.gz
| Download URL | prxref-0.30.1.tar.gz |
|---|---|
| Size | 1.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
40d094672d5caffda41ba44eba7ac9373a0cc16fc43a8a65e6a284bc9783c5ed
|
|
BLAKE2b-256 checksum How to use checksums |
6d729a64113ff7cce63e0bdd2bcd5b68baaefd81f3feef46ae57f35e04437ab4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / prxref-0.30.1-py3-none-any.whl
| Download URL | prxref-0.30.1-py3-none-any.whl |
|---|---|
| Size | 637.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
864dbc200a2fe5eda60a0c51ae06071701b75b183d394e2741f9399e6709cca0
|
|
BLAKE2b-256 checksum How to use checksums |
3c5871098950eb169962b988b18772f105aec7a7e51f4a832772321c0e6aa492
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logSecurity for prxref 0.30.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for prxref-0.30.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.