Markdown Post Frontmatter Validation
Concept of the skill
What it is: The project-specific validation discipline for the YAML frontmatter on markdown posts.
Mental model: Frontmatter is a typed interface between content files and the site runtime.
Why it exists: Routing, indexes, tags, dates, and previews all depend on frontmatter being complete and unambiguous.
What it is NOT: It is not general YAML schema design, parser performance work, or debugging a specific failed build.
Adjacent concepts: Content schemas, slug derivation, controlled vocabularies, date normalization.
One-line analogy: It is the checklist that makes every post safe for the build to consume.
Common misconception: If the markdown renders, the frontmatter is good enough; metadata can break listing pages even when body content renders.
Coverage
- Required-field enforcement — every post must declare
title, date, slug, and tags; missing fields fail the build at parse time
- Date format discipline — ISO 8601 with explicit timezone (
2026-05-06T12:00:00Z); ambiguous formats like 2026-05-06 or 5/6/26 are rejected
- Slug-to-path consistency — the
slug field must match the post's directory name; out-of-sync slugs cause silent route conflicts
- Controlled-vocabulary tagging — every tag in the post's
tags array must appear in lib/content/tag-vocabulary.ts; lowercase, hyphen-separated, no synonyms
- Schema evolution — when
lib/content/schema.ts changes, every post's frontmatter is re-validated; existing posts that violate the new schema are flagged before the next build runs
- Reserved-field protection — fields like
_id, _internal, or any underscore-prefixed key are reserved for the build pipeline and rejected in author-facing frontmatter
Philosophy of the skill
The frontmatter block is the contract every post makes with the site's index, the router, and the renderer. If that contract is loose — if posts can omit fields, use ambiguous dates, or invent ad-hoc tags — the index drifts, routes silently overlap, and the search surface degrades. The cost of catching frontmatter bugs at build time is one re-run; the cost of catching them in production is a broken page or a missing entry in the archive. The rule is: validate at parse time, fail loud, and keep the schema small enough that authors can hold it in their head.
Key Files
| File |
Purpose |
content/posts/_template.md |
The canonical template every new post copies — its frontmatter is the worked example of every required field |
lib/content/schema.ts |
The TypeScript schema (Zod or equivalent) that runtime validation calls |
lib/content/parse-frontmatter.ts |
The thin wrapper that reads the YAML block and runs schema.parse() — the failure surface for build-time errors |
Verification
Before merging any change to a post's frontmatter or to the schema:
Do NOT Use When
| Use instead |
When |
| (a generic schema-design skill) |
The task is designing a new YAML schema for an unrelated domain |
debugging |
A specific build is failing and you need to reproduce the validation error from logs |
documentation |
The task is writing a runbook or contributor doc about the frontmatter format |
refactor |
The task is restructuring parse-frontmatter.ts without changing the validation contract |
1---2# name: stable skill identifier. Match the skill directory name or the final namespace segment.3# Lowercase letters/numbers with hyphen, slash, or colon separators.4name: markdown-post-frontmatter-validation5# description: routing-facing summary of what the skill covers and when it activates.6# Include concrete triggers and an explicit negative boundary; keep routing semantics out of prose-only ambiguity.7description: "Use when authoring or reviewing the frontmatter of a markdown post — checking required fields (title, date, slug, tags), validating against the content schema in `lib/content/schema.ts`, catching ambiguous date formats or tags not in the controlled vocabulary, and ensuring the slug matches the file path. Activate this skill whenever the task touches files under `content/posts/**/*.md`, the `parsePostFrontmatter()` helper, or any code path that reads YAML frontmatter from a content file. Do NOT use for general YAML schema design (use a generic schema-design skill) or for chasing a specific build-time validation failure (use debugging)."89# === v8 Classification (subject + public; polyhierarchy via subjects[]) — see ADR-0017 ===10# subject: primary browse shelf — what the skill teaches. One of twelve closed values:11# backend-engineering / frontend-engineering / software-architecture / data-engineering / agent-ops / ai-engineering /12# quality-assurance / design / reasoning-strategy / software-engineering-method / knowledge-organization / product-domain.13subject: backend-engineering14# public: publishability/private-data gate. Boolean.15# true = publishable/shareable; false = private and excluded from public export.16# Project anchoring is carried separately by non-empty `project[]` plus `grounding`.17public: false18# taxonomy_domain: optional hierarchical sub-path within `subject`. Slash-delimited19# lowercase kebab-case segments. rename of the original v8 `domain`. Remove when the flat20# `subject` is sufficient.21taxonomy_domain: content/markdown/frontmatter22# scope: free-text PRD-style statement of what the skill teaches and what it excludes.23# (v8 required; not an enum). Mirrors Coverage + Do NOT Use When at frontmatter level.24scope: "Markdown post frontmatter validation for the markdown-static-site example project — required fields, date format, slug consistency, controlled-vocabulary tagging."25# stability: lifecycle marker. One of:26# experimental (active development) / stable (production-ready) /27# frozen (no further changes expected) / deprecated.28# When `deprecated`, schema's allOf REQUIRES `superseded_by: <real-skill-name>`.29stability: experimental30# license: SPDX license identifier (e.g., MIT, Apache-2.0).31license: MIT32# compatibility: runtime compatibility object. Prefer structured fields33# (`agent_runtimes`, `node_version`) over free-text `notes`.34compatibility:35 agent_runtimes:36 - node37 node_version: ">=20"38 notes: "Astro / Next / Eleventy / Hugo all read YAML frontmatter; the validation pattern is portable across them."39allowed-tools: Read Grep40# keywords: semantic phrases for fuzzy router activation. v8 cap: max 10.41# Keep terms a user would actually type when starting a task in this skill's domain.42keywords:43 - markdown frontmatter44 - post frontmatter45 - frontmatter validation46 - content schema47 - parsePostFrontmatter48 - YAML frontmatter49 - title required50 - tag vocabulary51 - slug mismatch52 - date format53 - controlled vocabulary54 - markdown post metadata55# triggers: explicit-match activation phrases the router fires on literally.56# Use when label-based routing is intended; usually keywords + examples are enough.57triggers:58 - markdown-post-frontmatter-validation59# paths: glob array of code surfaces this skill governs. Supports gitignore-style60# negation. Each glob should map to ONE canonical skill. Omit if purely conceptual.61paths:62 - "content/posts/**/*.md"63 - "lib/content/schema.ts"64 - "lib/content/parse-frontmatter.ts"65 - "!**/*.test.md"66# examples: 2-5 realistic user prompts the skill SHOULD activate for.67# Written in the user's voice. Improves retrieval recall beyond keywords alone.68examples:69 - "validate the frontmatter of a new post against our content schema"70 - "why does the build fail when I add a tag like `Politics` (capital P)?"71 - "review this post's frontmatter — is the date format correct?"72 - "explain how the slug is derived from the file path"73# anti_examples: near-miss prompts that should route ELSEWHERE.74# Pair with relations.suppresses (or legacy boundary alias) to name the confusable territory's owner.75anti_examples:76 - "design a YAML schema for a different domain"77 - "the build is failing — what's the actual error?"78 - "rewrite parsePostFrontmatter for performance"79# relations: typed graph edges to sibling skills. Current fields:80# related (adjacency for browse / co-routing expansion) /81# suppresses (exclude listed skills from co-routing when THIS skill wins; write reason82# as "I own this exclusively over X", not "use X instead") /83# boundary (DEPRECATED alias of suppresses, retained for unmigrated skills) /84# verify_with (cross-check; co-loaded as one-hop expansion) /85# depends_on (composition; transitive — A→B→C loads all three) /86# broader / narrower (SKOS-style generalization) /87# disjoint_with (mutual exclusion for incompatible ownership).88relations:89 related:90 - documentation91 - debugging92 - refactor93 verify_with:94 - testing-strategy95# grounding: required when `project[]` is non-empty. Declares the truth sources96# the skill anchors to and the failure modes those sources prevent. Omit when the97# skill is universal-knowledge. `subject_matter` replaces v8 `domain_object`.98grounding:99 subject_matter: "Markdown post frontmatter — the YAML block at the top of every content file that drives the site's index, routing, and rendering"100 grounding_mode: repo_specific101 truth_sources:102 - content/posts/_template.md103 - lib/content/schema.ts104 - lib/content/parse-frontmatter.ts105 failure_modes:106 - missing_required_title_field107 - ambiguous_date_format_no_timezone108 - tag_not_in_controlled_vocabulary109 - slug_mismatch_with_file_path110 - frontmatter_block_not_terminated111 evidence_priority: repo_code_first112# project: projects this skill is linked to. Array of {handle, role} objects.113# Non-empty project[] anchors the skill to a project and requires `grounding`.114# Suggested role values: source-of-truth, consumer, mirror. Replaces original v8 `workspace_tags`.115project:116 - handle: markdown-static-site117 role: primary118---119120# Markdown Post Frontmatter Validation121122## Concept of the skill123124**What it is:** The project-specific validation discipline for the YAML frontmatter on markdown posts.125**Mental model:** Frontmatter is a typed interface between content files and the site runtime.126**Why it exists:** Routing, indexes, tags, dates, and previews all depend on frontmatter being complete and unambiguous.127**What it is NOT:** It is not general YAML schema design, parser performance work, or debugging a specific failed build.128**Adjacent concepts:** Content schemas, slug derivation, controlled vocabularies, date normalization.129**One-line analogy:** It is the checklist that makes every post safe for the build to consume.130**Common misconception:** If the markdown renders, the frontmatter is good enough; metadata can break listing pages even when body content renders.131132## Coverage133134- Required-field enforcement — every post must declare `title`, `date`, `slug`, and `tags`; missing fields fail the build at parse time135- Date format discipline — ISO 8601 with explicit timezone (`2026-05-06T12:00:00Z`); ambiguous formats like `2026-05-06` or `5/6/26` are rejected136- Slug-to-path consistency — the `slug` field must match the post's directory name; out-of-sync slugs cause silent route conflicts137- Controlled-vocabulary tagging — every tag in the post's `tags` array must appear in `lib/content/tag-vocabulary.ts`; lowercase, hyphen-separated, no synonyms138- Schema evolution — when `lib/content/schema.ts` changes, every post's frontmatter is re-validated; existing posts that violate the new schema are flagged before the next build runs139- Reserved-field protection — fields like `_id`, `_internal`, or any underscore-prefixed key are reserved for the build pipeline and rejected in author-facing frontmatter140141## Philosophy of the skill142143The frontmatter block is the contract every post makes with the site's index, the router, and the renderer. If that contract is loose — if posts can omit fields, use ambiguous dates, or invent ad-hoc tags — the index drifts, routes silently overlap, and the search surface degrades. The cost of catching frontmatter bugs at build time is one re-run; the cost of catching them in production is a broken page or a missing entry in the archive. The rule is: validate at parse time, fail loud, and keep the schema small enough that authors can hold it in their head.144145## Key Files146147| File | Purpose |148|---|---|149| `content/posts/_template.md` | The canonical template every new post copies — its frontmatter is the worked example of every required field |150| `lib/content/schema.ts` | The TypeScript schema (Zod or equivalent) that runtime validation calls |151| `lib/content/parse-frontmatter.ts` | The thin wrapper that reads the YAML block and runs `schema.parse()` — the failure surface for build-time errors |152153## Verification154155Before merging any change to a post's frontmatter or to the schema:156157- [ ] Every post has the four required fields: `title`, `date`, `slug`, `tags`158- [ ] `date` is ISO 8601 with timezone (no naked `YYYY-MM-DD`, no locale-formatted dates)159- [ ] `slug` matches the post's directory name exactly — not derived from `title` at runtime160- [ ] Every tag in `tags` is present in `lib/content/tag-vocabulary.ts` (run `npm run check:tags` to confirm)161- [ ] No underscore-prefixed fields (`_id`, `_internal`, etc.) — those are reserved for the pipeline162- [ ] Schema changes (`lib/content/schema.ts`) are paired with a `npm run validate:posts` pass against the entire `content/posts/**/*.md` set163164## Do NOT Use When165166| Use instead | When |167|---|---|168| (a generic schema-design skill) | The task is designing a new YAML schema for an unrelated domain |169| `debugging` | A specific build is failing and you need to reproduce the validation error from logs |170| `documentation` | The task is writing a runbook or contributor doc about the frontmatter format |171| `refactor` | The task is restructuring `parse-frontmatter.ts` without changing the validation contract |