Skip to content

Documentation System

This file describes how the MkDocs documentation site is built, so that AI agents can understand the system and make changes confidently.

Overview

The site is built with MkDocs using the Material for MkDocs theme. Documentation is NOT authored directly in the docs/ directory. Instead, a build script (scripts/build_docs.sh) assembles markdown files from source locations across the repo into docs/, and then mkdocs build generates the static site into site/.

Key files

File Purpose
mkdocs.yml MkDocs configuration (theme, plugins, markdown extensions)
scripts/build_docs.sh Assembles docs/ from source files across the repo
scripts/generate_dbt_docs.py Converts dbt YAML schemas into markdown pages
scripts/resolve_snippets.py Resolves --8<-- snippet directives for Hex guides upload
hex_context.config.json Configures which files are uploaded as Hex guides
.github/workflows/hex_context_toolkit.yml GitHub Action that publishes guides to Hex on merge to main
docs/.pages Top-level sidebar navigation ordering (awesome-pages plugin)
docs/dbt/.pages Navigation ordering within the dbt section
docs/index.md Site homepage (static, checked into repo)
docs/how_to_document.md User-facing guide on how to add documentation (static, checked into repo)

Build process

Running bash scripts/build_docs.sh does two things:

1. Query docs

The script iterates over immediate child directories of queries/ (these are sections, e.g. engagement_and_usage). For each section, it recursively finds all .md files (at any depth) and copies them flat into docs/queries/<section>/. Any subdirectory structure is discarded.

Markdown files can live directly in the section directory or inside a query subdirectory — both are supported:

queries/
  engagement_and_usage/          <-- section
    call_log_count/              <-- query subdirectory (optional)
      call_log_count.md          <-- copied
      call_log_count.sql         <-- NOT copied (only .md files)
  network_growth_and_health/     <-- section
    network_size.md              <-- copied (directly in section)
    network_size.sql             <-- NOT copied
    daily_calls.md               <-- copied

Result in docs/:

docs/queries/
  engagement_and_usage/
    call_log_count.md
  network_growth_and_health/
    network_size.md
    daily_calls.md

In the sidebar, this renders as:

Queries
  Engagement And Usage          <-- collapsible section
    Call Log Count               <-- page
    Another Query                <-- page

2. dbt docs

The script runs scripts/generate_dbt_docs.py, which reads dbt/models/_sources.yml and dbt/models/_models.yml and generates two markdown files:

  • docs/dbt/sources.md — one section per source, with tables listing columns, types, and descriptions
  • docs/dbt/models.md — one section per model, with tables listing columns, types, descriptions, and tests

The Python script uses yaml.safe_load to parse the YAML and produces markdown with ## headings per source/model and ### headings per table, with pipe-delimited markdown tables for columns.

Static pages

docs/index.md and docs/how_to_document.md are checked directly into the repo and are not generated by the build script. They persist across builds.

Sidebar ordering is controlled by .pages files (via the awesome-pages MkDocs plugin), NOT by a nav: key in mkdocs.yml. The top-level docs/.pages defines the order of top-level sections. Subdirectories can have their own .pages files (e.g. docs/dbt/.pages).

Currently docs/.pages contains:

nav:
  - Home: index.md
  - How to Document: how_to_document.md
  - dbt
  - queries

Markdown extensions

Configured in mkdocs.yml:

  • pymdownx.snippets (base_path: .): Allows embedding file contents inline using --8<-- "path/from/repo/root". This is how query .md files include their SQL source. Paths are relative to the repo root. Note: the snippets preprocessor runs on raw markdown before rendering, so --8<-- directives are processed even inside code fences.
  • pymdownx.highlight + pymdownx.superfences: Syntax highlighting for code blocks.
  • tables: Markdown table support.
  • admonition + pymdownx.details: Callout boxes (warnings, tips, etc.).

Building and previewing

# Assemble docs from repo sources
bash scripts/build_docs.sh

# Build static site (output: site/)
mkdocs build --strict

# Or preview locally with live reload
mkdocs serve

Deployment

The site deploys to Cloudflare Pages. The Cloudflare build command runs the full pipeline:

pip install mkdocs-material mkdocs-awesome-pages-plugin && bash scripts/build_docs.sh && mkdocs build --strict

Build output directory: site

site/ is in .gitignore — Cloudflare builds it fresh on each deploy.

Hex guides

Query and utility documentation is also published as guides to Hex (our BI tool), giving Hex's AI context about our queries. This is handled by a separate GitHub Action (.github/workflows/hex_context_toolkit.yml) that runs on merge to main.

Because Hex receives raw markdown (not rendered HTML), the --8<-- snippet directives must be resolved before upload. The pipeline works as follows:

  1. scripts/resolve_snippets.py reads .md files from queries/**/*.md and docs/utilities/**/*.md, inlines any --8<-- referenced files, and writes resolved markdown to .hex_guides/.
  2. The hex-inc/action-context-toolkit action uploads all .md files from .hex_guides/ to Hex.

The .hex_guides/ directory is ephemeral (created only in CI, listed in .gitignore). The Hex config (hex_context.config.json) points at this directory.

This is completely independent of the MkDocs/Cloudflare pipeline — both consume the same source .md files but process them differently.

Important gotchas

  • Do not author docs directly in docs/queries/ — they will be overwritten by build_docs.sh. Query docs live in queries/<section>/<query_name>/.
  • Snippet paths must match the actual file location in the repo. If a query is moved to a different section, the --8<-- path in its .md file must be updated. This affects both the MkDocs site and the Hex guides.
  • The --8<-- snippet directive cannot be displayed as example text in a code block because the snippets preprocessor runs before markdown rendering. To show the syntax to users, use HTML with &#45; to break the pattern (e.g. <pre><code>&#45;-8<-- "path"</code></pre>).