---
title: Configuration
description: The .lawlint/config.json schema, discovery rules, and how CLI flags override it.
icon: settings-2
---

Configuration is optional. Linting works with none at all.

```jsonc
// .lawlint/config.json — every field optional, camelCase
{
  "enable": ["no-em-dash", "no-hedging"],
  "disable": ["no-legalese"],
  "severity": { "no-em-dash": "suggestion" },
  "thresholds": { "no-hedging": 8 },
  "markdown": false,
  "ruleDirs": [".lawlint/rules"],
  "judge": { "enabled": false, "maxTokens": 16384 },
  "ai": {
    "model": "anthropic:claude-haiku-4-5-20251001",
    "features": { "judge": "...", "learn": "..." },
  }
}
```

Written by [`lawlint init`](/docs/guides/ai-setup). Safe to commit — API keys
are never stored here.

## Fields

| Prop | Type | Default | Description |
| - | - | - | - |
| `enable?` | `string[]` | - | Run only these rules. Full ids or bare aliases. |
| `disable?` | `string[]` | - | Run every rule except these. |
| `severity?` | `Record<string, "error" \| "warning" \| "suggestion">` | - | Override a rule's severity. "info" is accepted as an alias for "suggestion". |
| `thresholds?` | `Record<string, number>` | - | Override a density or statistical rule's threshold. |
| `markdown?` | `boolean` | - | Treat stdin as Markdown. .md files enable this on their own. |
| `ruleDirs?` | `string[]` | - | Additional rule package directories, merged over the built-ins. Resolved relative to the config file's directory. |
| `judge?` | `JudgeOptions` | - | How the soft-rule AI judge runs. See below. |
| `ai?` | `AiOptions` | - | Which model powers AI features. See below. |

### `judge`

| Prop | Type | Default | Description |
| - | - | - | - |
| `enabled?` | `boolean` | - | Run the soft-rule judge on every lint, without passing --judge. |
| `floor?` | `number` | `0.6` | Minimum confidence for a judge finding to be reported. Findings below it are dropped before scoring. |
| `maxTokens?` | `number` | `16384` | Tokens the model may generate per request, including any hidden reasoning tokens. |
| `concurrency?` | `number` | `4` | Requests in flight at once. |
| `contextChars?` | `number` | `24000` | Document text per request. Also the cache granule. |
| `perRule?` | `boolean` | `true` | Send one request per rule instead of one per section carrying every rubric. |

The defaults suit a capable hosted model. If you point lawlint at a small
self-hosted model, lower `contextChars` and set `perRule: false`.

The two shaping fields are worth understanding together:

- **`perRule: true`** judges each rule in its own request, so a rule gets the
  model's undivided attention and its own cache entry — adding a rule no
  longer invalidates the others. Each request carries the document, which is
  cheaper than it sounds: the document precedes the rubric in the prompt, so
  requests over one document share a prefix that providers bill at a discount.
- **`contextChars`** is also the **cache granule**. Raising it means fewer
  requests and more cross-section context; it also means an edit anywhere in a
  section re-runs that whole section. Lower it if you lint large documents on
  every keystroke.

:::note
`maxTokens` covers everything the model generates, including the hidden
reasoning tokens a thinking model spends before it answers. If the judge
reports that it failed on every section, this is almost always why — see
[Troubleshooting](/docs/guides/judge#when-the-judge-fails).
:::

### `ai`

| Prop | Type | Default | Description |
| - | - | - | - |
| `model?` | `string` | - | Default model spec for every AI feature. |
| `features?` | `Record<string, string>` | - | Per-feature overrides keyed by feature name ("judge", "learn", …). Takes precedence over `model`. |

API keys are **not** part of this file. They live in
`~/.lawlint/credentials` (mode `0600`) or in the environment —
`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `AZURE_FOUNDRY_API_KEY` — and the
environment wins.

## Discovery

The CLI walks up from the current directory. At each level it looks for
`.lawlint/config.json`, falling back to the legacy `lawlint.config.json`. The
first directory containing either is the project root.

If no project config is found, the CLI falls back to a user-level
`~/.lawlint/config.json` (`$LAWLINT_HOME` overrides the directory; the pre-0.8
`~/.config/lawlint/config.json` is still read). This is what lets lawlint work
on documents outside any project.

A project config **layers over** the user-level one field by field: whatever
the project sets wins, and everything it leaves out keeps the user-level value.
A repo that pins only `ruleDirs` still inherits your AI model.

If both exist at the same level, the nested `.lawlint/config.json` is used and
the CLI warns.

:::warning
A config file that exists but does not parse is a configuration error — exit
`2` — not a silent fallback to defaults. A typo in your config never quietly
changes which rules run.
:::

Relative `ruleDirs` resolve against the project root, so a committed config
works no matter which subdirectory you run from. Paths passed to `--rule-dir`
resolve against the current directory instead.

## Precedence

CLI flags override the config file, field by field:

| Config field | Overridden by |
| ------------ | ------------- |
| `enable` | `--rules` |
| `disable` | `--disable` |
| `markdown` | `--markdown` |
| `judge.enabled` | `--judge` |
| `ai.model` | `--judge=MODEL`, `learn --model` |

`severity` and `thresholds` merge rather than replace: a CLI override applies to
the named rule and leaves the rest of the map intact.
