Risk Scoring & Auto-Approve
Every review reports three risk numbers and a verdict:
| Field | Meaning |
|---|---|
base_risk_score |
Computed from the diff alone, so it’s deterministic and repeatable |
model_risk_score |
The highest risk_score the model gave any chunk |
risk_score |
max(base, model): the model can raise the risk, never lower it |
auto_approve |
true only when every rule below holds |
risk_files |
Per-file breakdown of the base score |
The base score
Section titled “The base score”Each changed file contributes:
points = weight × (changed lines + 5 × max(0, decision points added − removed))Decision points are counted on each changed line:
| Language | Extensions | Decision points |
|---|---|---|
| Go | .go |
if for case && || (exact, from Go’s scanner) |
| C-family | .c .h .cc .cpp .cxx .hpp .cs .java .js .jsx .mjs .cjs .ts .tsx .mts .cts .kt .kts .swift .rs .php .scala .dart .groovy |
if for foreach while case catch guard && || |
| Python | .py .pyi |
if elif for while except and or, and case at the start of a line |
| Ruby | .rb |
if elsif unless while until for when rescue and or && || |
Outside Go, the count is an estimate. It drops string literals and comments
and counts whole words only, so iffy and orders don’t count. It works
line by line, though, so a keyword inside a docstring or comment that spans
lines can count. The ternary ?:, ?? and pattern-match arms aren’t counted.
Every other file type counts 0.
New branching counts five times as much as straight-line code, and removing complexity never lowers the score. A changed binary file adds 10 points. The total becomes a score with:
score = round(100 × (1 − e^(−total / scale)))The score rises quickly for the first changes and never exceeds 100.
Path weights
Section titled “Path weights”Weights come from #RiskPolicy in schema/review.cue. The first matching
pattern wins, so tests and docs are listed before the high-risk directories:
| Pattern | Weight |
|---|---|
*_test.go |
0.3 |
**/testdata/** |
0.2 |
**/docs/**, *.md |
0.1 |
**/auth/**, **/security/**, **/crypto/** |
3 |
**/schema/**, **/migrations/** |
2.5 |
anything else (default_weight) |
1 |
When a change is auto-approved
Section titled “When a change is auto-approved”All of these must hold:
- At least one chunk was reviewed. A lockfile-only change needs a human.
- No hunk was skipped as oversized.
- No touched file is sensitive, meaning its weight is at least
sensitive_weight(2.5). A one-line auth fix still needs a human. risk_score ≤ auto_approve_threshold(20).- No
CriticalorMajorfindings.
Your own policy
Section titled “Your own policy”Pass a JSON document validated against #RiskPolicy. Omitted fields keep
their defaults:
{ "auto_approve_threshold": 10, "weights": [ { "pattern": "*_test.go", "weight": 0.3 }, { "pattern": "**/billing/**", "weight": 4 }, { "pattern": "**/auth/**", "weight": 3 } ]}pr-review --mode review --risk-policy risk.json --format jsonFrom Go, set reviewer.Config{RiskPolicy: data}. An invalid policy, such as
a weight above 10, a zero scale or a malformed glob, is rejected when the
engine is created.
A repository keeps its own policy at .vizzini/risk.json. That’s the
conventional location: pass it with --risk-policy .vizzini/risk.json in CI,
and embedders such as mrman look for it there.
For display, a score falls in one of three bands:
| Band | Scores (defaults) |
|---|---|
low |
at or below auto_approve_threshold (20) |
medium |
between the two thresholds |
high |
at or above high_threshold (60) |
high_threshold must stay above auto_approve_threshold. If your policy
raises auto_approve_threshold to 60 or more, set high_threshold too, or
the policy is rejected.
Without a model
Section titled “Without a model”The base score needs only the diff and the policy, so you can compute it without a proxy, a model or a tokenizer:
git diff main...HEAD | pr-review --mode riskgit diff main...HEAD | pr-review --mode risk --risk-policy .vizzini/risk.json --format jsonThe JSON is a RiskAssessment report: {apiVersion, kind, score, band, files[]}. Its score and files equal the base_risk_score and
risk_files that a review of the same diff would report.
From Go, use pkg/risk. It’s a dependency leaf: it never links the LLM
client, the tokenizer or the knowledge-base loader, so a program can embed it
just to score changes. See Embedding in Go.