{"record":{"id":"c5a67ed6f912bc5b","repo":"github/spec-kit","slug":"singular-capitalize-missing-name-or-file","errorCode":null,"errorMessage":"{singular.capitalize()} missing 'name' or 'file'","messagePattern":"(.+?) missing 'name' or 'file'","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":593,"sourceCode":"        distinguishes template vs script) and 'strategy' (extension-provided\n        artifacts are always 'replace' -- see the forced-replace resolver\n        behavior for extension layers in presets/__init__.py). A present\n        'strategy' key is rejected rather than silently ignored, so an author\n        who copies a preset-style entry gets a clear error instead of a\n        silently-dropped field. Duplicate names within a section are also\n        rejected: the resolver returns the first matching entry by name\n        (``PresetResolver._extension_manifest_declared_template``), so a\n        later duplicate would be silently unreachable while still being\n        exposed by ``ExtensionManifest.templates``/``.scripts``.\n        \"\"\"\n        seen_names: set[str] = set()\n        for entry in entries:\n            if not isinstance(entry, dict):\n                raise ValidationError(\n                    f\"Each entry in 'provides.{section}' must be a mapping\"\n                )\n            if \"name\" not in entry or \"file\" not in entry:\n                raise ValidationError(f\"{singular.capitalize()} missing 'name' or 'file'\")\n\n            name = entry[\"name\"]\n            if not isinstance(name, str):\n                raise ValidationError(\n                    f\"Invalid {singular} name: expected a string, got {type(name).__name__}\"\n                )\n            if not VALID_EXTENSION_ARTIFACT_NAME_PATTERN.match(name):\n                raise ValidationError(\n                    f\"Invalid {singular} name '{name}': \"\n                    \"must be lowercase alphanumeric with hyphens only\"\n                )\n            if name in seen_names:\n                raise ValidationError(\n                    f\"Duplicate {singular} name '{name}' in 'provides.{section}'\"\n                )\n            seen_names.add(name)\n\n            file_value = entry[\"file\"]","sourceCodeStart":575,"sourceCodeEnd":611,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L575-L611","documentation":"A provides.templates/scripts entry is a mapping but is missing the required 'name' or 'file' key. Both are mandatory: 'name' is the artifact slug used by the resolver (e.g. PresetResolver._extension_manifest_declared_template returns the first match by name) and 'file' is the relative path inside the extension directory.","triggerScenarios":"{\"file\": \"templates/plan.md\"} (no name), {\"name\": \"plan\"} (no file), or a typo'd key like \"filename\". Raised immediately after the mapping check in the artifact validator.","commonSituations":"Typos in key names ('path' instead of 'file', 'id' instead of 'name'); partial copy-paste from another manifest; key silently dropped by a template engine leaving an empty value context.","solutions":["Add both keys to every entry: \"name\": \"<slug>\", \"file\": \"<relative-path>\".","Check for typos — the validator wants exactly 'name' and 'file'.","If a template engine builds the manifest, assert {'name','file'} <= set(entry) for each entry before writing."],"exampleFix":"// before\n{ \"name\": \"plan\", \"path\": \"templates/plan.md\" }\n// after\n{ \"name\": \"plan\", \"file\": \"templates/plan.md\" }","handlingStrategy":"validation","validationCode":"for section in (\"templates\", \"scripts\"):\n    for entry in manifest.get(\"provides\", {}).get(section, []):\n        if isinstance(entry, dict):\n            missing = {\"name\", \"file\"} - entry.keys()\n            assert not missing, f\"{section} entry missing {missing}: {entry!r}\"","typeGuard":"def has_name_and_file(entry: object) -> bool:\n    return isinstance(entry, dict) and \"name\" in entry and \"file\" in entry","tryCatchPattern":"try:\n    ExtensionManifest.load(path)\nexcept ValidationError as e:\n    if \"missing 'name' or 'file'\" in str(e):\n        # add the missing key (exact names: 'name', 'file') and retry\n        ...","preventionTips":["Memorize the two mandatory keys: name (slug) and file (relative path).","Watch for typo'd keys like 'path' or 'id'.","Assert {'name','file'} <= set(entry) in manifest generators."],"tags":["extensions","manifest","validation","required-field"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}