{
  "dsl": "minigraph-commands",
  "version": "1.0",
  "description": "Machine-readable command catalog for the MiniGraph Playground DSL. Mirrors command-reference.md. An AI agent should ingest this to generate and validate commands deterministically before dispatching them via the companion endpoint (POST /api/graph/{id} for execution; POST /api/companion/{session-id} for live Playground commands).",
  "source_of_truth": "system/minigraph-playground-engine/src/main/resources/help/",
  "lexical": {
    "node_name": "lowercase letters and hyphen only",
    "node_type": "a descriptive label; examples capitalize structural types (Root, End, Provider, Dictionary, Fetcher, Island)",
    "reserved_names": { "root": "the root/entry node must be named 'root'", "end": "the end/exit node must be named 'end'" },
    "property": "key=value; keys may be composite using dot-bracket notation",
    "list_property": "a 'key[]=entry' line appends one entry to the list 'key'; repeat to add more",
    "multiline_value": "wrap a value in triple single quotes ''' ... ''' to span lines",
    "constant": "Event Script constant syntax type(value), e.g. text(hello), int(100); full set in the Event Script docs",
    "mapping_operator": "source -> target (left is source, right is target)",
    "variable_substitution": "{namespace.key} inside COMPUTE/IF expressions, e.g. {book.price}"
  },
  "namespaces": [
    { "name": "input.body", "read": true, "write": "seeded at instantiate", "meaning": "incoming request body" },
    { "name": "input.header", "read": true, "write": "seeded at instantiate", "meaning": "incoming request headers" },
    { "name": "model.*", "read": true, "write": true, "meaning": "intermediate working state" },
    { "name": "output.body", "read": true, "write": true, "meaning": "response body" },
    { "name": "output.header", "read": true, "write": true, "meaning": "response headers" },
    { "name": "{node}", "read": true, "write": true, "meaning": "a node's own properties" },
    { "name": "{node}.result", "read": true, "write": "by skill", "meaning": "a skill's output" },
    { "name": "{node}.status", "read": true, "write": "by engine", "meaning": "skill execution status" },
    { "name": "{node}.error", "read": true, "write": "by engine", "meaning": "skill error, if any" },
    { "name": "response.*", "read": "in a Dictionary output[]", "write": "by fetch", "meaning": "a data Provider's raw HTTP response body" },
    { "name": "result.*", "read": true, "write": "by skill", "meaning": "a Dictionary/Fetcher result set" }
  ],
  "commands": [
    { "name": "create node", "multiline": true, "help": "help create.md",
      "syntax": "create node {name}\nwith type {type}\nwith properties\n{key}={value}",
      "notes": ["properties optional (used as defaults)", "zero or one skill via skill={route}", "name/type lowercase+hyphen; root/end reserved"],
      "example": "create node end\nwith type End\nwith properties\nskill=graph.data.mapper\nmapping[]=text(hello world) -> output.body" },
    { "name": "update node", "multiline": true, "help": "help update.md",
      "syntax": "update node {name}\nwith type {type}\nwith properties\n{key}={value}",
      "notes": ["same shape as create node; replaces the node definition"],
      "example": "update node end\nwith type End\nwith properties\nskill=graph.data.mapper\nmapping[]=input.body -> output.body" },
    { "name": "connect", "multiline": false, "help": "help connect.md",
      "syntax": "connect {node-a} to {node-b} with {relation}",
      "notes": ["directional: a->b differs from b->a"],
      "example": "connect root to end with done" },
    { "name": "delete node", "multiline": false, "help": "help delete.md",
      "syntax": "delete node {name}", "example": "delete node fetcher" },
    { "name": "delete connection", "multiline": false, "help": "help delete.md",
      "syntax": "delete connection {node-a} and {node-b}", "example": "delete connection root and end" },
    { "name": "instantiate graph", "multiline": true, "help": "help instantiate.md", "alias": "start",
      "syntax": "instantiate graph\n{constant} -> input.body.{key}",
      "notes": ["required before run/execute/inspect", "seed lines optional"],
      "example": "instantiate graph\nint(100) -> input.body.profile_id\ntext(application/json) -> input.header.content-type" },
    { "name": "run", "multiline": false, "help": "help run.md",
      "syntax": "run", "notes": ["traverse from root to end"], "example": "run" },
    { "name": "execute", "multiline": false, "help": "help execute.md",
      "syntax": "execute {node}", "notes": ["run a single node after instantiate"], "example": "execute fetcher" },
    { "name": "inspect", "multiline": false, "help": "help inspect.md",
      "syntax": "inspect {namespace.key}", "example": "inspect {output.body}" },
    { "name": "describe graph", "multiline": false, "help": "help describe.md", "syntax": "describe graph", "example": "describe graph" },
    { "name": "describe node", "multiline": false, "help": "help describe.md", "syntax": "describe node {name}", "example": "describe node end" },
    { "name": "describe connection", "multiline": false, "help": "help describe.md", "syntax": "describe connection {node-a} and {node-b}", "example": "describe connection root and end" },
    { "name": "describe skill", "multiline": false, "help": "help describe.md", "syntax": "describe skill {skill.route}", "example": "describe skill graph.api.fetcher" },
    { "name": "list nodes", "multiline": false, "help": "help list.md", "syntax": "list nodes", "example": "list nodes" },
    { "name": "list connections", "multiline": false, "help": "help list.md", "syntax": "list connections", "example": "list connections" },
    { "name": "seen", "multiline": false, "help": "help seen.md", "syntax": "seen", "example": "seen" },
    { "name": "export graph", "multiline": false, "help": "help export.md",
      "syntax": "export graph as {name}", "notes": ["fails if any node is an orphan"], "example": "export graph as my-first-graph" },
    { "name": "import graph", "multiline": false, "help": "help import.md", "syntax": "import graph from {name}", "example": "import graph from my-first-graph" },
    { "name": "import node", "multiline": false, "help": "help import.md", "syntax": "import node {node} from {name}", "example": "import node fetcher from tutorial-3" },
    { "name": "session", "multiline": false, "help": "help session.md",
      "syntax": "session | session subscribe {id} | session unsubscribe | session reset",
      "example": "session subscribe ws-178443-2" }
  ],
  "node_types": [
    { "type": "Root", "structural": true, "name_must_be": "root" },
    { "type": "End", "structural": true, "name_must_be": "end" },
    { "type": "Dictionary", "structural": false, "role": "external attribute definition (config)" },
    { "type": "Provider", "structural": false, "role": "external endpoint definition (config)" },
    { "type": "<descriptive>", "structural": false, "role": "label validated by the node's skill, if any" }
  ],
  "skills": [
    { "route": "graph.data.mapper", "help": "help graph-data-mapper.md", "required": ["mapping[]"], "optional": [],
      "example": "skill=graph.data.mapper\nmapping[]=input.body.hr_id -> employee.id" },
    { "route": "graph.math", "help": "help graph-math.md", "required": ["statement[]"], "optional": ["for_each[]", "NEXT:", "DELAY:", "BEGIN", "END"],
      "statement_types": ["COMPUTE", "IF", "MAPPING", "EXECUTE", "RESET"],
      "example": "skill=graph.math\nstatement[]=COMPUTE: amount -> (1 - {input.body.discount}) * {book.price}" },
    { "route": "graph.js", "help": "help graph-js.md", "required": ["statement[]"], "optional": ["for_each[]", "NEXT:", "DELAY:"],
      "notes": ["full JavaScript via GraalVM; max 50 instances; prefer graph.math for simple math"],
      "example": "skill=graph.js\nstatement[]=COMPUTE: amount -> (1 - {input.body.discount}) * {book.price}" },
    { "route": "graph.api.fetcher", "help": "help graph-api-fetcher.md", "required": ["dictionary[]", "input[]", "output[]"], "optional": ["for_each[]", "concurrency", "exception"],
      "notes": ["concurrency 1-30 (default 3); identical requests are deduplicated"],
      "example": "skill=graph.api.fetcher\ndictionary[]=person-name\ninput[]=input.body.person_id -> person_id\noutput[]=result.name -> output.body.name" },
    { "route": "graph.extension", "help": "help graph-extension.md", "required": ["extension", "input[]"], "optional": ["output[]", "for_each[]", "concurrency", "exception"],
      "notes": ["extension={graph-id} for a sub-graph, or flow://{flow-id} for an Event Script flow"],
      "example": "skill=graph.extension\nextension=evaluate-sales-performance\ninput[]=input.body.department_id -> id\noutput[]=result -> output.body" },
    { "route": "graph.join", "help": "help graph-join.md", "required": [], "optional": [],
      "notes": ["returns next only when all upstream nodes complete, else .sink (pause)"],
      "example": "skill=graph.join" },
    { "route": "graph.island", "help": "help graph-island.md", "required": [], "optional": [],
      "notes": ["always returns .sink; organizational/isolated node"],
      "example": "skill=graph.island" }
  ],
  "config_nodes": {
    "Provider": { "properties": ["url", "method", "feature[]", "input[]"], "input_targets": ["header.*", "query.*", "path_parameter.*", "body.*"] },
    "Dictionary": { "properties": ["provider", "input[]", "output[]"], "output_target": "result.*" }
  },
  "invariants": [
    "root node named 'root'; end node named 'end'",
    "a node has 0 or 1 skill",
    "node names are lowercase + hyphen only (types are descriptive labels, commonly capitalized)",
    "every node in the traversal path must connect to >=1 node or export fails",
    "Dictionary/Provider config nodes are referenced by name (not traversed) and need no connections; optionally grouped under a graph.island",
    "a node is executed once per run (RESET in graph.math/graph.js is the only escape)",
    "instantiate graph must precede run/execute/inspect"
  ]
}
