import re from collections.abc import Mapping from pathlib import PurePosixPath from types import MappingProxyType from typing import ClassVar from typing import Final from pydantic import BaseModel from pydantic import ConfigDict from pydantic import field_validator SKILL_ID_RE: Final[re.Pattern[str]] = re.compile(r"^[a-z][a-z0-9-]*$") SEMVER_RE: Final[re.Pattern[str]] = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+][0-9A-Za-z.-]+)?$") class StrictFrozenModel(BaseModel): """Immutable base model with strict field validation rules.""" model_config: ClassVar[ConfigDict] = ConfigDict( extra="forbid", frozen=True, validate_by_alias=True, validate_by_name=True, str_strip_whitespace=True, ) def frozen_mapping[K, V](value: Mapping[K, V] | None = None) -> Mapping[K, V]: return MappingProxyType(dict(value) if value is not None else {}) class ReferenceEntry(StrictFrozenModel): """Reference metadata for a markdown file within a skill.""" path: str mime_type: str = "text/markdown" title: str | None = None @field_validator("path") @classmethod def validate_reference_path(cls, value: str) -> str: path = PurePosixPath(value) if path.is_absolute() or ".." in path.parts: raise ValueError("reference path must be a relative in-skill path") if not str(path).startswith("references/"): raise ValueError("reference path must stay under references/") if path.suffix.lower() != ".md": raise ValueError("reference path must target a markdown file") return path.as_posix()