# Findings and triage

What a finding is, the finding families repowise produces, which finding types each surface shows, and how a status you set persists across re-analysis.

A finding is one stored row that says something specific is wrong or worth a
look at a specific place: a marker that lowers a file's
[health score](https://docs.repowise.dev/intelligence/code-health), a file nothing imports, a
committed credential, a markdown link to a heading that no longer exists.
Every finding is computed from the index with no model, and every one carries
the evidence that produced it.

## The finding families

| Family | Produced by | Keyed by | Carries a triage status |
|---|---|---|---|
| Health findings | [Code health](https://docs.repowise.dev/intelligence/code-health) markers across defect risk, maintainability and performance, plus governance findings | `biomarker_type`, with a `dimension` and `severity` | Yes |
| Dead-code findings | [Dead code](https://docs.repowise.dev/intelligence/dead-code) | `kind`: `unreachable_file`, `unused_export`, `unused_internal`, `zombie_package` | Yes |
| Refactoring plans | [Refactoring intelligence](https://docs.repowise.dev/intelligence/refactoring) | plan id and a content-derived `public_id` | Yes |
| Security findings | [Security signals](https://docs.repowise.dev/intelligence/security-signals) | `kind` and a fixed `severity` | No, see below |
| Documentation drift | [Documentation drift](https://docs.repowise.dev/intelligence/doc-drift) | `kind`: `path`, `link`, `anchor`, `command`, `symbol` | No |
| Component ownership | [Components and ownership](https://docs.repowise.dev/intelligence/components) | no active owner, inactive owner, bus factor of 1, empty claim | No |

Health and dead-code findings meet in one place: dead-code percentage folds
into module health, and the dashboard's Code Health page lists both. Security
and documentation-drift findings sit on their own tabs of the same page and do
not deduct from a file's health score.

## Which finding types a surface shows

A finding type can be real in the analyzer and still too noisy to put in front
of a reader. The finding-type registry records, per dead-code `kind` and per
health `biomarker_type`, one of three states:

| State | What surfaces do |
|---|---|
| `validated` | Shown everywhere. A type the registry does not list is validated. |
| `provisional` | Shown only when a caller names the type or opts into unverified types, and then labelled `unverified`. |
| `hidden` | Left out of every default surface, even when named. |

The registry changes visibility only. The analyzers still run, the rows are
still stored and scored, and the raw CLI analysis still reports them. What
changes is what the reading surfaces show: the MCP tools
([`get_health`](https://docs.repowise.dev/mcp/get-health), [`get_dead_code`](https://docs.repowise.dev/mcp/get-dead-code),
and the health blocks inside [`get_context`](https://docs.repowise.dev/mcp/get-context) and
[`get_risk`](https://docs.repowise.dev/mcp/get-risk)), the REST finding lists, the dashboard's
attention list, file pages and "Do next" actions, the PR change-health review,
the generated `CLAUDE.md`, and the prompts behind the documentation pages.

Three types are hidden, each with the measurement that put it there:

| Type | Family | Why it is hidden |
|---|---|---|
| `unused_internal` | Dead code | Measured at 0.5% precision on 1,511 hand-labelled findings. TS/JS/Python emit no same-file read edges, so a private symbol used only in its own file looks unused. |
| `duplicated_assertion_block` | Health | Clones are matched on token shape only, so unrelated assertions pair up. |
| `dry_violation` | Health | Flags duplicated import blocks and data literals as design duplication. |

No type is provisional. A type returns to `validated` only when its
measured precision clears the bar for its family.

### Preview by language

The same registry holds one switch per layer per language. Where a layer's
hand-judged precision on a language is below the bar, its rows are served by
default with a **Preview** label carrying the measurement: a badge with the
measurement in its tooltip on the web, a `preview` field in REST, one compact
`_meta.preview` entry in MCP and one line in the CLI.

| Layer | Preview on | Measured as |
|---|---|---|
| Health findings | C++ | 82.5% not wrong, the lower of 2 repositories |
| Refactoring plans | Java, C#, C, C++ | Precision of judged plans |
| Performance opportunities | Java, C#, C, C++ | Precision of judged opportunities |
| Dead code | Java, C#, C, C++, Kotlin, Swift | Pooled precision, with the lowest repository named |

Health findings on Java, C# and C ship without a label, each measured at
92.5% or more correct or debatable on a blind sample. A layer switched to
`gated` on a language is held back instead and counted in a `gated` field.

### Held back by the file

Some health findings are held back by where they sit, not by type:

- Findings on vendored and generated files: that code is not yours to change.
- Git-history markers on test files: they describe how the test changes.
- Git-history markers on production files leave the default defect lists and
  are counted under `history`, while scores, KPIs, `get_risk` and the Fix
  first order still use them.

`include=["unverified"]` shows all of them.

`get_dead_code` says what it held back instead of shrinking silently: when a
withheld type had rows, its summary carries a `withheld_types` entry with the
count, the state and the reason.

```python
get_dead_code()                       # hidden kinds left out, counted in summary.withheld_types
get_health(include=["unverified"])    # also show provisional types, labelled "unverified"
```

## Triage: setting a status

Health findings, dead-code findings and refactoring plans share one status
vocabulary:

| Status | Meaning |
|---|---|
| `open` | Outstanding. The default, and what every list shows unless asked otherwise. |
| `acknowledged` | Real, and the team is not acting on it now. |
| `resolved` | Fixed, or the code moved on. |
| `false_positive` | Wrong for this repository. |

Where you set it:

- **Dashboard.** The health drawer on the Code Health page and the dead-code
  list have a status control per finding.
- **MCP.** [`set_finding_status`](https://docs.repowise.dev/mcp/set-finding-status) sets the status
  of one refactoring plan. It is opt-in and the only tool on the MCP surface
  that writes.
- **REST.** `PATCH /api/repos/{repo_id}/health/findings/{id}` for a health finding and
  `PATCH /api/dead-code/{id}` for a dead-code finding, on the local server
  started by [`repowise serve`](https://docs.repowise.dev/cli/server).

There is no CLI command for triage.

## What persists across re-analysis

A re-run of the analysis replaces only `open` rows, so a status you set is
never overwritten. What the next run does with the same issue depends on the
family:

- **Dead code.** A finding you acted on is not re-created. The next run drops
  any new finding that matches it on file, kind and symbol.
- **Refactoring plans.** A plan marked `false_positive` is never re-emitted. A
  plan a person marked `resolved` stays resolved even if the detector still
  fires. `acknowledged` records the judgment without suppressing anything.
- **Health findings.** A re-detected finding that matches a triaged row
  refreshes that row: `acknowledged` and `false_positive` stand, and `resolved`
  reopens. Finding ids are anchored on the symbol and the line offset into it,
  so edits above a function keep the id.

## Security and documentation-drift findings

Neither family has a status in the local index. To silence a security
finding, put a `repowise-security-ignore` comment on its line
(`repowise-security-ignore: kind, kind` for specific kinds). The finding leaves
the stored scan, so it does not reach the dashboard, `get_risk` or REST, and
the [CI gate](https://docs.repowise.dev/ci) counts it as suppressed and does not fail on it. Other
scanners' markers and a `.gitleaks.toml` allowlist are honoured too; see
[Security signals](https://docs.repowise.dev/intelligence/security-signals#suppressing-a-finding).
A documentation-drift finding goes away when the document or the tree is
fixed, since the check runs again on every update.

On the [hosted platform](https://docs.repowise.dev/hosted/security), the Security page's secret
and vulnerability lists carry statuses of their own, and only the repo owner
can change them.
