{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://codedrifters.github.io/packages/schemas/focus.schema.json",
  "title": "focus.json",
  "description": "Focus-scoring engine contract for configulator-based projects. Declares named focus areas (customers, segments, organizations, software, regulations, people) that boost the priority of matching issues at triage time.",
  "type": "object",
  "additionalProperties": false,
  "required": ["schemaVersion", "focusAreas"],
  "properties": {
    "schemaVersion": {
      "type": "integer",
      "const": 1,
      "description": "Schema version. Bumped only on backwards-incompatible changes."
    },
    "thresholds": {
      "type": "object",
      "description": "Cumulative focus-score thresholds that map into the priority:* taxonomy.",
      "additionalProperties": false,
      "required": ["high", "medium"],
      "properties": {
        "high": {
          "type": "integer",
          "minimum": 1,
          "description": "Total focus score at or above which an issue is boosted to priority:high."
        },
        "medium": {
          "type": "integer",
          "minimum": 1,
          "description": "Total focus score at or above which an issue holds at priority:medium. Must be less than thresholds.high."
        }
      }
    },
    "agentExpansionRules": {
      "type": "object",
      "description": "Guard-rails on what agents may append to focus.json without human review. Append-only: agents never modify or remove existing entries.",
      "additionalProperties": false,
      "required": ["maxWeight", "allowedTypes", "forbiddenTypes"],
      "properties": {
        "maxWeight": {
          "type": "integer",
          "minimum": 1,
          "description": "Hard cap on the weight an agent may set on a newly appended focus area."
        },
        "allowedTypes": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "uniqueItems": true,
          "description": "Whitelist of FocusArea.type values agents are permitted to introduce."
        },
        "forbiddenTypes": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "uniqueItems": true,
          "description": "Blocklist of FocusArea.type values agents must never introduce. Wins on conflict with allowedTypes."
        },
        "maxKeywords": {
          "type": "integer",
          "minimum": 1,
          "description": "Optional cap on the number of entries in each keyword array (labels, titleKeywords, bodyKeywords) on an agent-appended focus area."
        }
      }
    },
    "focusAreas": {
      "type": "array",
      "description": "Ordered list of scored focus areas. Order is not significant — each area contributes its weight independently when its match predicate fires.",
      "items": { "$ref": "#/$defs/focusArea" }
    }
  },
  "$defs": {
    "focusArea": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "type", "source", "match", "weight"],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Human-readable name for the focus area (e.g. 'Acme Corp')."
        },
        "type": {
          "type": "string",
          "minLength": 1,
          "description": "Consuming-repo-defined vocabulary (e.g. 'customer', 'segment', 'organization', 'software', 'regulation', 'person')."
        },
        "source": {
          "type": "string",
          "enum": ["human", "agent"],
          "description": "Provenance. 'human' entries are curated by a maintainer; 'agent' entries were appended by an agent under the agent-expansion rules and must carry discoveredIn."
        },
        "discoveredIn": {
          "type": "string",
          "pattern": "^#\\d+$",
          "description": "Issue reference (e.g. '#123') that introduced the focus area. Required when source='agent'."
        },
        "match": { "$ref": "#/$defs/focusAreaMatch" },
        "weight": {
          "type": "integer",
          "minimum": 1,
          "description": "Positive integer weight contributed to the issue's focus score when the match fires."
        }
      },
      "allOf": [
        {
          "if": {
            "properties": { "source": { "const": "agent" } },
            "required": ["source"]
          },
          "then": {
            "required": ["discoveredIn"]
          }
        }
      ]
    },
    "focusAreaMatch": {
      "type": "object",
      "additionalProperties": false,
      "description": "Match predicate. At least one sub-field should be set; multiple sub-fields combine as a logical OR.",
      "properties": {
        "labels": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "minItems": 1,
          "uniqueItems": true,
          "description": "Any of these labels present on the issue matches."
        },
        "titleKeywords": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "minItems": 1,
          "uniqueItems": true,
          "description": "Case-insensitive substrings matched against the issue title."
        },
        "bodyKeywords": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "minItems": 1,
          "uniqueItems": true,
          "description": "Case-insensitive substrings matched against the issue body."
        }
      },
      "anyOf": [
        { "required": ["labels"] },
        { "required": ["titleKeywords"] },
        { "required": ["bodyKeywords"] }
      ]
    }
  }
}
