{"record":{"id":"e27f27b8c22df505","repo":"github/spec-kit","slug":"integration-must-provide-at-least-one-command-or-s","errorCode":null,"errorMessage":"Integration must provide at least one command or script","messagePattern":"Integration must provide at least one command or script","errorType":"validation","errorClass":"IntegrationDescriptorError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/catalog.py","lineNumber":781,"sourceCode":"                    )\n\n        provides = self.data[\"provides\"]\n        if not isinstance(provides, dict):\n            raise IntegrationDescriptorError(\n                \"'provides' must be a mapping\"\n            )\n        commands = provides.get(\"commands\", [])\n        scripts = provides.get(\"scripts\", [])\n        if \"commands\" in provides and not isinstance(commands, list):\n            raise IntegrationDescriptorError(\n                \"Invalid provides.commands: expected a list\"\n            )\n        if \"scripts\" in provides and not isinstance(scripts, list):\n            raise IntegrationDescriptorError(\n                \"Invalid provides.scripts: expected a list\"\n            )\n        if not commands and not scripts:\n            raise IntegrationDescriptorError(\n                \"Integration must provide at least one command or script\"\n            )\n        for cmd in commands:\n            if not isinstance(cmd, dict):\n                raise IntegrationDescriptorError(\n                    \"Each command entry must be a mapping\"\n                )\n            if \"name\" not in cmd or \"file\" not in cmd:\n                raise IntegrationDescriptorError(\n                    \"Command entry missing 'name' or 'file'\"\n                )\n            cmd_name = cmd[\"name\"]\n            cmd_file = cmd[\"file\"]\n            if not isinstance(cmd_name, str) or not cmd_name.strip():\n                raise IntegrationDescriptorError(\n                    \"Command entry 'name' must be a non-empty string\"\n                )\n            if not isinstance(cmd_file, str) or not cmd_file.strip():","sourceCodeStart":763,"sourceCodeEnd":799,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/catalog.py#L763-L799","documentation":"Raised by IntegrationDescriptor._validate() (src/specify_cli/integrations/catalog.py:781) when 'provides' contains neither commands nor scripts (both default to empty lists). Every integration descriptor must declare at least one artifact so the catalog can guarantee an integration actually installs something.","triggerScenarios":"IntegrationDescriptor(path) on a descriptor where 'provides:' is an empty mapping, or where commands/scripts keys are present but set to empty lists — e.g. 'provides: {}' or 'provides: {commands: [], scripts: []}'.","commonSituations":"Starting a descriptor from a template and not yet filling in the provides section; accidentally commenting out the command list; trimming a descriptor down for testing and removing all entries.","solutions":["Add at least one entry to provides.commands (a mapping with 'name' and 'file') or provides.scripts (a relative path string).","If the integration genuinely provides nothing, reconsider whether it should be an integration descriptor at all.","Reload the descriptor to verify validation passes."],"exampleFix":"# before\nprovides: {}\n\n# after\nprovides:\n  commands:\n    - name: plan\n      file: commands/plan.md","handlingStrategy":"validation","validationCode":"import yaml\n\ndef has_provision(data):\n    provides = data.get(\"provides\") or {}\n    commands = provides.get(\"commands\") or []\n    scripts = provides.get(\"scripts\") or []\n    return bool(commands) or bool(scripts)\n\nassert has_provision(yaml.safe_load(open(\"integration.yml\")))","typeGuard":"def provides_something(provides: dict) -> bool:\n    return bool(provides.get(\"commands\")) or bool(provides.get(\"scripts\"))","tryCatchPattern":null,"preventionTips":["Treat provides.commands or provides.scripts as mandatory content before shipping a descriptor.","Add a pre-submit check that rejects empty provides blocks.","Use a descriptor template that already includes one command entry."],"tags":["yaml","validation","integration-descriptor","spec-kit"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}