{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://reviewgate.dev/config.schema.json",
  "title": "ReviewGate team policy (.reviewgate/config.yml)",
  "description": "Team review policy for ReviewGate. Lives in the repository as .reviewgate/config.yml (a stray .yaml file is not read — the bot reports it with a rename hint). Full reference: https://reviewgate.dev/docs/config. The bot parses defensively at runtime and reports unrecognised keys and values with a notice in the review summary; this schema catches the same mistakes BEFORE the commit — in the editor, in CI, or in the hands of an AI agent.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "version": {
      "description": "Config schema version, currently 1.",
      "enum": [
        1
      ]
    },
    "language": {
      "type": "string",
      "description": "Language of findings and of the summary frame (en, ru, or any other language name the model understands). Default: en."
    },
    "preset": {
      "description": "Framework layer of checks. Default: none.",
      "enum": [
        "angular",
        "react",
        "vue",
        "svelte",
        "nextjs",
        "nestjs",
        "express",
        "typescript",
        "django",
        "fastapi",
        "flask",
        "python",
        "gin",
        "echo",
        "fiber",
        "go",
        "spring",
        "java",
        "aspnet",
        "csharp",
        "laravel",
        "symfony",
        "yii2",
        "php",
        "android",
        "ktor",
        "kotlin",
        "rails",
        "ruby",
        "axum",
        "actix",
        "rust",
        "ios",
        "swift",
        "flutter",
        "dart",
        "none"
      ]
    },
    "severity_gate": {
      "description": "Block merge when a finding at this severity or above is open. off (default) blocks nothing. warning/comment are accepted as pre-2.0 aliases. off/no are exact lowercase (that is what the parser accepts).",
      "anyOf": [
        {
          "enum": [
            "off",
            "no"
          ]
        },
        {
          "$ref": "#/definitions/severity"
        },
        {
          "const": false
        }
      ]
    },
    "tests": {
      "description": "required (default) flags missing tests; optional does not.",
      "enum": [
        "optional",
        "required"
      ]
    },
    "rules": {
      "type": "array",
      "description": "Team rules in plain language. The rule schema is closed: id, description and optional severity — conditional logic and file scoping belong in review_prompt.",
      "items": {
        "$ref": "#/definitions/rule"
      }
    },
    "dont_flag": {
      "type": "array",
      "description": "Team assumptions — classes of NON-problems that silence false positives.",
      "maxItems": 30,
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "ignore": {
      "$ref": "#/definitions/globList",
      "description": "Glob masks of files not to review. Do not hide under ignore anything a team rule talks about."
    },
    "integration_branches": {
      "$ref": "#/definitions/globList",
      "description": "Review only requests targeting these branches (otherwise all of them)."
    },
    "incremental_review": {
      "$ref": "#/definitions/bool",
      "description": "A re-push reviews only the changed files (default: true)."
    },
    "review_drafts": {
      "$ref": "#/definitions/bool",
      "description": "Review draft requests too (default: false — the run happens when marked ready)."
    },
    "committable_suggestions": {
      "$ref": "#/definitions/bool",
      "description": "Ready fixes as a native one-click suggestion (default: false)."
    },
    "min_severity": {
      "description": "Threshold for inline comments; findings below it are folded into the summary. Default: info (post everything).",
      "$ref": "#/definitions/severity"
    },
    "review_prompt": {
      "type": "object",
      "description": "The team's own review guideline text.",
      "additionalProperties": false,
      "required": [
        "text"
      ],
      "properties": {
        "mode": {
          "description": "extend (default) adds to the built-in guideline; replace substitutes it.",
          "enum": [
            "extend",
            "replace"
          ]
        },
        "text": {
          "type": "string",
          "minLength": 1
        }
      }
    },
    "cost": {
      "type": "object",
      "description": "Review cost in the summary (opt-in).",
      "additionalProperties": false,
      "properties": {
        "show": {
          "$ref": "#/definitions/bool"
        },
        "currency": {
          "type": "string",
          "minLength": 1,
          "maxLength": 8
        },
        "models": {
          "type": "object",
          "description": "Price list: model name → price per 1M tokens.",
          "additionalProperties": {
            "$ref": "#/definitions/modelPrice"
          }
        }
      }
    },
    "reply": {
      "type": "object",
      "description": "Bot answers in review threads.",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "$ref": "#/definitions/bool"
        },
        "model": {
          "type": "string",
          "minLength": 1
        },
        "effort": {
          "$ref": "#/definitions/effort"
        },
        "backend": {
          "$ref": "#/definitions/backendName"
        },
        "max_replies_per_thread": {
          "type": "integer",
          "minimum": 1
        }
      }
    },
    "diagnostics": {
      "$ref": "#/definitions/bool",
      "description": "The 🔬 run diagnostics block in the summary. Unset: the installation default applies."
    },
    "questions": {
      "$ref": "#/definitions/bool",
      "description": "The ❓ question genre — a second lens over judge-rejected candidates (default: false)."
    },
    "llm": {
      "type": "object",
      "description": "Ensemble roles and context. Connection settings and secrets do NOT belong here — they live in the installation environment.",
      "additionalProperties": false,
      "properties": {
        "max_context_files": {
          "type": "integer",
          "minimum": 1
        },
        "full_file_context": {
          "$ref": "#/definitions/bool"
        },
        "environment_context": {
          "$ref": "#/definitions/bool"
        },
        "generators": {
          "type": "array",
          "description": "Generator roles — the eyes of the review. Each needs model or backend.",
          "items": {
            "$ref": "#/definitions/addressedRole"
          },
          "minItems": 1
        },
        "judges": {
          "type": "array",
          "description": "Judge panel, at most 2: agreement decides, a split goes to the arbiter.",
          "maxItems": 2,
          "items": {
            "$ref": "#/definitions/addressedRole"
          }
        },
        "arbiter": {
          "$ref": "#/definitions/role"
        }
      }
    },
    "angular": {
      "$ref": "#/definitions/presetOptions"
    },
    "react": {
      "$ref": "#/definitions/presetOptions"
    },
    "vue": {
      "$ref": "#/definitions/presetOptions"
    },
    "svelte": {
      "$ref": "#/definitions/presetOptions"
    },
    "nextjs": {
      "$ref": "#/definitions/presetOptions"
    },
    "nestjs": {
      "$ref": "#/definitions/presetOptions"
    },
    "express": {
      "$ref": "#/definitions/presetOptions"
    },
    "typescript": {
      "$ref": "#/definitions/presetOptions"
    },
    "django": {
      "$ref": "#/definitions/presetOptions"
    },
    "fastapi": {
      "$ref": "#/definitions/presetOptions"
    },
    "flask": {
      "$ref": "#/definitions/presetOptions"
    },
    "python": {
      "$ref": "#/definitions/presetOptions"
    },
    "gin": {
      "$ref": "#/definitions/presetOptions"
    },
    "echo": {
      "$ref": "#/definitions/presetOptions"
    },
    "fiber": {
      "$ref": "#/definitions/presetOptions"
    },
    "go": {
      "$ref": "#/definitions/presetOptions"
    },
    "spring": {
      "$ref": "#/definitions/presetOptions"
    },
    "java": {
      "$ref": "#/definitions/presetOptions"
    },
    "aspnet": {
      "$ref": "#/definitions/presetOptions"
    },
    "csharp": {
      "$ref": "#/definitions/presetOptions"
    },
    "laravel": {
      "$ref": "#/definitions/presetOptions"
    },
    "symfony": {
      "$ref": "#/definitions/presetOptions"
    },
    "yii2": {
      "$ref": "#/definitions/presetOptions"
    },
    "php": {
      "$ref": "#/definitions/presetOptions"
    },
    "android": {
      "$ref": "#/definitions/presetOptions"
    },
    "ktor": {
      "$ref": "#/definitions/presetOptions"
    },
    "kotlin": {
      "$ref": "#/definitions/presetOptions"
    },
    "rails": {
      "$ref": "#/definitions/presetOptions"
    },
    "ruby": {
      "$ref": "#/definitions/presetOptions"
    },
    "axum": {
      "$ref": "#/definitions/presetOptions"
    },
    "actix": {
      "$ref": "#/definitions/presetOptions"
    },
    "rust": {
      "$ref": "#/definitions/presetOptions"
    },
    "ios": {
      "$ref": "#/definitions/presetOptions"
    },
    "swift": {
      "$ref": "#/definitions/presetOptions"
    },
    "flutter": {
      "$ref": "#/definitions/presetOptions"
    },
    "dart": {
      "$ref": "#/definitions/presetOptions"
    },
    "none": {
      "$ref": "#/definitions/presetOptions"
    }
  },
  "definitions": {
    "bool": {
      "description": "Boolean; the parser also accepts the quoted YAML 1.1 spellings.",
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "enum": [
            "true",
            "false",
            "on",
            "off",
            "yes",
            "no"
          ]
        }
      ]
    },
    "severity": {
      "description": "Severity 2.0 scale; warning/comment are accepted as pre-2.0 aliases. Any letter case is accepted (the parser lowercases).",
      "anyOf": [
        {
          "enum": [
            "blocker",
            "critical",
            "major",
            "minor",
            "info",
            "warning",
            "comment"
          ]
        },
        {
          "type": "string",
          "pattern": "^(?:[Bb][Ll][Oo][Cc][Kk][Ee][Rr]|[Cc][Rr][Ii][Tt][Ii][Cc][Aa][Ll]|[Mm][Aa][Jj][Oo][Rr]|[Mm][Ii][Nn][Oo][Rr]|[Ii][Nn][Ff][Oo]|[Ww][Aa][Rr][Nn][Ii][Nn][Gg]|[Cc][Oo][Mm][Mm][Ee][Nn][Tt])$"
        }
      ]
    },
    "effort": {
      "description": "Reasoning effort of the role.",
      "enum": [
        "low",
        "medium",
        "high",
        "xhigh",
        "max"
      ]
    },
    "backendName": {
      "description": "Name of a backend declared in the installation environment (LLM_BACKEND_<NAME>_*): latin letters and digits, no separators.",
      "type": "string",
      "pattern": "^[A-Za-z0-9]+$"
    },
    "rule": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "description"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Stable identifier — shows up as the 📐 badge on findings."
        },
        "description": {
          "type": "string",
          "minLength": 1,
          "description": "The agreement in ordinary prose. Request variables like {mr:author} are allowed."
        },
        "severity": {
          "$ref": "#/definitions/severity"
        }
      }
    },
    "role": {
      "type": "object",
      "description": "One ensemble role: model, reasoning effort, named backend.",
      "additionalProperties": false,
      "properties": {
        "model": {
          "type": "string",
          "minLength": 1
        },
        "effort": {
          "$ref": "#/definitions/effort"
        },
        "backend": {
          "$ref": "#/definitions/backendName"
        }
      }
    },
    "addressedRole": {
      "description": "A role in a list must address something: model or backend.",
      "allOf": [
        {
          "$ref": "#/definitions/role"
        },
        {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "model": {
                  "type": "string",
                  "minLength": 1
                }
              },
              "required": [
                "model"
              ]
            },
            {
              "type": "object",
              "properties": {
                "backend": {
                  "$ref": "#/definitions/backendName"
                }
              },
              "required": [
                "backend"
              ]
            }
          ]
        }
      ]
    },
    "modelPrice": {
      "type": "object",
      "description": "Price per 1M tokens; cache prices are optional (vendor multipliers apply otherwise).",
      "additionalProperties": false,
      "required": [
        "input",
        "output"
      ],
      "properties": {
        "input": {
          "type": "number",
          "minimum": 0
        },
        "output": {
          "type": "number",
          "minimum": 0
        },
        "cache_write_5m": {
          "type": "number",
          "minimum": 0
        },
        "cache_write_1h": {
          "type": "number",
          "minimum": 0
        },
        "cache_read": {
          "type": "number",
          "minimum": 0
        }
      }
    },
    "globList": {
      "type": "array",
      "maxItems": 200,
      "items": {
        "type": "string",
        "maxLength": 1024
      }
    },
    "presetOptions": {
      "type": "object",
      "description": "Free-form options of the selected preset."
    }
  }
}
