# validate-docs-frontmatter.sh / validate-docs-frontmatter.ps1

## Purpose

Validate that every rendered `docs/**/*.md` page has well-formed YAML frontmatter with required fields `title` and `description`. Optional fields (`tags`, `date`) validated when present.

This catches docs that lack the metadata Material for MkDocs uses for tag indexing, search, and SEO. Closes audit gap 14.2 from the doc-structure audit.

## Usage

```bash
./scripts/validate-docs-frontmatter.sh
./scripts/validate-docs-frontmatter.sh --strict
```

```powershell
.\scripts\validate-docs-frontmatter.ps1
.\scripts\validate-docs-frontmatter.ps1 -Strict
```

## What It Does

1. Walks the filesystem for `docs/**/*.md` excluding `docs/internal/`
2. Parses `mkdocs.yml` to extract `exclude_docs:` (those files are skipped)
3. Skips files matching auto-skip patterns (`templates/*`)
4. For each remaining file:
   - Verifies first line is `---` (matches `lint-skills-frontmatter` v2.11.1 rule)
   - Verifies a closing `---` exists
   - Verifies required fields `title` (non-empty, ≤80 chars) and `description` (50-300 chars) are present
5. Reports findings

## Exit Codes

| Code | Meaning |
|------|---------|
| `0`  | All docs pass OR advisory mode (default) |
| `1`  | Findings AND `--strict` (bash) or `-Strict` (pwsh) was passed |

## Posture (v2.13.0)

**Advisory.** Deviation from audit Section 16.5's "enforcing" recommendation. The deviation is intentional and documented:

- Pre-existing rendered docs lack frontmatter on many hand-edited pages (concepts, guides, reference, contributing). Auto-generated content (skills, showcase) has frontmatter via the generator.
- Shipping enforcing now would block CI on existing drift; cleanup is a Bucket B / v2.14 effort.
- Per the v2.13 Codex Round 1 lesson: don't ship enforcing on a check that has known pre-existing drift unless cleanup happens in the same cycle.

**Promotion plan:** v2.14 (or earlier if frontmatter coverage is filled in) - promote to enforcing after a clean cycle.

## What's checked

| Field | Required? | Constraint |
|---|---|---|
| `---` (delimiter) | Yes | First line of file |
| `---` (closing) | Yes | Must close the frontmatter block |
| `title` | Yes | Non-empty, ≤80 chars |
| `description` | Yes | 50-300 chars |
| `tags` | No | NOT validated by this script (Material for MkDocs and the tags plugin handle their own validation) |
| `date` | No | NOT validated by this script |

## Auto-skip

| Pattern | Reason |
|---|---|
| `docs/internal/**` | Internal-only; not rendered |
| Anything in `mkdocs.yml exclude_docs` | Excluded from build by design |
| `templates/*` | Template files; reference content for `creating-skills.md` and `agent-skill-anatomy.md`; not navigational and not user-facing |
| `skills/*` | Auto-generated from `skills/**/SKILL.md`; description format follows the skill 20-100-word rule (can exceed 300 chars); validated by `lint-skills-frontmatter` instead |
| `workflows/*` | Auto-generated from `_workflows/`; their frontmatter rules are governed by the generator |
| `showcase/*` | Auto-generated; thread-content pages |
| `reference/commands.md` | Auto-generated by `generate-skill-pages.py` |
| `releases/Release_v*.md` | Release notes; transitively reachable from `releases/index.md` table; per-release format is governed by the release-notes template, not page-frontmatter rules |

## Example Output

```
=== Docs Frontmatter Validation ===

Files checked: 45
Excluded by mkdocs.yml exclude_docs or auto-skip: 88

PASS: All 45 docs have valid frontmatter.
```

```
=== Docs Frontmatter Validation ===

Files checked: 45
Excluded by mkdocs.yml exclude_docs or auto-skip: 88

Frontmatter findings: 12 failures, 3 warnings

  docs/concepts/skill-anatomy.md: missing frontmatter delimiter (first line is not '---')
  docs/guides/using-skills.md: missing frontmatter delimiter (first line is not '---')
  docs/reference/categories.md: missing frontmatter delimiter (first line is not '---')
  ...

WARN: 15 frontmatter finding(s) (advisory mode).
  Many rendered docs currently lack frontmatter; cleanup is a Bucket B / v2.14 effort.
  Promote to enforcing (--strict in CI) once frontmatter coverage is complete.
```

## Limitations / Known Issues

- **YAML parsing is regex-based.** Complex frontmatter (multiline block scalars beyond simple `>` or `|`, anchors, aliases) may not parse correctly. Sufficient for current pm-skills frontmatter conventions.
- **Description multiline support is basic.** Block scalars (`description: >-` or `description: |`) are supported but unusual indentation may confuse the parser.
- **No semantic validation.** A title of `"foo"` passes the length check even though it's not meaningful. Triage is manual.

## See Also

- `lint-skills-frontmatter.{sh,ps1}` (sibling enforcing check for `skills/**/SKILL.md` frontmatter, stricter rules)
- `check-nav-completeness.{sh,ps1}` (companion check for nav coverage)
- v2.13 CI audit: `docs/internal/audit/ci-audit_2026-05-03.md` Section 16.5
- v2.13 CI execution plan: `docs/internal/release-plans/v2.13.0/plan_v2.13_ci-refactor.md` Wave 3 item 9

## Safety

Read-only. Does not modify any files.
