Add docs CI gates: frontmatter, code samples, internal links, redirects - #180
Add docs CI gates: frontmatter, code samples, internal links, redirects#180tylergoerzen-mxp with Copilot wants to merge 3 commits into
Conversation
Co-authored-by: tylergoerzen-mxp <259741734+tylergoerzen-mxp@users.noreply.github.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Confidence Score: 0/5The PR is not safe to merge because the required documentation gates remain incomplete and the workflow still executes actions through mutable references. Redirect-source links and redirect chains still pass, priority examples are not compiled, required frontmatter metadata is not fully checked, OpenAPI validation is absent, and every new job relies on movable action tags. Files Needing Attention: .github/workflows/docs-ci.yml, scripts/check_links.py, scripts/check_code_samples.py, scripts/check_frontmatter.py, scripts/check_redirects.py
|
| Filename | Overview |
|---|---|
| .github/workflows/docs-ci.yml | Adds four parallel validation jobs, while the previously reported OpenAPI omission and mutable action references remain. |
| scripts/check_links.py | Adds internal-link scanning, but the previously reported acceptance of redirect-source links remains. |
| scripts/check_code_samples.py | Adds code-fence language validation, but the previously required compile and smoke testing remains absent. |
| scripts/check_frontmatter.py | Adds title-presence validation, but description and title-uniqueness requirements remain absent. |
| scripts/check_redirects.py | Adds redirect validation and cycle detection, but still explicitly permits redirect chains. |
| docs.json | Repoints stale redirect destinations to existing canonical pages. |
| docs/mcp.mdx | Replaces a dead encoded service-account link with its canonical route. |
| reference/event-deduplication.mdx | Updates the event-object link to the current data-structure page. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
PR[Docs pull request] --> CI[Docs CI]
CI --> F[Frontmatter check]
CI --> C[Code-fence check]
CI --> L[Internal-link check]
CI --> R[Redirect check]
Reviews (2): Last reviewed commit: "Fix false positives and false negatives ..." | Re-trigger Greptile
| content = fh.read() | ||
|
|
||
| in_block = False | ||
| for lineno, line in enumerate(content.splitlines(), 1): | ||
| stripped = line.strip() | ||
| if stripped.startswith("```"): | ||
| if in_block: | ||
| # Closing fence | ||
| in_block = False | ||
| else: | ||
| # Opening fence — extract language token | ||
| rest = stripped[3:].strip() | ||
| lang = rest.split()[0] if rest else "" | ||
| if not lang: | ||
| errors.append( | ||
| f"{path}:{lineno}: code block is missing a language identifier" | ||
| ) | ||
| in_block = True | ||
|
|
||
| return errors |
There was a problem hiding this comment.
When a labeled sample contains invalid syntax, package names, exports, environment variables, or API hosts, this checker examines only the opening fence and still passes it. Uncompilable priority examples therefore remain mergeable despite TOF-447 requiring compile or smoke tests.
Source Used: Linear — AEO QW10: Add docs CI gates (links, code samples, frontmatter, redirects, OpenAPI)
|
|
||
| m = FRONTMATTER_RE.match(content) | ||
| if not m: | ||
| errors.append(f"{path}: missing front-matter block") | ||
| return errors | ||
|
|
||
| fm = m.group(1) | ||
| if not re.search(r"^\s*title\s*:", fm, re.MULTILINE): |
There was a problem hiding this comment.
Frontmatter requirements remain unchecked
When a page omits its description or duplicates another page's title, this function accepts it as long as a title key exists. Pages that violate TOF-447's description and unique-title requirements therefore pass CI.
Source Used: Linear — AEO QW10: Add docs CI gates (links, code samples, frontmatter, redirects, OpenAPI)
|
|
||
| redirects: | ||
| name: Redirects check | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.12" | ||
| - name: Check redirects | ||
| run: python scripts/check_redirects.py |
There was a problem hiding this comment.
OpenAPI validation gate is missing
When a PR introduces an invalid OpenAPI specification or nonconforming example, none of these four jobs validates it. The workflow can pass while generated API reference pages render broken or incomplete, contrary to TOF-447's OpenAPI acceptance criteria.
Knowledge Base Used: API Reference (reference/ and openapi/)
Source Used: Linear — AEO QW10: Add docs CI gates (links, code samples, frontmatter, redirects, OpenAPI)
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-python@v5 |
There was a problem hiding this comment.
Action dependencies use mutable tags
The new jobs execute actions/checkout@v4 and actions/setup-python@v5 from mutable references, unlike the repository's existing SHA-pinned workflow. Pinning these actions prevents upstream tag movement from changing executable CI code and its results.
How this was verified: All four jobs use mutable major tags, while .github/workflows/stale.yml pins its action to a full commit SHA.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
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>
Adds four Python validation scripts and a GitHub Actions workflow that block merges when docs quality invariants are violated.
CI workflow (
.github/workflows/docs-ci.yml)Runs on every PR and push to
main. Four parallel jobs, eachpermissions: contents: read.Validation scripts (
scripts/)check_frontmatter.py— all MDX pages (excludingsnippets/,links/,openapi/) must have atitle:in YAML front-mattercheck_code_samples.py— every fenced code block must declare a language (```python, not bare```)check_links.py— internal links (/-prefixed, non-asset) must resolve to an existing MDX file or redirect source; wildcard redirect prefixes (e.g./changelogs/*) handled with an O(n_prefixes) check rather than iterating all valid pathscheck_redirects.py— redirect sources indocs.jsonmust be unique; destinations must resolve to a known page or another redirect sourcePre-existing violations fixed
To get the gates green from day one:
docs.json: 25 broken redirect destinations — stale/changelogs/2022-*subpage URLs →/changelogs; deleted-page destinations updated to current equivalents; one missing leading slash (docs/getting-started/...→/docs/what-is-mixpanel)docs/mcp.mdx: URL-encoded dead link/reference/Mixpanel%20APIs/authentication/service-accounts→/reference/service-accountsreference/event-deduplication.mdx: stale/docs/data-model#anatomy-of-an-event→/docs/data-structure/events-and-properties