Skip to content

skill_manifest

Utilities for parsing manifest-based SKILL.md files.

SkillManifest dataclass

Parsed representation of a file-based skill manifest.

Source code in src/mada/core/skills/skill_manifest.py
@dataclass(frozen=True)
class SkillManifest:
    """Parsed representation of a file-based skill manifest."""

    name: str
    description: str
    content: str
    path: Path
    manifest_path: Path
    license: str = ""
    compatibility: str = ""
    allowed_tools: List[str] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)

SkillManifestError

Bases: Exception

Raised when a SKILL.md manifest cannot be parsed or validated.

Source code in src/mada/core/skills/skill_manifest.py
class SkillManifestError(Exception):
    """Raised when a SKILL.md manifest cannot be parsed or validated."""

parse_skill_manifest(skill_path)

Parse and validate the SKILL.md manifest for a single skill directory.

Parameters:

Name Type Description Default
skill_path Path

Directory containing the SKILL.md file.

required

Returns:

Type Description
SkillManifest

A parsed SkillManifest instance.

Raises:

Type Description
SkillManifestError

If the manifest is missing, malformed, or fails validation.

Source code in src/mada/core/skills/skill_manifest.py
def parse_skill_manifest(skill_path: Path) -> SkillManifest:
    """
    Parse and validate the SKILL.md manifest for a single skill directory.

    Args:
        skill_path: Directory containing the SKILL.md file.

    Returns:
        A parsed SkillManifest instance.

    Raises:
        SkillManifestError: If the manifest is missing, malformed, or fails
            validation.
    """
    skill_path = Path(skill_path).resolve()
    manifest_path = skill_path / "SKILL.md"
    LOG.debug(f"Parsing skill manifest at '{manifest_path}'")

    if not manifest_path.exists():
        raise SkillManifestError(
            f"Skill directory '{skill_path}' does not contain required SKILL.md."
        )

    if not manifest_path.is_file():
        raise SkillManifestError(
            f"Manifest path '{manifest_path}' exists but is not a file."
        )

    raw_text = manifest_path.read_text(encoding="utf-8")
    frontmatter_text, content = _extract_frontmatter_parts(raw_text)
    manifest_data = _parse_frontmatter(frontmatter_text)

    unknown_fields = sorted(set(manifest_data) - SUPPORTED_SKILL_MANIFEST_FIELDS)
    if unknown_fields:
        raise SkillManifestError(
            f"Skill manifest '{manifest_path}' has unsupported fields: "
            f"{', '.join(unknown_fields)}."
        )

    name = _parse_string_field(
        manifest_data,
        "name",
        manifest_path,
        required=True,
    )
    description = _parse_string_field(
        manifest_data,
        "description",
        manifest_path,
        required=True,
    )
    license_value = _parse_string_field(manifest_data, "license", manifest_path)
    compatibility = _parse_string_field(manifest_data, "compatibility", manifest_path)
    allowed_tools = _parse_allowed_tools(
        manifest_data.get("allowed_tools"),
        manifest_path,
    )
    metadata = manifest_data.get("metadata", {})

    if not content:
        raise SkillManifestError(
            f"Skill manifest '{manifest_path}' must include non-empty markdown content "
            "after the YAML frontmatter."
        )

    if skill_path.name != name:
        raise SkillManifestError(
            f"Skill directory name '{skill_path.name}' must match manifest name '{name}'."
        )

    if metadata is None:
        metadata = {}
    if not isinstance(metadata, dict):
        raise SkillManifestError(
            f"Skill manifest '{manifest_path}' has invalid 'metadata': expected a mapping."
        )

    LOG.debug(
        f"Parsed skill '{name}' with {len(allowed_tools) or 'all'} allowed tool(s)"
    )
    return SkillManifest(
        name=name,
        description=description,
        content=content,
        path=skill_path,
        manifest_path=manifest_path,
        license=license_value,
        compatibility=compatibility,
        allowed_tools=allowed_tools,
        metadata=metadata,
    )