{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/phases.json",
  "description": "The phase contract, in one place: the phase ids and names, which phases each mode runs, the per-phase token ceilings and the phase numbers other code compares against. gen-mode-dispatch.mjs, render-work-summary.sh, the autopilot runner, runs-index.mjs, usage-report.mjs and gen-facts.mjs read it; smoke-phase-contract.sh holds every copy that cannot read it (the phase docs and prose counts, agent-state.schema.json, token-budget.json, phase-tracker.sh, phase-banner.sh, gc-abandoned.sh) to it. Adding or merging a phase means editing here. Ceilings are measured against the current phase documents, never summed from earlier ones: smoke-token-budget.sh fails a ceiling more than 25% above the measurement.",
  "contractVersion": "2.0.0",
  "phaseSchema": 2,
  "phaseSchemaNote": "The phase vocabulary generation. metrics.jsonl is append-only and v19.0.0 renumbered the phases, so every line written from v19.0.0 on carries this number and a line without the field is generation 1. Aggregators pick their name table from it; without it phase 3 means Dev in old rows and Review in new ones and no reader can tell them apart.",
  "legacyPhaseNames": {
    "0": "Init",
    "1": "Analysis",
    "2": "Planning",
    "3": "Dev",
    "4": "Review",
    "5": "Test",
    "6": "Commit",
    "7": "Report",
    "note": "Generation 1 phase names, kept so an aggregator can label a historical metrics.jsonl row correctly instead of printing the current name for a number that meant something else. Read-only history: nothing emits these any more. The generation 1 -> 2 number map is not repeated here, it is the `was` array on each phase above."
  },
  "phases": [
    {
      "id": 0,
      "name": "Init",
      "doc": "phase-0-init.md",
      "maxTokens": 13400,
      "was": [0]
    },
    {
      "id": 1,
      "name": "Plan",
      "doc": "phase-1-plan.md",
      "maxTokens": 10250,
      "was": [1, 2]
    },
    {
      "id": 2,
      "name": "Dev",
      "doc": "phase-2-dev.md",
      "maxTokens": 10650,
      "was": [3]
    },
    {
      "id": 3,
      "name": "Review",
      "doc": "phase-3-review.md",
      "maxTokens": 17200,
      "was": [4, 5]
    },
    {
      "id": 4,
      "name": "Commit",
      "doc": "phase-4-commit.md",
      "maxTokens": 6600,
      "was": [6]
    },
    {
      "id": 5,
      "name": "Report",
      "doc": "phase-5-report.md",
      "maxTokens": 5700,
      "was": [7]
    }
  ],
  "totalMaxTokens": 63550,
  "modes": {
    "full": {
      "phases": [0, 1, 2, 3, 4, 5],
      "autopilot": false
    },
    "autopilot": {
      "phases": [0, 1, 2, 3, 4, 5],
      "autopilot": true
    },
    "analysis": {
      "phases": [0, 1, 3, 4, 5],
      "autopilot": false
    }
  },
  "thresholds": {
    "waitingFromPhase": 4,
    "mcpAllowedThroughPhase": 1,
    "note": "Phase numbers other code compares against, named here so a renumbering moves them with the contract. waitingFromPhase: runs-index.mjs groups a run as 'waiting on you' from Commit on. mcpAllowedThroughPhase: Figma MCP is reachable only through Plan; smoke-no-mcp-in-dev-phases.sh flags any recorded Figma call at a higher phase. Analysis runs inside Plan, so the permitted set is {0,1}."
  }
}
