# How YAML Frontmatter Parsing Works for Installing AI Agent Skills in Spec-Kit

> Learn how SpecKit parses YAML frontmatter to install AI agent skills. Understand the process of splitting Markdown, safely loading YAML, and generating new SKILL.md files for agentskills.io.

- Repository: [GitHub/spec-kit](https://github.com/github/spec-kit)
- Tags: internals
- Published: 2026-03-05

---

**When running `specify init ... --ai-skills`, Spec-Kit parses YAML frontmatter from command templates by splitting Markdown files on `---` delimiters, safely loading the YAML block with `yaml.safe_load`, merging descriptions from the `SKILL_DESCRIPTIONS` mapping, and generating new [`SKILL.md`](https://github.com/github/spec-kit/blob/main/SKILL.md) files with agentskills.io-compatible frontmatter using `yaml.safe_dump`.**

The `specify init` command with the `--ai-skills` flag enables automatic installation of agent skills from command templates in the Spec-Kit repository. This process relies on robust YAML frontmatter parsing to extract metadata from Markdown files and transform them into standardized skill definitions. The entire workflow is implemented in the `install_ai_skills()` function within [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py), handling everything from template discovery to additive file installation.

## The AI Skills Installation Pipeline

The `install_ai_skills()` function orchestrates a ten-step pipeline that converts command templates into installable agent skills. This process begins with template resolution and ends with additive file writing, with YAML frontmatter parsing at its core.

### Template Discovery and File Enumeration

The function first resolves the source directory containing command templates. According to lines 1018-1023 in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py), it constructs `templates_dir` from the selected agent's folder defined in `AGENT_CONFIG` and its `commands_subdir`. If this directory contains no `*.md` files, the system falls back to the repository-wide `templates/commands/` directory. The function then collects and sorts all Markdown files at line 1033, ensuring deterministic processing order.

Each file is read as UTF-8 text at line 1052, preparing the content for frontmatter extraction.

### Extracting and Parsing YAML Frontmatter

The core parsing logic occurs at lines 1054-1066. If a file begins with `---`, the content is split at the second occurrence of the delimiter. The slice between these markers—accessed as `parts[1]`—is passed to `yaml.safe_load` for safe deserialization.

When the closing `---` delimiter is missing, the implementation at lines 1062-1067 treats the entire file as plain content, prints a warning, and continues with an empty frontmatter dictionary. This ensures malformed delimiters do not crash the installation process.

### Command Normalization and Description Enrichment

After parsing, the function normalizes command names by stripping any `speckit.` prefix at lines 1071-1077, yielding clean base names like `speckit-<cmd>` for the skill directory structure.

The description selection logic at lines 1084-1086 implements a priority system: it first checks `frontmatter["description"]`, but if the command exists in the global `SKILL_DESCRIPTIONS` mapping (defined at lines 558-569), that richer description overrides the template value. This allows curated descriptions to take precedence over raw template metadata.

### Generating Agentskills.io-Compatible Output

At lines 1097-1107, the function constructs a new frontmatter dictionary compliant with agentskills.io specifications. This includes:

- **name**: The normalized skill identifier
- **description**: The enriched description from the previous step
- **compatibility**: Project structure requirements
- **metadata**: Author and source file tracking

The dictionary is serialized using `yaml.safe_dump` with `sort_keys=False` to preserve logical ordering and ensure proper quoting and escaping.

### Assembly and Additive Installation

The final [`SKILL.md`](https://github.com/github/spec-kit/blob/main/SKILL.md) assembly occurs at lines 1111-1114, concatenating:

1. Opening `---`
2. The generated YAML frontmatter
3. Closing `---`
4. A headline (`# Speckit ... Skill`)

5. The original body content (everything after the original frontmatter)

The write operation at lines 1115-1122 creates the directory structure `<agent-folder>/skills/speckit-<cmd>/` if needed, but only writes the file if it does not already exist. This makes the installation **additive** and safe for repeated runs without overwriting existing customizations.

## Error Handling and Edge Cases

The parser implements graceful degradation for several failure modes. When `yaml.safe_load` raises a `YAMLError` due to malformed YAML, the exception is caught and the frontmatter is replaced with an empty dictionary, allowing installation to continue with fallback descriptions. Missing or empty frontmatter (such as `---\n---`) similarly results in an empty dictionary, with the system defaulting to `SKILL_DESCRIPTIONS` or generic descriptions.

If neither the agent-specific nor fallback template directories contain Markdown files, the function prints a warning and returns `False` at line 1124, preventing empty skill directory creation.

These behaviors are verified in [`tests/test_ai_skills.py`](https://github.com/github/spec-kit/blob/main/tests/test_ai_skills.py), including `test_empty_yaml_frontmatter`, `test_malformed_yaml_frontmatter`, and `test_skills_install_for_all_agents`.

## The Extension Parser Alternative

Spec-Kit maintains two frontmatter parsers for different contexts. While `install_ai_skills()` handles extracted command templates, the `CommandRegistrar` class in [`src/specify_cli/extensions.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/extensions.py) provides a similar `parse_frontmatter()` method at lines 88-107 for registering extension-provided commands. Both implementations share the same core strategy—splitting on the first two `---` markers and using `yaml.safe_load`—but operate on different file sources within their respective subsystems.

## Implementation Examples

### Minimal Frontmatter Extraction

The following abstraction illustrates the core extraction logic from lines 1054-1066:

```python
def _extract_frontmatter(content: str) -> tuple[dict, str]:
    if not content.startswith("---"):
        return {}, content

    # Find the closing delimiter after the opening one

    end = content.find("---", 3)
    if end == -1:
        return {}, content

    fm_raw = content[3:end].strip()
    body = content[end + 3 :].strip()

    try:
        fm = yaml.safe_load(fm_raw) or {}
    except yaml.YAMLError:
        fm = {}  # malformed YAML → empty dict

    return fm, body

```

### Complete Skill Generation Pipeline

This example demonstrates the transformation from parsed frontmatter to final output, abstracting lines 1097-1114:

```python
def _make_skill_md(command_name: str, frontmatter: dict, body: str) -> str:
    # Enhanced description with override support

    description = SKILL_DESCRIPTIONS.get(
        command_name,
        frontmatter.get("description", f"Spec-kit workflow command: {command_name}")
    )

    # Build agentskills.io frontmatter

    fm = {
        "name": f"speckit-{command_name}",
        "description": description,
        "compatibility": "Requires spec-kit project structure with .specify/ directory",
        "metadata": {
            "author": "github-spec-kit",
            "source": f"templates/commands/{command_name}.md",
        },
    }
    fm_yaml = yaml.safe_dump(fm, sort_keys=False).strip()

    # Assemble final document

    return (
        f"---\n{fm_yaml}\n---\n\n"
        f"# Speckit {command_name.title()} Skill\n\n"

        f"{body}\n"
    )

```

### High-Level Installation Flow

The complete orchestration logic spans lines 1018-1144 in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py):

```python
def install_ai_skills(project_path: Path, selected_ai: str) -> bool:
    templates_dir = _find_templates_dir(project_path, selected_ai)
    command_files = sorted(templates_dir.glob("*.md"))
    
    skills_dir = _get_skills_dir(project_path, selected_ai)  # lines 727-747

    skills_dir.mkdir(parents=True, exist_ok=True)

    for cmd_file in command_files:
        content = cmd_file.read_text(encoding="utf-8")
        fm, body = _extract_frontmatter(content)  # lines 1054-1066

        
        # Normalize name (lines 1071-1077)

        skill_name = f"speckit-{cmd_file.stem.removeprefix('speckit.')}"
        skill_dir = skills_dir / skill_name
        skill_dir.mkdir(parents=True, exist_ok=True)

        # Additive write (lines 1115-1122)

        skill_path = skill_dir / "SKILL.md"
        if not skill_path.exists():
            skill_path.write_text(
                _make_skill_md(skill_name[8:], fm, body), 
                encoding="utf-8"
            )

```

## Summary

- **Template Resolution**: `install_ai_skills()` discovers command templates from `AGENT_CONFIG` or falls back to `templates/commands/` (lines 1018-1023).
- **Safe YAML Parsing**: The parser splits on `---` delimiters and uses `yaml.safe_load` with exception handling for malformed content (lines 1054-1066).
- **Description Enrichment**: The `SKILL_DESCRIPTIONS` mapping overrides template frontmatter for better human readability (lines 1084-1086).
- **Additive Installation**: Skills are written only if [`SKILL.md`](https://github.com/github/spec-kit/blob/main/SKILL.md) does not exist, making the process idempotent and safe for re-runs (lines 1115-1122).
- **Dual Parsers**: Both `install_ai_skills()` and `CommandRegistrar.parse_frontmatter()` implement similar splitting logic for different contexts.

## Frequently Asked Questions

### What happens if a command template lacks YAML frontmatter?

If a Markdown file does not start with `---` or lacks a closing delimiter, the parser treats the entire file as body content and returns an empty dictionary for the frontmatter. According to lines 1062-1067 in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py), the installation proceeds using fallback descriptions from `SKILL_DESCRIPTIONS` or a generic default, ensuring the skill is still installed.

### How does Spec-Kit handle malformed YAML in frontmatter blocks?

When `yaml.safe_load` raises a `YAMLError` due to invalid YAML syntax, the exception is caught and the frontmatter is replaced with an empty dictionary. As implemented at lines 1054-1066, this allows the installation to continue gracefully, using available fallback descriptions rather than failing the entire process.

### Why does the installation process use `yaml.safe_dump` instead of string concatenation?

The function uses `yaml.safe_dump` at lines 1097-1107 to serialize the agentskills.io frontmatter because it guarantees proper YAML syntax, correct escaping of special characters, and consistent formatting. This is safer than manual string concatenation and ensures compatibility with YAML parsers in agent skill ecosystems.

### Where does Spec-Kit install the generated SKILL.md files?

The target directory is determined by `_get_skills_dir()` at lines 727-747, which respects overrides from `AGENT_SKILLS_DIR_OVERRIDES` before defaulting to `<agent-folder>/skills/speckit-<cmd>/`. The function creates parent directories as needed and performs an additive installation, writing files only if they do not already exist at lines 1115-1122.