---
title: JSON output
description: The schema of --format json — diagnostics, spans, fixes, and stats.
icon: braces
---

```sh
lawlint draft.md --format json
```

```json
{
  "diagnostics": [
    {
      "ruleId": "no-em-dash",
      "severity": "warning",
      "tier": "static",
      "intent": "detection",
      "span": { "start": 42, "end": 45 },
      "message": "An em dash where a comma would do.",
      "line": 3,
      "column": 12,
      "endLine": 3,
      "endColumn": 15,
      "excerpt": "The clause — which we drafted — is void.",
      "suggestion": "Use a comma.",
      "weight": 1,
      "fix": {
        "edits": [{ "range": { "start": 42, "end": 45 }, "replacement": "," }],
        "applicability": "machineApplicable"
      }
    }
  ],
  "stats": { "wordCount": 812, "sentenceCount": 41, "score": 73 }
}
```

Optional fields are omitted rather than emitted as `null`.

## `diagnostics[]`

| Prop | Type | Default | Description |
| - | - | - | - |
| `ruleId` | `string` | - | Namespaced for package rules: <package>/<id>. |
| `severity` | `"error" \| "warning" \| "suggestion"` | - | "info" is accepted on input as an alias for "suggestion". |
| `tier` | `"static" \| "statistical" \| "inferential"` | - | The stable kind contract. static and statistical are hard rules; inferential is soft. |
| `intent?` | `"detection" \| "style"` | `"detection"` | Only detection findings charge the score. Absent in results serialized before intent existed. |
| `span` | `{ start, end }` | - | Byte offsets into the linted text. |
| `message` | `string` | - | What the rule found. |
| `line` | `number` | - | 1-based. |
| `column` | `number` | - | 1-based, in UTF-16 code units. |
| `endLine?` | `number` | - | 1-based. Omitted for single-position findings. |
| `endColumn?` | `number` | - | 1-based, UTF-16 code units. |
| `excerpt` | `string` | - | The trimmed source line the finding sits on. |
| `suggestion?` | `string` | - | Prose advice. Present even when there is no applicable fix. |
| `weight?` | `number` | - | Score penalty multiplier. Density rules scale it by how far past threshold the text runs. |
| `confidence?` | `number` | - | Inferential (soft-rule) findings only. |
| `fix?` | `Fix` | - | Present only when the rule produced one. |

:::note
`span` is in **byte** offsets, while `column` is in **UTF-16 code units**. Use
`span` to slice the source text and `column` to position a cursor in an editor;
they disagree on any line containing non-ASCII characters — including the em
dashes several rules flag.
:::

## `fix`

| Prop | Type | Default | Description |
| - | - | - | - |
| `edits` | `{ range: { start, end }, replacement: string }[]` | - | Byte ranges to replace. A fix may carry more than one edit. |
| `applicability` | `"machineApplicable" \| "maybeIncorrect"` | - | --fix applies machineApplicable edits only. |

See [Fixes and tracked changes](/docs/guides/fixing#only-what-is-safe).

## `stats`

| Prop | Type | Default | Description |
| - | - | - | - |
| `wordCount` | `number` | - | Words in the linted text — the denominator for score normalization. |
| `sentenceCount` | `number` | - | |
| `score` | `number` | - | Human-likeness score, 0–100. |

## `judge`

Present only when the [AI judge](/docs/guides/judge) ran, carrying that run's
judging statistics. A "chunk" here is one judge request.

| Prop | Type | Default | Description |
| - | - | - | - |
| `chunks` | `number` | - | Requests the run planned. Depends on the backend's batching — one per rule for hosted models, one per section for local ones. |
| `cacheHits` | `number` | - | Requests answered from the on-disk cache instead of the provider. |
| `chunksFailed` | `number` | - | Requests that failed after a retry. Those chunks contributed no findings and fell back to the deterministic tiers. |
| `grounded` | `number` | - | Findings whose quote was matched back to a source span. Only these become diagnostics. |
| `hallucinated` | `Record<string, number>` | - | Discarded findings per rule id: an ungroundable quote, or a rule the request never asked about. |
| `firstFailure?` | `string` | - | Why the first failed chunk failed. Absent when chunksFailed is 0. |

```json
{
  "chunks": 2,
  "cacheHits": 0,
  "chunksFailed": 0,
  "grounded": 3,
  "hallucinated": { "empty-hedge": 1 }
}
```

:::warning
`chunksFailed > 0` means the soft rules did not fully run, so the `score` in
`stats` is earned on the deterministic tiers alone. Treat it the way the CLI
does — as an incomplete review, not a clean document. `firstFailure` carries
the cause; see [When the judge
fails](/docs/guides/judge#when-the-judge-fails).
:::

## Rule metadata

`lawlint rules --json` prints the loaded rule set rather than a lint result:

```json
[
  {
    "id": "empty-hedge",
    "meta": {
      "tier": "inferential",
      "scope": "text",
      "severity": "warning",
      "description": "Flags hedges that carry no information about actual uncertainty.",
      "docsUrl": "https://lawlint.com/rules/empty-hedge",
      "examples": [],
      "rationale": "A hedge earns its place only when it tells the reader what is uncertain and why."
    }
  }
]
```

This is what generates the [rule reference](/docs/rules).

## Compatibility

Key off `tier`, not the hard/soft terminology — `tier` is the serialized
contract and will not change. New optional fields may be added; parse
permissively.
