{"record":{"id":"0b5eeda03262aa4e","repo":"github/spec-kit","slug":"provides-must-be-a-mapping-when-present","errorCode":null,"errorMessage":"'provides' must be a mapping when present.","messagePattern":"'provides' must be a mapping when present\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/bundler/models/manifest.py","lineNumber":139,"sourceCode":"            tools=_parse_str_list(requires_raw.get(\"tools\"), \"requires.tools\"),\n            mcp=_parse_str_list(requires_raw.get(\"mcp\"), \"requires.mcp\"),\n        )\n\n        integration = None\n        integration_raw = data.get(\"integration\")\n        # Mirror the requires/provides guards above: a present-but-non-mapping\n        # 'integration' (e.g. a bare string \"copilot\") was silently dropped,\n        # leaving the bundle wrongly integration-agnostic. Reject it instead.\n        if integration_raw is not None and not isinstance(integration_raw, dict):\n            raise BundlerError(\"'integration' must be a mapping when present.\")\n        if isinstance(integration_raw, dict) and integration_raw.get(\"id\"):\n            integration = IntegrationRef(id=str(integration_raw[\"id\"]).strip())\n\n        provides = data.get(\"provides\")\n        if provides is None:\n            provides = {}\n        elif not isinstance(provides, dict):\n            raise BundlerError(\"'provides' must be a mapping when present.\")\n\n        tags_raw = data.get(\"tags\")\n        if tags_raw is None:\n            tags_raw = []\n        else:\n            tags_raw = _parse_str_list(tags_raw, \"tags\")\n\n        manifest = cls(\n            schema_version=schema_version,\n            bundle=meta,\n            requires=requires,\n            integration=integration,\n            extensions=_parse_refs(\"extensions\", provides.get(\"extensions\")),\n            presets=_parse_refs(\"presets\", provides.get(\"presets\")),\n            steps=_parse_refs(\"steps\", provides.get(\"steps\")),\n            workflows=_parse_refs(\"workflows\", provides.get(\"workflows\")),\n            tags=tuple(str(t) for t in tags_raw),\n        )","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/bundler/models/manifest.py#L121-L157","documentation":"Raised by BundleManifest.from_dict when an optional 'provides' key is present but is not a YAML mapping. 'provides' groups component lists (skills, commands, templates, ...) keyed by kind; a null/absent value defaults to empty, but any non-mapping type is malformed and rejected.","triggerScenarios":"A manifest contains 'provides:' followed by a list (e.g. a flat list of component entries) or a scalar. Parsing it with BundleManifest.from_dict raises before component-ref parsing begins.","commonSituations":"Flattening provides into a list because the kinds feel redundant; hand-writing a first manifest; copy-paste from the 'requires' block's list style.","solutions":["Nest component lists under kind keys inside a 'provides' mapping.","Remove 'provides' if the bundle provides nothing — absence is valid."],"exampleFix":"# before (bundle.yml)\nprovides:\n  - id: my-skill\n    kind: skill\n\n# after\nprovides:\n  skills:\n    - id: my-skill","handlingStrategy":"type-guard","validationCode":"def provides_shape_ok(data: dict) -> bool:\n    p = data.get(\"provides\")\n    return p is None or isinstance(p, dict)","typeGuard":"def is_provides_mapping(raw: object) -> bool:\n    return raw is None or isinstance(raw, dict)","tryCatchPattern":"try:\n    manifest = BundleManifest.from_file(p)\nexcept BundlerError as e:\n    if \"'provides' must be a mapping\" in str(e):\n        # restructure to kind-keyed lists under a mapping\n        ...","preventionTips":["'provides' maps component kinds to lists; it is never a flat list itself.","Mirror the shape: provides: {skills: [...], commands: [...]}."],"tags":["bundler","manifest","yaml","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}