get_change_risk reviews one commit, a base..head range, or the
uncommitted working tree. It analyses both revisions from their own
content and reports what the change newly made worse, which tests
execute the changed lines, and how often the touched files have been
bug-fixed before. 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 reviews the change. The scoring model is
Change risk, and the covering tests come
from Test intelligence. The other
tools are on MCP tools.
No LLM calls. The comparison is git plus the same deterministic
health markers the Code Health layer
runs, so the same diff reviews the same way twice.
When to call
- Before merging a commit or PR range.
- On uncommitted work, by omitting
revspecentirely. - When the diff matters more than the files, for example a three-line change spread across six subsystems.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
revspec | string | — | Commit, base..head range, or base...head (from the merge-base: what a pull request changed). Omit it to review uncommitted work, or pass HEAD when the tree is clean. |
repo | string | — | Repository alias (workspace mode only). Usually omitted. |
extensions | string[] | — | File suffixes to count, for example [".py", ".ts"]. |
exclude_patterns | string[] | — | Gitignore-style paths to omit. Combined with any rules in a repository-root .riskignore. |
baseline | number | 200 | Recent commits to sample for percentile ranking. Set 0 to disable every percentile, risk_percentile and fix_history.percentile alike. |
include | string[] | — | "findings" lifts the top_findings cap and returns every change finding. "diagnostics" adds the raw score mechanics. "scales" adds per-field units and calibration. "evidence" adds the evidence checklist of the change, the same record repowise changes show prints. "tests" adds why each listed test runs and the evidence per changed file. |
finding_id | string | — | Expand one health_delta finding by its id. Ids are bound to the two revisions that produced them. |
Returns
The response leads with directive, then health_delta. Those two are
the review. Everything after them is supporting evidence. The default keeps
every fact an agent acts on and leaves raw mechanics to include.
directive
| Field | Meaning |
|---|---|
status | review_required, review_recommended, clear_in_analyzed_scope, or unknown |
headline | One line saying what the change did |
reasons | Up to three reasons behind the status. Findings that gate the verdict (high or critical, not performance) lead the reasons, the inspect actions and top_findings, so the headline's finding comes first in each. The headline ends with the changed code's health on both sides |
next_actions | Concrete steps: which finding to inspect, which tests to run, which skipped files to look at |
clear_in_analyzed_scope is scoped on purpose. It says the files that
were compared came back clean, not that the change is safe.
health_delta
What the change newly made worse across defect, maintainability and performance. Both revisions are analysed from their own content, so a finding present at head is reported only when the diff explains it.
| Field | Meaning |
|---|---|
status | available, partial, or unavailable. A partial comparison is never a clean bill |
scope | Changed, eligible, analysed, skipped and failed file counts |
skipped | Why files were left out, counted by reason |
introduced, worsened, resolved | Totals |
by_dimension, by_severity | Counts over the emitted findings |
top_findings | The three most actionable, with findings_total and the exact call that returns the rest |
files | Up to 10 analysed files, largest structure move first, each with base_score, head_score, nloc_base and nloc_head; a side the file is missing on reads null. files_total counts them all |
score_change | The average over files scored on both sides, weighted by base nloc: base, head, delta, structure_delta, files_compared |
base, head, analyzer | With include=["diagnostics"]: the two revisions compared, and the analyzer fingerprint |
Each finding names its dimension, biomarker, severity, path,
symbol and head-side lines, a reason, and an attribution. That
attribution has a basis (added_lines, changed_symbol,
changed_call_edge, new_file, file_change, context_change,
unknown) and a confidence. Finding identity ignores line numbers, so
moving code introduces nothing and a rename carries its findings across.
Findings pair across code a change moves between files, so a pure split
reads as moved, and a deleted file's row names moved_to. Each score is the
structure-only reading (score_basis: "structure_only"), the one
get_health(counts="code_shape") gives.
Performance findings carry opportunity_id and opportunity_rank and
are ordered by opportunity rank and actionability, never by defect
impact, which is zero for them by construction.
inspect on a row gives the exact finding_id call that expands it. A
finding that exactly matches a stored one also carries a
health_reference for get_health(finding_id=...).
Trust health_delta.status. partial means files were skipped and the
change is not cleared, and skipped says why each one was left out.
fix_history
fix_history is the recency-weighted bug-fix record of the files this
change touches: a density, a percentile ranking it against the same
measure over the repo's own recent commits, and files naming where the
pressure sits. It separates a small edit to a fragile file from a large
edit to a safe one. available is false when the history walk could not
run.
fix_history.overlap is the indexed, filtered view and appears when the changed
files carry counted bug 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, and files is capped at ten. Each file also
carries changed_lines and share_of_change, so when one file holds at
least half the changed lines, concentration names it.
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.
impacted_tests
tests_to_run names the tests 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.
line_coverage 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.
With no coverage map ingested, the change is never reported as untested.
status becomes inferred when the import graph can name test files
reaching the change (candidates, file-level, no line attribution, so
line_coverage stays empty), and no_map when it cannot. basis
carries the same distinction in one word: measured, inferred, or
absent. Build the measured map with pytest --cov=<package> --cov-context=test
(needs pytest-cov) followed by repowise coverage add .coverage.
patch_coverage
The share of the changed executable lines that stored coverage ran, the
same figure repowise coverage check
gates on. With no revspec it covers everything a push would bring:
committed work since the merge-base with the branch CI compares against,
plus uncommitted and untracked files. Coverage older than the change is
marked stale.
Each file row carries its risk, and each uncovered range carries hints:
up to three test files to extend, with a basis of per_test (measured),
call_graph or import_graph (inferred), or none (the range needs a new
test). When changed lines are uncovered, directive.next_actions says
which tests to extend, or to re-run the tests with coverage first when the
coverage is stale.
Diff shape and ranking
Top-level fields: risk_percentile ("riskier than N% of this repo's
commits"), review_priority (Below typical, Typical or Elevated,
from terciles of this repo's own commits), classification, is_fix, and
diff_shape, one sentence on the change's size and spread.
The ranking measures diff size and spread. It is not a probability, and it
is not where the change lands. Lead the review with directive and
health_delta. The raw 0 to 10 score is behind include=["diagnostics"].
independent_changes says when the diff is several
unrelated changes instead of one. It groups the changed files by
connectivity over index edges, stored co-change pairs, and, for a
base..head range, the files each commit of the range touched. count
is the number of groups; each group lists its files and its
bridging_files, the files that alone hold the group together.
ungrouped_files carries what was left out and summary names why:
docs, config, tests, files not in the index, or files it has never
linked. Only an indexed file that is not a test and is written in a
language whose resolver can emit an import edge is eligible to be
grouped. basis states in words what was actually checked, and that
sentence changes with the subject, since a single commit and uncommitted
work carry no commit evidence to read. The block is absent without an
index and whenever the diff is one change.
branch_overlap
Other open branches editing the files this change edits. Git alone
answers it, so it appears whether or not the repo is indexed; an index
only orders the shared files and adds the history rows. base and
current name the two ends. Each branches[] entry carries the branch
name, ahead and behind commit counts, last_commit, and files[],
where every file row states its basis in words: either same file or
co-change pair, N of M commits. There is no score and no percentage in
the block. The scan is bounded to the newest 50 branches by committer
date and to 20 seconds, and the block is absent when no other branch
overlaps.
cross_repo (workspace mode)
Built from the artifacts of the last repowise update --workspace, not
from a graph traversal. It appears when the commit touches a file that
provides a contract another repo consumes, or when the compatibility
report attributes a finding to one of the changed files.
consumers[]: each link with itsprovider_file, the consumer'srepo,fileandcontract_id, pluscontract_typeand thematch_typethat joined them. Capped at ten.consumer_reposlists the other repos in one place.- Every consumer row carries a
testsblock: astate(measured,inferred,noneorunresolved), up to fivetests_to_runrows, andunresolved_reason/unresolved_detailwhen the join could not be followed. breaking_changes[]: thecontract_id,type,kind,severity,detail,provider_fileandimpacted_reposof each incompatibility or comparison warning. Capped at five.breaking_changes_availablesays whether a detection pass ran at all, so an empty list reads as silence instead of as an all-clear.breaking_changes_as_ofstamps that half only.
Impacted repositories are directly linked endpoint exposure, not proof
of a runtime failure. The block answers whether this commit crosses a
repo boundary; get_blast_radius is the tool for the full traversal.
include=["evidence"]
Adds evidence, the change's evidence checklist: components touched,
declared owners, suggested reviewers, reaching tests (measured or
inferred), contracts and consumers in other repositories, decisions
crossed, the health delta, AI provenance with coverage, and the diff
size rank. Each item carries a status (ok, attention or unknown),
the basis it rests on and its source. It is the same record
repowise changes show prints and REST serves.
get_change_risk(revspec="main..HEAD", include=["evidence"])include=["diagnostics"]
Adds score, fallback_band, risk_authority, score_measures,
score_unit, baseline_sample_size, features and drivers. 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 instead of
inferred. Note that drivers are reported relative to the model's
baseline commit (the calibration-corpus mean), not to your repo. A
small change can legitimately read "more lines added than baseline"
while still ranking Below typical for a repo of large commits.
Example
get_change_risk()
get_change_risk(
revspec="main..HEAD",
extensions=[".py"],
exclude_patterns=["tests/"],
)
get_change_risk(revspec="main..HEAD", include=["findings"])
get_change_risk(revspec="main..HEAD", finding_id="chf_27a13be11e7ee33f")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 (underinclude=["diagnostics"]). Avoid it unless the repo is too young to have a distribution.is_fixis the defect benchmark's keyword rule read over the commit subject, not the conventional-commit type, so afeat:commit whose subject says it fixes something reads true. The rule is frozen for comparability instead of tuned.- Blast radius belongs to
get_risk. This tool compares two revisions and does not walk the import graph outward from them.
The same scoring is available from the shell as
repowise risk.