{"record":{"id":"230f570eb71e0a8d","repo":"bmad-code-org/BMAD-METHOD","slug":"extra-must-be-a-json-array-of-objects-230f57","errorCode":null,"errorMessage":"--extra must be a JSON array of objects","messagePattern":"--extra must be a JSON array of objects","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/core-skills/bmad-brainstorming/scripts/brain.py","lineNumber":72,"sourceCode":"    # utf-8-sig: tolerate BOM-prefixed catalogs (Excel \"CSV UTF-8\", Notepad)\n    with open(file, newline=\"\", encoding=\"utf-8-sig\") as f:\n        rows = list(csv.DictReader(f))\n    for r in rows:\n        for k in FIELDS:\n            r.setdefault(k, \"\")\n            r[k] = (r.get(k) or \"\").strip()\n    return rows\n\n\ndef load_extra(file: Path) -> list[dict]:\n    \"\"\"Merge-in techniques from a JSON overlay — a list of\n    {category, technique_name, description[, detail]} objects. This is how\n    customize.toml's `additional_techniques` become first-class across *every*\n    subcommand (categories/list/random/show/html), so the browse page and\n    category draws include them too, not just the in-chat flows.\"\"\"\n    data = json.loads(file.read_text(encoding=\"utf-8-sig\"))\n    if not isinstance(data, list):\n        raise ValueError(\"--extra must be a JSON array of objects\")\n    rows = []\n    for item in data:\n        if not isinstance(item, dict):\n            raise ValueError(f\"each --extra entry must be a JSON object, got: {item!r}\")\n        rows.append({\n            \"category\": str(item.get(\"category\", \"\")).strip(),\n            \"technique_name\": str(item.get(\"technique_name\", \"\")).strip(),\n            \"description\": str(item.get(\"description\", \"\")).strip(),\n            \"detail\": str(item.get(\"detail\") or \"\").strip(),\n            \"provenance\": str(item.get(\"provenance\") or \"\").strip(),\n            \"good_for\": str(item.get(\"good_for\") or \"\").strip(),\n            \"audience\": str(item.get(\"audience\") or \"\").strip(),\n        })\n    return rows\n\n\ndef merge_extra(rows: list[dict], extras: list[dict]) -> list[dict]:\n    \"\"\"Extras replace a catalog row with the same technique_name (case-insensitive),","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/core-skills/bmad-brainstorming/scripts/brain.py#L54-L90","documentation":"brain.py's `load_extra` reads a JSON overlay file of additional brainstorming techniques and requires the parsed content to be a top-level array. Unlike pick_methods.py this variant only accepts a file path (no inline literal). The error is raised when `json.loads` succeeds but the value is not a list — typically a single object or a bare scalar. The merged techniques feed every subcommand (categories/list/random/show/html), so a wrong root shape would corrupt the browse page too.","triggerScenarios":"Pointing `--extra` at a file whose JSON root is an object (`{\"category\":\"...\",\"technique_name\":\"...\"}`) or a string, instead of an array of such objects.","commonSituations":"Authoring one extra technique and forgetting the array brackets; exporting a single record from a tool that defaults to object serialization; a template that wraps the list under a key like `{\"techniques\": [...]}`.","solutions":["Make the file root an array: `[ {\"category\":\"...\",\"technique_name\":\"...\",\"description\":\"...\"}, ... ]`.","If the content is wrapped under a key (e.g. `{\"techniques\": [...]}`), unwrap it so the array is at the top level.","Validate with `python -m json.tool file.json` and confirm the first non-whitespace character is `[`.","Generate the overlay from `customize.toml`'s `additional_techniques` through the project emitter to guarantee the shape."],"exampleFix":"# before (extra_techniques.json)\n{\n  \"category\": \"divergent\",\n  \"technique_name\": \"SCAMPER\",\n  \"description\": \"...\"\n}\n\n# after\n[\n  {\n    \"category\": \"divergent\",\n    \"technique_name\": \"SCAMPER\",\n    \"description\": \"...\",\n    \"detail\": \"\",\n    \"provenance\": \"\",\n    \"good_for\": \"\",\n    \"audience\": \"\"\n  }\n]","handlingStrategy":"validation","validationCode":"import json\ndata = json.loads(Path(file).read_text(encoding='utf-8-sig'))\nassert isinstance(data, list), '--extra must be a JSON array of objects'","typeGuard":"def file_is_json_array(path) -> bool:\n    try:\n        return isinstance(json.loads(Path(path).read_text(encoding='utf-8-sig')), list)\n    except (OSError, json.JSONDecodeError):\n        return False","tryCatchPattern":"try:\n    extras = load_extra(Path(args.extra))\nexcept ValueError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["Make the file root an array; do not wrap the list under a key.","Confirm the first non-whitespace character is [.","Generate overlays from customize.toml's additional_techniques."],"tags":["json","validation","brainstorming","config","cli-input"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}