# get_risk

get_risk assesses modification risk before an edit: hotspots, dependents, co-change partners, blast radius, suggested reviewers, test gaps and security.

Before changing a file, your agent should know what it's walking
into. `get_risk` rolls up churn, complexity, dependents, hidden
coupling (co-changes), ownership, test coverage, and security signals
into a single per-file profile, plus an optional PR-style blast-radius
analysis when you pass a `changed_files` list. The inputs are
[git history](https://docs.repowise.dev/intelligence/git-history), [bug-fix history](https://docs.repowise.dev/intelligence/bug-history)
and [security signals](https://docs.repowise.dev/intelligence/security-signals); the other tools
are on [MCP tools](https://docs.repowise.dev/mcp/overview).

## When to call

- **Before editing** any file the agent doesn't already understand.
- **PR review**: pass `changed_files` to get the transitive impact
  surface, missing co-change partners, and recommended reviewers.
- **Architectural planning**: surface bus-factor risks and coupling
  hotspots ahead of time.

## Parameters

| Name | Type | Default | Description |
|---|---|---|---|
| `targets` | string[] |  | File paths to assess. Defaults to changed_files. |
| `repo` | string |  | Repository alias. Usually omitted: in a workspace, targets route to the repo that owns them. One repo per call; "all" is not supported. |
| `changed_files` | string[] |  | When provided, switches the response into PR mode: it leads with a directive (may_break, missing_cochanges, missing_tests, tests_to_run, tests_to_update), and adds co-change warnings and recommended reviewers. Works without targets. |
| `include` | string[] |  | Opt-in blocks. "graph" adds dependents, consumers, cross_repo_links, impact_surface, relationship_analysis, and (in PR mode) direct_risks. "churn" adds change_magnitude, risk_type, and change_pattern. "owners" adds detailed owner metrics. "tests" adds typed test rows. "blast" adds pr_blast_radius. "changes" lists the recent Change Records of each file. "scales" adds the per-field units and calibration, which never vary between calls, so ask for them once per session. |

## Returns

`targets` is keyed by file path. Each entry contains:

| Field | Meaning |
|---|---|
| `hotspot_score` | 0 to 1 churn percentile (higher = changed more often) |
| `health_score` | The stored [code-health](https://docs.repowise.dev/intelligence/code-health) score, 0 to 10. Absent when the file has no stored health row |
| `trend` | `"increasing"`, `"decreasing"`, or `"stable"` (30d vs 60d rate) |
| `dependents_count` | Direct directed dependents: files the graph records as depending on this one. A floor over the indexed graph, not a proof of completeness |
| `co_change_partners` | Top 5 historical co-changers (see below) |
| `primary_owner`, `owner_pct` | Historical owner |
| `recent_owner`, `recent_owner_pct` | Active maintainer if different |
| `bus_factor` | How many people could disappear before the file becomes unowned |
| `contributor_count` | Total contributors |
| `coverage_pct`, `branch_coverage_pct` | From ingested coverage data. Absent when none was ingested |
| `top_biomarkers` | Up to 3 open health findings, highest impact first |
| `defect_profile` | *(only on files with counted bug fixes)* `fix_count`, `last_fix_days_ago`, `bug_magnet` flag, and up to 3 `top_symbols` most often touched by those fixes |
| `test_gap` | `true` if coverage data doesn't prove a test exercises this file, and no test file matches its basename |
| `security_signals` | `kind`, `severity`, `snippet` from static analysis |
| `episodes` | *(only when at least one exists)* count of dated episode records bound to this target; `get_why` serves the bodies |
| `risk_summary` | One-line human summary |

A target that does not resolve to an indexed file returns only `target`,
`resolved: false`, an `unresolved_reason` (`directory`, `not_indexed`,
`no_such_path` or `unsupported_target_kind`) and a `risk_summary`, never a
card of zeros. Pass files, not directories. In PR mode an unresolved
changed file is left out of the enrichment.

These fields ship only when `include` asks for them:

| Field | Gated behind |
|---|---|
| `dependents`, `consumers`, `cross_repo_links`, `impact_surface`, `relationship_analysis` | `include=["graph"]` |
| `direct_risks` (inside `pr_blast_radius`) | `include=["graph"]` |
| `risk_type`, `change_pattern`, `change_magnitude` | `include=["churn"]` |
| `owner_pct`, `owner_line_pct`, `recent_owner`, `recent_owner_pct`, `bus_factor`, `contributor_count` | `include=["owners"]` |
| `pr_blast_radius` | `include=["blast"]`; its raw `structural_impact_score` only with `include=["blast", "scales"]` |
| `changes` (each file's recent Change Records) | `include=["changes"]` |

All of them are computed either way and all of them feed `risk_summary`.
`include` decides whether they ship, not whether they run. The gating is
deliberate: `impact_surface` and `direct_risks` are pagerank floats an
agent cannot rank against anything, and `risk_type`, `change_pattern` and
`change_magnitude` restate numbers already printed beside them.

Top-level extras:

- `global_hotspots`: top 5 hotspot files in the repo, excluding the
  targets. It accompanies a multi-target call only. A single named file
  does not need ambient orientation.
- `pr_blast_radius` *(only if `changed_files` provided)*:
  `transitive_affected`, `cochange_warnings`, `recommended_reviewers`,
  `test_gaps`, `structural_impact_score`, and `direct_risks` under
  `include=["graph"]`.

### Co-change partners

Each partner row carries:

| Field | Meaning |
|---|---|
| `weight` | A recency-decayed sum over shared commits, not a count. Fractional by construction |
| `direction` | `a_to_b`, `b_to_a`, or `undirected`, where `a` is the assessed file and `b` the partner |
| `conf_ab`, `conf_ba` | The share of each file's own commits that touched the other |
| `has_structural_link`, `has_import_link` | Whether the graph also connects them |
| `last_co_change` | Date of the most recent shared commit |

`direction` is decided by comparing the two confidences: the side that
seldom changes without the other leads. Equal confidences stay
`undirected`, and so does an index written before the per-file commit
totals were recorded, which omits `conf_ab` and `conf_ba` entirely.
Co-change is historical correlation. It is not a dependency.

> **Warning**
>
> `structural_impact_score` is a deterministic, uncalibrated 0 to 10
> structural-exposure heuristic: `localized` below 4, `moderate` from 4 to
> below 7, `broad` at 7 or above. It is not a runtime-breakage
> probability, and it is not comparable to `get_change_risk`'s `score`.
> `overall_risk_score` remains an exact alias for the compatibility
> window, carrying its own migration metadata.

## PR mode: read `directive` first

When `changed_files` is passed, the response leads with a `directive`
block. This is the block a PR-reviewing agent is told to read before
anything else in the response:

| Field | Meaning |
|---|---|
| `may_break` | Production files the diff structurally reaches, by a reverse-import walk. Candidates for review, not proven breakage, which is why it says may |
| `may_break_tests` | Test files reached the same way, kept separate so a burst of broken tests doesn't crowd out production impact |
| `missing_cochanges` | Historical co-changers absent from the diff |
| `missing_tests` | Changed files without test coverage |
| `tests_to_run` | Runnable test ids for the changed files, measured first and falling back to inferred. `tests_to_run_basis` says which: `measured`, `inferred`, or `none` |
| `test_recommendations` | The typed form of the same evidence. Each row keeps its `basis` (`measured` means the per-test coverage map found the test; `inferred` means structural reachability named a candidate and is not coverage proof), its source files, and its repository identity |

Unavailable coverage with `tests_to_run: []` never means no tests are
needed. `coverage_analysis`, `test_inference_analysis` and
`test_analysis` separate available-but-empty evidence from unavailable,
stale, partial and degraded analysis.

`get_risk` is handed a file list and never a diff, so `may_break` and
`may_break_tests` cannot know whether the symbol an importer actually
uses changed. Read both as reach worth reviewing instead of predicted
breakage.

In workspace mode, the same directive also carries the changed repo's
cross-repo fallout: `will_break_consumers` (services in other repos that
structurally depend on this one), `missing_cross_repo_cochanges`,
`breaking_changes` (incompatible provider-contract changes and the
consumers they endanger), `conformance_violations` (declared
dependency-rule breaches) and `dependency_cycles`.
Despite its name, `will_break_consumers` is structural reach and not a
runtime claim: its rows carry `claim: structural_reach` and
`runtime_breakage_claim: false`. Only `breaking_changes` comes from
contract diffing.

> **Warning**
>
> `directive` is the block to act on. Everything else in the response
> ranks and describes; `directive` recommends. Read it before
> `pr_blast_radius` or any of the per-target fields.

## Example

```python
get_risk(["src/auth/service.py"])

get_risk(
    targets=["src/payments/api.py", "src/payments/db.py"],
    changed_files=["src/payments/api.py", "src/payments/db.py"],
)

get_risk(targets=["src/auth/service.py"], include=["graph", "churn"])
```

## Things worth knowing

- **Risk type classification (priority order)**, under
  `include=["churn"]`: `bug-prone` if the file is flagged `bug_magnet`
  or has 3+ counted bug fixes in the fix history (not a keyword scan
  over commit messages); `churn-heavy` if churn percentile is 0.7 or
  above; `bus-factor-risk` if `bus_factor == 1`, more than 20 commits,
  and the repo isn't a small team unless the file is itself a hotspot;
  `high-coupling` if there are 5 or more dependents.
- **Change pattern** is the dominant commit category at 50% or more,
  else `mixed-activity`.
- **Test gap detection** checks ingested coverage data first: if a
  coverage report proves a test exercises the file, it's not a gap. Only
  when there's no coverage proof does it fall back to a filename
  heuristic, looking for `test_*.py`, `*_test.py`, or `*.spec.*` matching
  the file's basename. Test files themselves are never reported as having
  a test gap.
- **PR blast radius** walks the import graph up to depth 3.
  Recommended reviewers are the top-5 owners of affected files.
  `cochange_warnings` flag missing historical co-change partners not
  in the PR.
- **Cross-repo impact (workspace mode)**: co-change partners and
  contract links from other repos contribute to `dependents_count`.
- **Responses are bounded.** A default call fits 24,000 serialized
  characters and a call with a nonempty `include` gets 32,000. Anything
  reduced carries counts plus a recovery ref in `_meta.omitted`. An
  include-gated block is a projection, not an omission, so it is not
  listed there.

> **Warning**
>
> A high `hotspot_score` plus `bus_factor: 1` plus `test_gap: true` is
> the danger triangle. Don't ship a refactor of that file without pulling
> the primary owner into review.
