get_change_risk
Pre-merge defect risk for a whole commit or diff range, scored from the shape of the live diff, plus the tests that actually cover the changed lines and whether those files have been bug-fixed before.
Before merging, your agent should know whether this particular change
is a routine one or an unusual one for this repo. get_change_risk
scores a commit or a base..head range from the shape of its diff:
how many lines, how many files, how scattered across directories, and
how experienced the author is. It reads the live checkout, so it needs
no index refresh and works on a branch you indexed an hour ago.
It is deliberately different from get_risk,
which scores indexed files by path and can report blast radius.
get_change_risk scores the change. That means it catches risky
small diffs that a file-level view misses.
No LLM, no network. It is pure git plus learned constants, so the
same diff always scores the same.
When to call
- Before merging a commit or PR range, as a triage signal.
- On a branch you want to compare against the repo's own norm.
- When the diff matters more than the files, for example a three-line change spread across six subsystems.
Parameters
Prop
Type
Returns
Read the top three fields first. They are repo-relative and they are what you triage on.
| Field | Meaning |
|---|---|
review_priority | Below typical, Typical, or Elevated, from terciles of this repo's own commit-risk distribution |
risk_percentile | "Riskier than N% of this repo's commits" |
classification | Short label summarising the change shape |
baseline_sample_size | How many filtered commits informed the percentile |
score, probability, level | The corpus-calibrated fallback, secondary context only |
features | The raw diff metrics behind the score |
drivers | Signed per-feature contributions, so the result is auditable |
exclude_patterns | The combined filters actually applied |
Lead with risk_percentile and review_priority, not score. The
raw 0 to 10 is anchored to an offline calibration corpus, so on a repo
whose typical commit is large, the diff-size term dominates and the
absolute band skews high. Two-thirds of commits can read "high" while
ranking perfectly normally for that repo. The ranking is sound, the
absolute band is not portable.
Note that drivers are reported relative to the model's baseline
commit (the calibration-corpus mean), not to your repo. So a small
change can legitimately read "more lines added than baseline" while
still ranking Below typical for a repo of large commits.
What it measures
The model uses Kamei-style change metrics over the diff:
| Feature | Meaning |
|---|---|
la, ld | Lines added / deleted |
nf | Files touched |
nd, ns | Distinct directories / top-level subsystems touched |
entropy | Shannon entropy of the per-file churn distribution (how diffuse the change is) |
exp | Author's prior commit count. Unknown is scored neutrally |
The score is a plain L2-logistic over standardized, log-compressed features, so every feature's push on the result is exact rather than inferred.
Impacted tests
impacted_tests lists the tests that the per-test coverage map proves
execute the change's changed lines. That is line-precise, so it is a
narrower set than get_risk's file-level tests_to_run. It is capped
at ten, with total and truncated reporting overflow.
missing_tests buckets the rest: untested_changes (covered file but
uncovered change), stale_test_candidates (covered lines whose
guarding test file is not in the diff), covered, and
no_coverage_data.
When no coverage map has been ingested, status is no_map and the
change is reported as unknown, never as untested. Empty means "run
the full suite", not "there are no tests". Build the map with
coverage run --contexts=test followed by repowise coverage add.
Prior fixes
When the changed files carry counted bug fixes, the response includes
prior_fixes: per file, how many past bug-fix commits touched it
(fix_count), how many of the change's lines fall inside ranges one
of those fixes replaced (overlapping_lines), and how long ago the
most recent was (last_fix_days_ago). total_fixes counts distinct
commits, not rows. The block is absent entirely on an index with no
fix history.
overlapping_lines is labelled approximate in the payload, and that
label carries weight: a past fix's ranges are numbered against its own
parent commit, so anything that moved lines in between shifts them.
Read it as "this neighbourhood has been patched before", not "this
exact line". The fix_count beside it has no such caveat.
The block is aggregate and never names the commit that introduced a bug. File-level SZZ attribution measured 74.5% precision on this repo's frozen judgments, which is enough to count fixes and not enough to accuse a commit of causing them.
Example
get_change_risk()
get_change_risk(
revspec="main..HEAD",
extensions=[".py"],
exclude_patterns=["tests/"],
)Things worth knowing
- Filters apply to both sides.
extensionsandexclude_patternsfilter the change and the recent commits sampled for its percentile, so the comparison stays like for like. - Project-wide rules belong in a repository-root
.riskignore. Those patterns apply automatically and combine with anything passed in the call. - An empty diff produces a
warningfield, which usually means a bad revspec or over-tight extension and exclusion filters. baseline: 0disables percentile ranking, which leaves only the corpus-anchored score. Avoid it unless the repo is too young to have a distribution.- No cross-repo fields. This tool is pure diff shape. Blast radius
and co-change partners belong to
get_risk.
The same scoring is available from the shell as
repowise risk.
get_risk
Modification-risk assessment for files before editing, covering hotspot scores, dependents, co-change partners, blast radius, recommended reviewers, test gaps, and security signals.
get_why
Architectural-decision archaeology. Search decisions in natural language, anchor them to a file path, or pull the global decision-health dashboard. Falls back to git archaeology when no recorded decisions exist.