{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "llmlint configuration",
"description": "Configuration for llmlint, an LLM-as-judge linter for code-quality checks deterministic linters can't express. Docs: https://github.com/nickderobertis/llmlint",
"type": "object",
"properties": {
"version": {
"description": "The config's published version (`1`, `1.1`, or `1.1.1`). Set this when\nthe config is consumed as a plugin: a consumer pins a desired version\nwith an `@` suffix on the plugin URL, validated against this value.",
"anyOf": [
{
"$ref": "#/$defs/Version"
},
{
"type": "null"
}
],
"default": null
},
"prompt_template": {
"description": "Master minijinja prompt template, rendered with `rules` (each with name +\ndescription) and `files` (the target paths). Overrides the built-in one.",
"type": [
"string",
"null"
],
"default": null
},
"files": {
"description": "Default include/exclude globs selecting target files when none are passed\non the CLI.",
"$ref": "#/$defs/FileFilter",
"default": {
"include": [],
"exclude": []
}
},
"oneharness": {
"description": "Defaults for how llmlint invokes the oneharness subprocess.",
"$ref": "#/$defs/OneharnessCfg",
"default": {
"config": [],
"bin": null,
"model": null,
"timeout": null,
"schema_max_retries": null
}
},
"rationales": {
"description": "Whether judges must justify each verdict with a short `rationale`\n(default `true`). Rationales aid auditability, debugging, and reliability\n(the judge reasons before concluding) but cost extra output tokens on\nevery request. A per-rule `rationale` overrides this default. The\n`--rationales`/`--no-rationales` CLI flags override the config.",
"type": [
"boolean",
"null"
]
},
"diff_base": {
"description": "Default base the `--diff` git backend compares target files against when\n`--diff-base` is not passed. Any git revision — a branch, tag, commit, or\n`A..B`/`A...B` range — e.g. `main` to make a quality gate review whatever\nthe current branch changed versus the default branch. A plain ref uses\nthree-dot / merge-base semantics (like a PR's \"Files changed\"), so a\nbranch behind its base doesn't see base-branch drift as its own changes;\nan `A..B` range is forwarded to git as-is. Unset keeps the built-in `HEAD`\n(working-tree) base; the `--diff-base` flag overrides it.",
"type": [
"string",
"null"
]
},
"history": {
"description": "Whether/how/where to log each run's full results to disk (default: on, the\nlast 100 runs, in the platform data dir). See [`HistoryCfg`].",
"$ref": "#/$defs/HistoryCfg"
},
"plugins": {
"description": "Plugins (shared rule sets) merged in, one entry each: a local file path\nor a URL (`http(s)://`, `file://`), the URL optionally pinned with an\n`@version` suffix. Named `plugins` (not `include`) to avoid confusion\nwith `files.include`. Resolution lives in [`crate::io::plugins`].",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"agents": {
"description": "Named agents that group rules and share harness/model/batch config. A\nrule with no `agent` uses the `default` agent.",
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/Agent"
},
"default": {}
},
"rules": {
"description": "The lint rules. Each is a positive invariant judged true/false about the\ntarget files (holds=true passes; holds=false is a violation).",
"type": "array",
"items": {
"$ref": "#/$defs/Rule"
},
"default": []
}
},
"$defs": {
"Version": {
"type": [
"integer",
"number",
"string"
]
},
"FileFilter": {
"description": "Include/exclude glob set used to select target files.",
"type": "object",
"properties": {
"include": {
"description": "Globs selecting files to lint.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"exclude": {
"description": "Globs subtracted from the included set.",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"additionalProperties": false
},
"OneharnessCfg": {
"description": "Passthrough/defaults for how llmlint invokes `oneharness`.",
"type": "object",
"properties": {
"config": {
"description": "oneharness config files to forward, each as its own `--config`, in\nlayering order: oneharness applies them lowest first, so a later file\noverrides an earlier one per field. Across nested llmlint configs and\nplugins the lists concatenate — the most distant config's entries first,\nthe nearest's last, an exact duplicate path kept only at its nearest\nposition — then `LLMLINT_ONEHARNESS_CONFIG`'s paths and the\n`--oneharness-config` flags follow, so the command line is the top layer.\nMore than one file needs oneharness >= 0.18.0 (layered `--config`).",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"bin": {
"description": "Override the oneharness binary path.",
"type": [
"string",
"null"
],
"default": null
},
"model": {
"description": "Default model for every judge (an agent's `model` overrides it).",
"type": [
"string",
"null"
],
"default": null
},
"timeout": {
"description": "Per-judge timeout in seconds (default 600).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 1,
"default": null
},
"schema_max_retries": {
"description": "Schema-validation re-prompt budget passed to oneharness `--schema-max-retries`.",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0,
"default": null
}
},
"additionalProperties": false
},
"HistoryCfg": {
"description": "Whether, how many, and where to log each run's full results to disk. When\nlogging is on (the default) every `lint`/`lint-config` run is written as one\nJSON record so callers can retrieve the complete results later — including\nthe per-rule detail the terminal report omits — via `llmlint history <id>`.",
"type": "object",
"properties": {
"enabled": {
"description": "Whether to log each run's results (default `true`). Set `false` to turn\nthe feature off entirely; nothing is written and no run id is shown.",
"type": [
"boolean",
"null"
]
},
"max_runs": {
"description": "How many of the most recent runs to keep (default 100). After each run the\noldest records beyond this count are pruned. Must be >= 1.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 1
},
"dir": {
"description": "Directory the JSON records are written to. Defaults to the platform\nper-user data directory for llmlint (e.g. `~/.local/share/llmlint/history`\non Linux, `%LOCALAPPDATA%\\llmlint\\data\\history` on Windows). The\n`LLMLINT_HISTORY_DIR` environment variable overrides this.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"Agent": {
"description": "A group of rules sharing reviewer context and harness/model/batch config.",
"type": "object",
"properties": {
"harness": {
"description": "Harness id from `oneharness list`. When unset, llmlint omits `--harness`\nand oneharness selects its own configured default harness.",
"type": [
"string",
"null"
],
"default": null
},
"model": {
"description": "Model override for this agent's judges.",
"type": [
"string",
"null"
],
"default": null
},
"batch_size": {
"description": "Max rules per judge run (default 20).",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 1,
"default": null
},
"prompt_template": {
"description": "Extra prompt text appended to the master template before rendering.",
"type": [
"string",
"null"
],
"default": null
}
},
"additionalProperties": false
},
"Rule": {
"description": "A single lint rule: a statement judged true/false about the target files.",
"type": "object",
"properties": {
"name": {
"description": "Terse snake_case identifier: an ASCII letter followed by letters, digits,\nor underscores. Used as a JSON Schema key for the judge's verdict.",
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_]*$"
},
"description": {
"description": "The invariant the judge evaluates. State clearly what is true (passes)\nand what is false (a violation). Required for a normal rule (an empty one\nis rejected); an `override` rule may omit it to inherit the base rule's\ntext, so the schema leaves it optional.",
"type": "string",
"minLength": 1,
"default": ""
},
"override": {
"description": "Override a same-named rule contributed by a plugin: inherit all of the\nbase rule's fields, replacing only the ones set here. Without this, a\nduplicate rule name is an error. Set it on the consuming (nearer-root)\nconfig; the override is resolved into the base when the config loads.",
"type": "boolean"
},
"agent": {
"description": "Name of the agent (under `agents`) this rule runs on. Defaults to the\n`default` agent.",
"type": [
"string",
"null"
],
"default": null
},
"judges": {
"description": "Independent judges to run; the majority verdict wins (default 1). Must be\nodd so the vote can't tie.",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 1,
"default": null
},
"files": {
"description": "Override the target files for this rule.",
"anyOf": [
{
"$ref": "#/$defs/FileFilter"
},
{
"type": "null"
}
],
"default": null
},
"rationale": {
"description": "Whether the judge must justify this rule's verdict with a `rationale`.\nOverrides the session-wide `rationales` default for this one rule; unset\ninherits it.",
"type": [
"boolean",
"null"
],
"default": null
},
"relevance": {
"description": "When this rule should be evaluated. `true` (the default) always\nevaluates; `false` never does (the rule is reported not relevant without\na judge call); a string is a condition the judge decides about the change\nfirst, reporting the rule \"not relevant\" when it does not hold. Lets a\nrule scope itself to applicable changes instead of every `description`\nneeding its own \"or not applicable\" escape hatch.",
"anyOf": [
{
"$ref": "#/$defs/Relevance"
},
{
"type": "null"
}
]
},
"require_line_attribution": {
"description": "Whether every violation of this rule must cite a concrete `file` and\n`line`. Off by default — some findings (e.g. a cross-cutting\narchitectural drift) genuinely can't be pinned to one source line, so a\nviolation may omit its location. Set `true` for a rule whose violations\nmust always be localizable: the generated schema then marks each\nviolation's `file`/`line` **required**, so oneharness re-prompts the judge\nto localize *every* violation in one batched turn (no per-violation back\nand forth), and the default template asks for it up front. A violation\nthat still arrives without a file+line is a hard error rather than a\nsilently-imprecise report. Inherited/overridable like the other per-rule\nfields.",
"type": [
"boolean",
"null"
]
}
},
"additionalProperties": false,
"required": [
"name"
]
},
"Relevance": {
"description": "When a rule should be evaluated. Mirrors the `description`/verdict split: a\nboolean is resolved deterministically by llmlint, a string is a\nnatural-language condition the judge decides about the change first.",
"anyOf": [
{
"description": "`true` (the default): always evaluate — the judge may not opt out.\n`false`: never evaluate — the rule is statically not applicable and is\nreported as not relevant without calling a judge.",
"type": "boolean"
},
{
"description": "A natural-language condition describing when the rule applies. The judge\ndecides whether it holds for the change *before* evaluating the verdict,\nand reports the rule \"not relevant\" (with no verdict) when it does not.",
"type": "string"
}
]
}
},
"$id": "https://raw.githubusercontent.com/nickderobertis/llmlint/main/assets/llmlint.schema.json"
}