{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/mmerterden/multi-agent-pipeline/schemas/code-graph.schema.json",
  "title": "Code graph",
  "description": "Deterministic, LLM-free code graph produced by graph-build.mjs and consumed by Phase 1 (Explore scope narrowing) and Phase 7 (knowledge capture). One file per project under ~/.claude/knowledge/<project>/code-graph.json.",
  "type": "object",
  "required": ["schemaVersion", "stack", "root", "generatedAt", "stats", "nodes", "edges"],
  "additionalProperties": false,
  "properties": {
    "schemaVersion": {
      "const": "1.0.0"
    },
    "stack": {
      "type": "string",
      "enum": ["ios", "android", "node", "python", "go"]
    },
    "root": {
      "type": "string",
      "description": "Absolute repo root the graph was built from."
    },
    "baseCommit": {
      "type": ["string", "null"],
      "description": "git HEAD at build time. Phase 1 compares it against the current HEAD to decide staleness; null when the root is not a work tree."
    },
    "generatedAt": {
      "type": "string",
      "description": "ISO 8601 build timestamp."
    },
    "stats": {
      "type": "object",
      "required": ["files", "nodes", "edges"],
      "additionalProperties": false,
      "properties": {
        "files": { "type": "integer", "minimum": 0 },
        "nodes": { "type": "integer", "minimum": 0 },
        "edges": { "type": "integer", "minimum": 0 }
      }
    },
    "nodes": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "kind", "name", "degree"],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "description": "file:<relpath> | sym:<relpath>#<name> | module:<name>"
          },
          "kind": { "type": "string", "enum": ["file", "symbol", "module"] },
          "name": { "type": "string" },
          "path": { "type": ["string", "null"] },
          "line": { "type": "integer", "minimum": 1 },
          "symbolKind": { "type": "string" },
          "nested": {
            "type": "boolean",
            "description": "Symbol nodes only. true when the declaration is indented, which in every stack these rules cover means it is declared inside another one. A nested symbol keeps its node and its defines edge, so it stays findable by name, but it is never a reference target: sealed cases and inner classes are named after the concept they model (Icon, Color, Success) and each is declared exactly once, so the ambiguity rule does not catch them."
          },
          "isTest": { "type": "boolean" },
          "degree": { "type": "integer", "minimum": 0 }
        }
      }
    },
    "edges": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["from", "to", "kind"],
        "additionalProperties": false,
        "properties": {
          "from": { "type": "string" },
          "to": { "type": "string" },
          "kind": { "type": "string", "enum": ["defines", "imports", "references"] }
        }
      }
    },
    "manifest": {
      "type": "object",
      "description": "relpath -> {size, mtimeMs}. Staleness signal only: a full rebuild takes seconds at corporate-repo scale, so the pipeline never rebuilds a subset.",
      "additionalProperties": {
        "type": "object",
        "required": ["size", "mtimeMs"],
        "additionalProperties": false,
        "properties": {
          "size": { "type": "integer", "minimum": 0 },
          "mtimeMs": { "type": "integer", "minimum": 0 }
        }
      }
    }
  }
}
