{"record":{"id":"6fc02487f7e4aa83","repo":"github/spec-kit","slug":"missing-requires-speckit-version","errorCode":null,"errorMessage":"Missing requires.speckit_version","messagePattern":"Missing requires\\.speckit_version","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":350,"sourceCode":"                    \"Invalid extension.category: must be a non-empty string\"\n                )\n\n        # Validate optional effect field\n        if \"effect\" in ext:\n            if not isinstance(ext[\"effect\"], str) or ext[\"effect\"] not in VALID_EFFECTS:\n                raise ValidationError(\n                    f\"Invalid extension.effect '{ext.get('effect')}': \"\n                    f\"must be one of {sorted(VALID_EFFECTS)}\"\n                )\n\n        # Validate requires section\n        requires = self.data[\"requires\"]\n        if not isinstance(requires, dict):\n            raise ValidationError(\n                f\"Invalid requires: expected a mapping, got {type(requires).__name__}\"\n            )\n        if \"speckit_version\" not in requires:\n            raise ValidationError(\"Missing requires.speckit_version\")\n        # Presence alone is not enough: check_compatibility() feeds this value to\n        # ``SpecifierSet(required)``, guarded only by ``except InvalidSpecifier``,\n        # which a non-string escapes two different ways. A float/int/bool/None\n        # raises TypeError from the constructor, while a list or dict is an\n        # *iterable*, so SpecifierSet accepts it and the failure surfaces much\n        # later as ``AttributeError: 'str' object has no attribute 'filter'`` from\n        # inside .contains(). Neither is a CompatibilityError, so both bypass the\n        # CLI's \"Compatibility Error\" handler and exit 1 with a raw traceback\n        # naming no field. An unquoted ``speckit_version: 1.0`` is an easy YAML\n        # slip. Mirrors the sibling IntegrationDescriptor, which already requires\n        # a non-empty string here.\n        if (\n            not isinstance(requires[\"speckit_version\"], str)\n            or not requires[\"speckit_version\"].strip()\n        ):\n            raise ValidationError(\n                \"Invalid requires.speckit_version: expected a non-empty string, \"\n                f\"got {type(requires['speckit_version']).__name__}\"","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L332-L368","documentation":"The requires mapping must contain the key speckit_version. This is the only required entry in requires and is what check_compatibility() later feeds into packaging SpecifierSet to decide whether the installed Spec Kit version can run the extension.","triggerScenarios":"A manifest has `requires: {}`, or a misspelled key like `requires:\\n  speckit-ver: '>=1'` or `specify_version:`. The membership test `\"speckit_version\" not in requires` fires.","commonSituations":"Typos in the key (speckit-ver, speckitversion, specify_version), or an empty requires block left from a template.","solutions":["Add `speckit_version` under requires with a PEP 440 specifier string, e.g. `speckit_version: \">=1.0.0\"`.","Fix the key spelling — it must be exactly speckit_version, snake_case.","Pick the specifier from your extension's actual minimum supported Spec Kit version."],"exampleFix":"# before\nrequires:\n  speckit-ver: '>=1.0.0'\n\n# after\nrequires:\n  speckit_version: \">=1.0.0\"","handlingStrategy":"validation","validationCode":"def has_speckit_version(data: dict) -> bool:\n    return \"speckit_version\" in data.get(\"requires\", {})","typeGuard":null,"tryCatchPattern":null,"preventionTips":["The key is exactly `speckit_version` — snake_case, no hyphens.","Scaffold new extensions from a bundled one so required keys come pre-filled."],"tags":["extension-manifest","validation","required-fields"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}