{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/research-output.schema.json",
  "title": "Multi-Agent Pipeline  -  research output",
  "description": "What a research pass (/multi-agent:research, engine multi-agent-refs/research/engine.md) writes to <run state dir>/research/research.json. research-gate.mjs reads it and decides which gaps closed; nothing here is taken on the writer's word. The gaps themselves come from the run state (maturity blockers and warnings, or analysis.openQuestions), so a finding can only address a gap the run actually has. Every source carries a label: evidence (tracker or fetched document, cited by URL or id and quoted), repo (file:line or a commit, quoted) or inference (reasoning, never enough on its own to close a gap).",
  "type": "object",
  "additionalProperties": false,
  "required": ["version", "kind", "findings"],
  "properties": {
    "version": { "type": "integer", "const": 1 },
    "kind": {
      "enum": ["maturity", "open-questions"],
      "description": "Which gap list the pass worked on. The gate takes the run state's own answer when the two disagree."
    },
    "item": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "kind": { "enum": ["jira", "github", "freetext"] },
        "key": { "type": ["string", "null"] },
        "url": { "type": ["string", "null"] }
      }
    },
    "findings": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["gap", "answer", "sources"],
        "properties": {
          "gap": {
            "type": "string",
            "minLength": 1,
            "description": "A maturity gap code (description_empty, no_repro_steps, ...) or an open question id (Q1, or the row's own id)."
          },
          "category": {
            "enum": [
              "description",
              "reproduction",
              "acceptance-criteria",
              "business-rule",
              "status",
              "scope",
              "technical",
              "other"
            ],
            "description": "The writer's view. The gate can only make it stricter: a gap that reads as acceptance criteria or a business rule is treated as one whatever this says."
          },
          "answer": { "type": "string", "minLength": 1 },
          "sources": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/$defs/source" }
          }
        }
      }
    },
    "notes": { "type": "string" }
  },
  "$defs": {
    "source": {
      "oneOf": [
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "ref", "quote"],
          "properties": {
            "label": { "const": "evidence" },
            "ref": {
              "type": "string",
              "minLength": 1,
              "description": "A URL or id present in research/context.json: the item, one of its comments, links or related issues, or a fetched document."
            },
            "quote": {
              "type": "string",
              "minLength": 1,
              "description": "Text found in that source, whitespace-insensitive."
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "file", "line", "quote"],
          "properties": {
            "label": { "const": "repo" },
            "file": { "type": "string", "minLength": 1 },
            "line": { "type": "integer", "minimum": 1 },
            "quote": { "type": "string", "minLength": 1 }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "commit", "quote"],
          "properties": {
            "label": { "const": "repo" },
            "commit": { "type": "string", "pattern": "^[0-9a-f]{7,40}$" },
            "quote": {
              "type": "string",
              "minLength": 1,
              "description": "Text in the commit's message or diff."
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "rationale"],
          "properties": {
            "label": { "const": "inference" },
            "rationale": { "type": "string", "minLength": 1 }
          }
        }
      ]
    }
  }
}
