How the DESIGN.md Parser Handles YAML Front Matter vs Markdown Body

The DESIGN.md parser treats YAML front matter and fenced YAML code blocks as interchangeable token sources while preserving the surrounding markdown content as discrete document sections, extracting both into a unified AST before merging them into a single token object.

The ParserHandler class in the google-labs-code/design.md repository processes DESIGN.md files through a three-phase pipeline that separates machine-readable YAML tokens from human-readable markdown content. Unlike standard markdown parsers that simply skip front matter, this implementation extracts both --- delimited front matter and ```yaml fenced blocks, validates them for duplicate keys, and maps the remaining markdown body into navigable sections based on level-2 headings.

Three-Phase Parsing Architecture

The parser operates through distinct phases defined in packages/cli/src/linter/parser/handler.ts. Each phase transforms the raw file into structured data that downstream linting and export tools consume.

Phase 1: AST Construction with Unified

The parser initializes a unified processor chain using remark-parse and remark-frontmatter to recognize both standard markdown and YAML front matter delimiters. This configuration happens at lines 30-34 of handler.ts, preparing the parser to identify "yaml" nodes separately from the markdown body.

// From handler.ts L30-L34
const processor = unified()
  .use(remarkParse)
  .use(remarkFrontmatter, ['yaml']);

Phase 2: YAML Extraction

During AST traversal, the parser identifies and extracts two distinct YAML sources:

  • Front matter blocks: Nodes of type "yaml" representing content between --- delimiters (lines 42-50)
  • Fenced code blocks: Nodes of type "code" where the lang property equals "yaml" or "yml" (lines 52-60)

Each extracted block stores its source line number and a flag indicating whether it originated from front matter or a numbered code block. If no YAML blocks are found, the parser immediately returns a NO_YAML_FOUND error (lines 112-120).

Phase 3: Markdown Body Extraction

Simultaneously, the parser captures every level-2 heading (## ...) to segment the document. It records the heading text and line number (lines 64-73), then uses these positions to slice the original file into document sections—including a pre-lude section before the first heading and individual sections for each heading (lines 76-104).

Distinguishing Front Matter from Fenced Blocks

While both sources contain YAML tokens, the parser tracks their provenance differently. Front matter blocks receive special handling as the primary token declaration site, whereas fenced YAML blocks are indexed incrementally via a blockIndex counter. This distinction enables precise source mapping—the parser maintains a sourceMap that correlates specific lines in the file to their corresponding YAML blocks.

The extraction logic in handler.ts lines 42-60 demonstrates this dual-path handling:

// Conceptual representation of the visit logic
visit(ast, (node) => {
  if (node.type === 'yaml') {
    // Front matter extraction
    blocks.push({ type: 'frontmatter', value: node.value, line: node.position.start.line });
  } else if (node.type === 'code' && (node.lang === 'yaml' || node.lang === 'yml')) {
    // Fenced block extraction with incremental indexing
    blocks.push({ type: 'code', index: blockIndex++, value: node.value, line: node.position.start.line });
  }
});

Building Document Sections from Headings

The markdown body reconstruction relies on the heading boundaries captured during AST traversal. Using the line numbers of each ## heading, the parser extracts raw markdown substrings from the original file content. This approach preserves the original formatting, whitespace, and any nested content that might not appear in the AST.

The resulting documentSections array contains objects with the heading title and raw content, enabling tools to render or diff specific sections without re-parsing the entire document.

Merging and Validating YAML Tokens

After extraction, the parser processes each YAML block using the yaml library (lines 45-48). All parsed objects are merged into a single token tree representing the design system. However, the parser enforces strict uniqueness: if the same top-level key appears in both front matter and a fenced code block, it returns a DUPLICATE_SECTION error (lines 61-75).

This validation ensures token definitions remain unambiguous across the file. The final output is a ParsedDesignSystem instance containing:

  • The merged token tree
  • A sourceMap for error reporting
  • The ordered list of sections (headings)
  • The raw documentSections for content rendering

Implementation Example

The following example demonstrates direct usage of the ParserHandler to process a DESIGN.md file containing both front matter and markdown body:

import { ParserHandler } from '@google/design.md/linter/parser/handler';
import { ParserInputSchema } from '@google/design.md/linter/parser/spec';

const designMd = `---
name: Horizon
colors:
  primary: "#112233"
---

## Overview

The Horizon theme focuses on deep blues.

## Typography

All headings use Inter font.
`;

const handler = new ParserHandler();
const result = handler.execute({ content: designMd });

if (result.success) {
  console.log('Tokens:', result.data.name, result.data.colors?.primary);
  // Output: Tokens: Horizon #112233
  
  console.log('Headings:', result.data.sections);
  // Output: Headings: ["Overview", "Typography"]
  
  console.log('First section body:', result.data.documentSections?.[1].content);
  // Output: The Horizon theme focuses on deep blues.
} else {
  console.error('Parse error:', result.error.code, result.error.message);
}

The test suite in packages/cli/src/linter/parser/handler.test.ts provides additional coverage for edge cases including empty front matter, malformed YAML, and duplicate key detection across block types.

Summary

  • Dual extraction: The parser extracts YAML from both --- front matter delimiters and ```yaml fenced code blocks, treating them as equivalent token sources.
  • Provenance tracking: Each block is flagged by source type (front matter vs. code) and indexed to support precise error reporting via the sourceMap.
  • Document sectioning: Level-2 headings (##) slice the markdown body into discrete sections, preserving raw content for downstream rendering.
  • Validation: Duplicate top-level keys across blocks trigger a DUPLICATE_SECTION error, while missing YAML returns NO_YAML_FOUND.
  • Unified output: The ParsedDesignSystem combines merged tokens, section headings, and raw document content for comprehensive design system tooling.

Frequently Asked Questions

What happens if no YAML is found in the file?

If the parser traverses the entire AST and finds no yaml nodes (front matter) or code nodes with YAML language identifiers, it immediately returns a NO_YAML_FOUND error. This check occurs at lines 112-120 of handler.ts before any further processing, ensuring the design system always has a valid token definition to work with.

How does the parser distinguish between front matter and fenced YAML blocks?

The parser uses the AST node type property to distinguish sources. Front matter appears as nodes with type: "yaml" created by remark-frontmatter, while fenced blocks appear as type: "code" nodes with a lang property of "yaml" or "yml". Both are extracted into a unified blocks array, but front matter blocks are flagged differently from incremental code block indices to maintain source traceability.

Can the same token key appear in both front matter and a code block?

No. The parser explicitly checks for duplicate top-level keys across all extracted YAML blocks during the merge phase (lines 140-176 of handler.ts). If a key defined in the front matter section also appears in a fenced YAML code block, the parser returns a DUPLICATE_SECTION error, preventing ambiguous token definitions in the design system.

How are markdown headings used to structure the document body?

The parser captures every level-2 heading (##) during AST traversal, recording its text and line number. Using these line positions, it slices the original file content into an array of documentSections, where index 0 represents the pre-lude (content before the first heading) and subsequent indices map to each heading's content. This preserves the original markdown formatting while enabling section-based navigation and diffing.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →