# Review calibration — verdicts, severity, and the report

Read this before verdicting. It defines the shape of an answer, not the rules — the rules are in the coding-preflight pack paths your brief listed, and you read those directly.

## One verdict per cell

Return verdicts in census order, one per cell. A clean cell is a single line — do not pad it with prose:

```
- cell: src/orders/pricing.py × structural — verdict: clean
```

A cell covers its whole rule group, so it can carry several findings from several rules; repeat the cell once per finding and name the violated rule in each:

```
- cell: src/orders/pricing.py × structural
  verdict: finding
  rule: DRY
  line: 42
  severity: must-fix
  snippet: `discount = subtotal * 0.15 if tier == "gold" else 0`
  note: The same tier→rate table is already in `domain/tiers.py:18`; a rate change now has two homes. Call the existing lookup instead of re-expressing it here.
```

Fields: `cell` (`target × axis`, exactly as the census wrote it — the axis is the rule group, and there is never one cell per individual rule), `verdict` (`clean` | `finding`), `rule` (the specific rule this finding violates, spelled as its pack spells it; omitted on `clean`), `line` (a line **this diff changed**), `severity`, `snippet` (the quoted changed line), `note` (1–2 sentences: what is wrong plus a concrete fix).

There is no length budget. Dropping a real finding to stay brief is the failure this review exists to prevent, and a missing cell is re-dispatched as unfinished work, never read as a clean.

## Severity and points

| Severity | Meaning | Points |
|---|---|---|
| `must-fix` | a defect, or a rule violation that will cost real work later — wrong behavior, a swallowed error, a duplicated decision, a self-mocked test that proves nothing | 3 |
| `should-fix` | a clear rule violation with no immediate breakage — a name that misleads, a function that fails the plain-English test, business logic in the wrong layer | 2 |
| `nit` | real but small; the fix is cheap and obviously better | 1 |

Grade the defect, not your confidence. If you are not confident, the verdict is `clean` — see the hedge test below.

## When `clean` is the right verdict

`clean` is a result, not a concession. Return it when:

- **The defect sits only on lines this diff did not change.** Findings anchor on changed lines; pre-existing warts are not this change's problem.
- **The rule does not apply to this cell.** The census over-includes on purpose, so cells where a rule turns out to be irrelevant are expected — that is exactly what a `clean` verdict records.
- **The change is mechanical for this rule.** A signature-only or type-annotation-only edit does not reopen a function's body-level rules; a function the diff neither added nor renamed does not reopen its name.

**The hedge test breaks a borderline `must-fix` or `should-fix`.** At those two severities the finding is one you would defend without qualifiers. If your draft note reaches for a softener that questions whether the point is worth raising at all — "marginal", "arguably", "if you are touching it anyway" — you are looking at a `nit` or at nothing: downgrade it or verdict `clean`, but never ship a hedged `should-fix`.

The test does not empty the `nit` tier. A `nit` says the defect is small because that is the tier's definition, not because the reviewer is hedging — it still names a concrete defect on a changed line and a concrete fix.

## Every finding carries its fix

A finding without a fix is a complaint. Specifically:

- **Readability findings** carry a pseudocode sketch — 4 to 6 lines that convey the intended reading experience, not a full refactor. If the sketch does not make a reader think "yes, that reads like prose", rewrite the sketch.
- **Naming findings** carry a concrete alternative name, and one clause saying what the new name encodes that the old one did not.
- **Structural findings** name the destination: which layer, module, or existing symbol the code should move to or call.

## Test files

Loose typing in test files (`any`, dynamic casts, untyped fixtures) is a **typing** carve-out only — never flag it on typing grounds alone. It stops being a carve-out the moment the cast is the vehicle for another violation: reaching into privates, or stubbing the unit under test. The self-mock rules apply through the cast exactly as they do without it — what is judged is the behavior, not the cast.

## The report

Merged by the orchestrator, written to `reviewPath`. Sections, in this order, empty ones omitted except `Coverage` and `Score`:

```
## Coverage
## Must-fix
## Should-fix
## Nits
## Score
```

- **Coverage** — one or two lines: N changed files → S `structural` / F `semantic` / T `state-and-tests` / G `general` cells, all verdicted; M files excluded with reasons; the applied packs, and any pack that was unavailable. The four counts are cell counts at the census's granularity — one cell per target per axis.
- **Findings** — each one opens with `` `path/to/file.py:42` `` + the verdict's `rule` name + severity + points, then the snippet as a blockquote, then the 1–2 sentence note with its fix.
- **Score** — every finding gets a row, and the table is emitted even when there are none, with a single total row reading 0:

```
| # | Location | Rule | Severity | Points |
|---|---|---|---|---|
| 1 | `src/orders/pricing.py:42` | DRY | must-fix | 3 |
|   |   |   | **Total** | **3** |
```

The report's prose is written in Korean. Paths, identifiers, rule names, and quoted code stay verbatim.
