{"record":{"id":"009c3f778c4128e0","repo":"github/spec-kit","slug":"each-command-entry-in-provides-commands-must-be","errorCode":null,"errorMessage":"Each command entry in 'provides.commands' must be a mapping","messagePattern":"Each command entry in 'provides\\.commands' must be a mapping","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":445,"sourceCode":"                        )\n                    if \"priority\" in entry:\n                        priority = entry[\"priority\"]\n                        if not isinstance(priority, int) or isinstance(priority, bool):\n                            raise ValidationError(\n                                f\"Hook '{hook_name}' has invalid 'priority': \"\n                                \"must be an integer\"\n                            )\n                        if priority < 1:\n                            raise ValidationError(\n                                f\"Hook '{hook_name}' has invalid 'priority': \"\n                                \"must be >= 1\"\n                            )\n\n        # Validate commands; track renames so hook references can be rewritten.\n        rename_map: Dict[str, str] = {}\n        for cmd in commands:\n            if not isinstance(cmd, dict):\n                raise ValidationError(\n                    \"Each command entry in 'provides.commands' must be a mapping\"\n                )\n            if \"name\" not in cmd or \"file\" not in cmd:\n                raise ValidationError(\"Command missing 'name' or 'file'\")\n            # The pattern match below would raise a bare TypeError on a\n            # non-string name (``name: 2``), escaping the ValidationError\n            # contract. The 'file' field needs no check here:\n            # relative_extension_path_violation() below already rejects a\n            # non-string value.\n            if not isinstance(cmd[\"name\"], str):\n                raise ValidationError(\n                    f\"Invalid command name: expected a string, \"\n                    f\"got {type(cmd['name']).__name__}\"\n                )\n\n            # Validate the 'file' field at manifest-load time using the single\n            # shared policy in relative_extension_path_violation(), so manifest\n            # validation cannot drift from the runtime registrar guard. This is","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L427-L463","documentation":"Every command mapping in provides.commands must contain both name and file keys. name is the slash-command identifier agents invoke; file is the template file installed for it. Either key missing aborts validation of the manifest.","triggerScenarios":"A command entry `{name: check}` with no file, or `{file: commands/check.md}` with no name. The `\"name\" not in cmd or \"file\" not in cmd` check fires.","commonSituations":"Author omits file assuming the loader infers it from name, or drops name for a file-only entry. Typos (filename, cmd) also trigger it.","solutions":["Add both keys: `- name: check\\n  file: commands/check.md`.","Point file at an existing file inside the extension directory.","Verify key spelling — exact literals `name` and `file` are required."],"exampleFix":"# before\nprovides:\n  commands:\n    - name: check\n\n# after\nprovides:\n  commands:\n    - name: check\n      file: commands/check.md","handlingStrategy":"validation","validationCode":"def commands_have_name_and_file(data: dict) -> bool:\n    cmds = (data.get(\"provides\") or {}).get(\"commands\") or []\n    return all(\"name\" in c and \"file\" in c for c in cmds if isinstance(c, dict))","typeGuard":"def is_complete_command_entry(v: object) -> bool:\n    return isinstance(v, dict) and isinstance(v.get(\"name\"), str) and \"file\" in v","tryCatchPattern":null,"preventionTips":["Both `name` and `file` are mandatory per command entry.","Point file at a real file in the extension directory; the path is also validated for containment."],"tags":["extension-manifest","validation","commands","required-fields"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}