How YAML Frontmatter Parsing Works for Installing AI Agent Skills in Spec-Kit
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 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, 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, 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 assembly occurs at lines 1111-1114, concatenating:
-
Opening
--- -
The generated YAML frontmatter
-
Closing
--- -
A headline (
# Speckit ... Skill) -
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, 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 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:
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:
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:
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 fromAGENT_CONFIGor falls back totemplates/commands/(lines 1018-1023). - Safe YAML Parsing: The parser splits on
---delimiters and usesyaml.safe_loadwith exception handling for malformed content (lines 1054-1066). - Description Enrichment: The
SKILL_DESCRIPTIONSmapping overrides template frontmatter for better human readability (lines 1084-1086). - Additive Installation: Skills are written only if
SKILL.mddoes not exist, making the process idempotent and safe for re-runs (lines 1115-1122). - Dual Parsers: Both
install_ai_skills()andCommandRegistrar.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, 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →