Skip to content

TOF-447: Add docs CI gates: frontmatter, links, redirects, code samples, OpenAPI - #180

Open
tylergoerzen-mxp with Copilot wants to merge 10 commits into
mainfrom
copilot/tof-447-add-docs-ci-gates
Open

tylergoerzen-mxp with Copilot wants to merge 10 commits into
mainfrom
copilot/tof-447-add-docs-ci-gates

Conversation

Copilot AI commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Adds five Python validation scripts and a GitHub Actions workflow that block merges when docs quality invariants break.

Relates to: https://linear.app/mixpanel/issue/TOF-447/aeo-qw10-add-docs-ci-gates

Why this matters for AEO

Search engines and assistants redistribute code samples verbatim. A broken snippet does not just fail one reader — it propagates at scale into answers, repos, and other people's docs. The Next.js page in TOF-438 had shipped a snippet importing a function that was never defined; that is the class of defect this exists to stop.

The rest of the plan is a one-time cleanup that decays without enforcement. Every gate here corresponds to work in another quick win:

Gate Locks in
Frontmatter: title, description, uniqueness TOF-439 descriptions, TOF-445 titles
Internal links resolve to canonical routes TOF-441
Redirects: no chains, loops, or duplicate sources TOF-441
Code blocks declare a language TOF-438
OpenAPI specs parse and resolve TOF-447

Descriptions matter beyond hygiene: Mintlify generates llms.txt from that field, and /llms.txt was the single most-fetched page by AI agents in the trailing 30 days at 7,833 requests. A page without a description renders bare in the index agents actually read.

The gates

  • check_frontmatter.py — every page needs a non-empty title and description, and no two pages may share a rendered title
  • check_links.py — internal links must resolve to a real page or a redirect source; code fences and inline code are excluded so a page documenting an example link does not fail
  • check_redirects.py — no duplicate sources, no loops, no chains (a destination that is itself redirected), and every destination resolves
  • check_code_samples.py — every fenced block declares a language
  • check_openapi.py (new) — all 14 specs parse, carry openapi/info/paths, and every local $ref resolves. Uses openapi-spec-validator for full schema validation when installed, and then checks every example (media-type, parameter, and schema-level) against the schema it illustrates. Still runs structurally without the validator.

Fixes from review

check_redirects.py had an 88% false-positive rate. Its docstring says chained redirects are allowed, but it only matched exact sources and ignored the 461 wildcard sources in docs.json. It reported 25 errors on main of which 3 were real. A gate that noisy gets disabled in a week.

It also could not see loops. Every node in a cycle is itself a source, so the chained-redirect rule silently swallowed /self → /self and /a → /b → /a. Both now fail.

check_links.py would have failed any future page showing an example link inside a code fence. Zero occurrences today, which is exactly when it is cheap to fix.

check_code_samples.py mis-parsed nested fences — a ```python block inside a ````mdx block closed the outer one early.

Actions are now pinned to commit SHAs, matching stale.yml. Four near-identical jobs collapsed into one with ordered steps, plus a concurrency group.

Verification

Each gate was executed, not just reviewed:

  • All five pass on this branch's content
  • Each fails on a deliberate bad fixture: empty title, bare fence, dead link, redirect loop, spec with a dangling $ref
  • check_openapi.py validates all 14 real specs

Existing violations: baselined, not blocking

The first CI run failed because the frontmatter gate correctly found real problems already on main: 362 pages without a description and 14 groups of duplicate titles. The checker wasn't wrong, but a gate that fails every unrelated PR on day one would get disabled.

So each check can list known violations in scripts/docs-ci-baseline.json. Only new violations fail. A new page without a description, a new duplicate title, or a new redirect chain is blocked; old ones aren't.

Gate Blocking? Baselined today
Frontmatter (title, description, unique titles) Yes, for new violations 407 entries (362 descriptions, 45 duplicate-title pages)
Code samples declare a language Yes, fully 0 (two bare fences from main fixed here)
Internal links Yes, fully 0
Redirects (duplicates, loops, chains, dead destinations) Yes, for new violations 229 chains
OpenAPI (parse, structure, $refs, schema, examples) Yes, fully 0 (one invalid GDPR example fixed here)

Baseline entries that stop occurring print a note but never fail. So the cleanup PRs can land in any order without touching the baseline. Verified against each tree: #172 clears all 362 description entries, #178 clears all 45 duplicate-title entries, #174 clears all 229 chains. Prune afterwards with:

python scripts/check_frontmatter.py --update-baseline
python scripts/check_redirects.py --update-baseline

Merge order no longer matters. This PR can merge before or after #172, #174, and #178.

Not implemented

Rec #20 Part B asks that priority code examples be compiled or smoke-tested, not just checked for a language tag. That needs fixtures or runnable example projects per language and is a larger piece of work. The Next.js page was instead verified by hand for TOF-438: both routers scaffolded with create-next-app, every snippet transcribed verbatim, npm run build and npm run dev passing in both.

@linear-code

linear-code Bot commented Aug 18, 2026

Copy link
Copy Markdown

TOF-447

Co-authored-by: tylergoerzen-mxp <259741734+tylergoerzen-mxp@users.noreply.github.com>
@mintlify

mintlify Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
mixpanel-docs 🟢 Ready View Preview Sep 25, 2026, 8:17 PM

Copilot AI changed the title [WIP] Add CI gates for docs validation including links and code samples Add docs CI gates: frontmatter, code samples, internal links, redirects Aug 18, 2026
Copilot AI requested a review from tylergoerzen-mxp August 18, 2026 19:07
@greptile-apps

greptile-apps Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Adds documentation validation gates to the CI pipeline.

The PR is not safe to merge until its link gate accepts the new Markdown-format documentation link.

Reviews (7) · Last reviewed commit: "Merge branch 'main' into copilot/tof-447..."

Comment thread scripts/check_links.py
Comment thread scripts/check_code_samples.py
Comment thread scripts/check_frontmatter.py Outdated
Comment thread scripts/check_redirects.py Outdated
Comment thread .github/workflows/docs-ci.yml Outdated
Comment thread .github/workflows/docs-ci.yml Outdated
check_redirects.py
- Honour wildcard redirect sources when resolving a destination. The
  docstring says chained redirects are allowed, but only exact sources
  were matched, so the 461 wildcard sources were ignored. That produced
  25 errors on main of which only 3 were real: an 88% false-positive
  rate that would have fired again on the next redirect anyone added.
- Detect loops. Every node in a cycle is also a source, so the
  chained-redirect rule silently swallowed /self -> /self and /a -> /b -> /a.

check_links.py
- Blank out fenced and inline code before extracting links, so a page
  documenting an example <a href="/docs/..."> does not fail CI. Line
  numbers are preserved.

check_code_samples.py
- Track fence length so a ```python block nested in a ````mdx block does
  not close the outer block early. Drop the unused FENCE_OPEN_RE and
  report repo-relative paths instead of absolute ones.

check_frontmatter.py
- An empty title no longer passes.

Verified: all four pass on this branch, and each rejects a deliberate
bad fixture (empty title, bare fence, dead link, redirect loop).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes rec #20 Part B's gate list.

- New check_openapi.py: parses all 14 specs, requires openapi/info/paths,
  and resolves every local $ref. Uses openapi-spec-validator for full
  schema validation when installed, and still runs structurally without
  it. Passes on all 14 specs today; rejects a spec with a dangling $ref.
- check_frontmatter.py now requires a non-empty description and fails on
  duplicate rendered titles, which rec #11 asked for.
- Pin actions/checkout and actions/setup-python to commit SHAs, matching
  stale.yml. Collapse four near-identical jobs into one with ordered
  steps, and add a concurrency group.

MERGE ORDER: the frontmatter gate is red until #172 (description
backfill) and #178 (title dedupe) land. Verified against the #172 tree:
all description errors clear, leaving only the duplicate titles that
#178 resolves. Merge this last, as the plan intends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tylergoerzen-mxp added a commit that referenced this pull request Aug 20, 2026
Rec #11 asks for a CI uniqueness test on rendered titles, which only
passes at zero duplicates. Three pairs survived the first pass:

- Data Pipeline Integrations: the old-pipelines copy is now marked
  (Legacy), keeping "Integrations" as its sidebar label
- Lookup Tables: the reference page is the API, so it becomes
  Lookup Tables API
- Mixpanel Headless: the guide gets the benefit-style title, leaving the
  bare product name to the docs page

Verified zero duplicate titles across the tree, so the frontmatter gate
in #180 can enforce uniqueness once this and #172 land.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread scripts/check_redirects.py Outdated
Comment thread scripts/check_openapi.py
@tylergoerzen-mxp tylergoerzen-mxp changed the title Add docs CI gates: frontmatter, code samples, internal links, redirects TOF-447: Add docs CI gates: frontmatter, links, redirects, code samples, OpenAPI Aug 20, 2026
tylergoerzen-mxp added a commit that referenced this pull request Sep 24, 2026
- Fix 13 residual chains the first pass missed (trailing slash on
  /docs/ and ?sdk= query strings) and 3 dangling destinations that
  existed on main
- Repoint ~50 links from main's AI-guides reorg to their canonical
  /guides/mcp, /guides/mixpanel-agent, /guides/use-mixpanel-ai paths
- Fix stale anchors: onboarding-playbook/plan/tracking-strategy#...,
  mixpanel-agent/use-cases#rca-agent and #experiments-agent,
  lookup-tables double-hyphen typo, pricing group-keys anchor

Verified with PR #180's check_redirects.py and check_links.py (both pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tylergoerzen-mxp and others added 4 commits September 25, 2026 12:48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… redirect chains; validate OpenAPI examples

The frontmatter gate failed on main content (362 pages missing a
description, 14 duplicate-title groups), so it would have blocked every
unrelated PR. Known violations now live in scripts/docs-ci-baseline.json
and only new ones fail. Entries that get fixed are reported, never fatal,
so TOF-439 (#172) and TOF-441 (#174) can land without touching it.

- check_redirects: reject chains (destination is itself redirected);
  229 existing chains baselined for #174. Loops reported once per cycle.
- check_openapi: validate media-type, parameter and schema-level examples
  against their schemas; fix the one invalid GDPR example.
- check_code_samples: tag two bare fences that landed on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread scripts/check_redirects.py
@greptile-apps

This comment has been minimized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tylergoerzen-mxp

Copy link
Copy Markdown
Contributor

Ready for review. Docs checks is green on 09a6745.

Why the job was failing

The checker wasn't buggy. The frontmatter gate correctly found real problems already on main: 362 pages without a description and 14 groups of duplicate titles. Merging it as-is would have blocked every unrelated PR.

What changed

What each gate enforces

Gate Enforces Blocking Baselined
Frontmatter Non-empty title and description, unique titles New violations 407 (362 descriptions, 45 duplicate-title pages)
Code samples Every fence declares a language Fully 0
Internal links Links resolve to a page or redirect Fully 0
Redirects No duplicate sources, loops, or chains; destinations exist New violations 229 chains
OpenAPI Parses, required structure, local $refs, spec schema, examples match schemas Fully 0

None of the gates are warn-only.

Updating the baseline

After a cleanup PR merges, prune the fixed entries:

python scripts/check_frontmatter.py --update-baseline
python scripts/check_redirects.py --update-baseline

Don't use --update-baseline to get a new violation past CI. Fix it instead.

Out of scope

Greptile flagged that the Slack Bot and Warehouse Sync Monitoring redirects in docs.json point to general pages. Those came in from main, not from this PR.

This branch was successfully deployed

1 active deployment
staging — 7bdfb920 Deployed Sep 25, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants