Docs as Code skill for Codex

Docs that ship in the same PR as the code. Codex inspects the existing docs setup before proposing anything, picks a platform from concrete trade-offs such as built-in versioning and search, and makes the build fail on broken internal links. It wires markdownlint, Vale with a project vocabulary and PR previews into CI, and prepares the publish job but asks before deploying anything public.

  • Writing & Docs
  • $docs-as-code
  • 207 lines
  • 3 files
  • ~2,150 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- docs-as-code

Installs to ~/.agents/skills/docs-as-code/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $docs-as-code, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Sets up and maintains documentation sites that live in the repo and ship through CI - Docusaurus, Astro Starlight and Mintlify (MkDocs as an escape hatch) - with Markdown/MDX sources, PR previews, strict link checking, Vale prose linting, markdownlint and versioned docs. Use when choosing or scaffolding a docs site, wiring docs builds and link checks into GitHub Actions, adding versioning for a new major release, migrating from a wiki, or fixing a docs build that breaks on broken links.

Use it when

  • You are choosing or scaffolding a documentation site that lives in the repo
  • Docs builds need lint, link checks and previews in GitHub Actions
  • A new major release needs versioned docs or a wiki is moving into Markdown

Use something else when

How to check the result

The docs build passes locally, fails when one internal link is deliberately broken, and the CI workflow runs on a test pull request with lint results and a preview link.

The mistake it prevents

Renaming or moving doc pages without redirects, which silently breaks every inbound link from search results and other sites.

~/.agents/skills/docs-as-code/SKILL.md

--- name: docs-as-code description: Sets up and maintains documentation sites that live in the repo and ship through CI - Docusaurus, Astro Starlight and Mintlify (MkDocs as an escape… license: MIT ---

Docs-as-code is working when docs change in the same pull request as the code they describe, a broken link or invalid page fails CI before merge, reviewers can see a rendered preview, and publishing is a merge rather than a manual upload.

Inspect first

cat AGENTS.md 2>/dev/null | head -80
ls docs website site 2>/dev/null; ls docusaurus.config.* astro.config.* docs.json mint.json mkdocs.yml 2>/dev/null
cat package.json 2>/dev/null | head -40                   # node version, docs scripts
ls .github/workflows; rg -l "docs" .github/workflows 2>/dev/null
ls .vale.ini .markdownlint* 2>/dev/null

If a platform already exists, keep it unless the user asked to migrate. Most “docs site” problems are content and CI problems, not platform problems.

Choose a platform

SituationDefaultWhy
Open-source project or product docs, React team, needs versioning and i18nDocusaurus 3Built-in versioning, mature plugin ecosystem
Wants fast static site, minimal JS, built-in search, Astro or framework-agnostic teamAstro StarlightPagefind search built in, strong defaults, accessible
API-first SaaS wanting hosted docs with API playground and little maintenanceMintlifyHosted, docs.json config, OpenAPI pages; paid tiers for some features
Python project already on MkDocsStay on MkDocsmkdocs build --strict gives the CI gate; migration rarely pays off
Internal docs for one serviceMarkdown in docs/ rendered by the Git hostNo site needed until there are ~20+ pages or outside readers

Versioning trade-off: Docusaurus versions by copying the docs folder per version (versioned_docs/version-x/). Starlight has no built-in versioning (community plugins exist). Mintlify versions through navigation.versions in docs.json. If you must keep several majors documented, that difference often decides the platform.

Platform-specific setup, config snippets and gotchas live in references/platforms.md; read it once the platform is chosen.

Workflow

- [ ] 1. Inspect existing docs, platform and CI (commands above)
- [ ] 2. Choose or confirm the platform (table above); confirm with the user before migrating
- [ ] 3. Scaffold or fix config; make the build fail on broken links
- [ ] 4. Add lint: markdownlint-cli2 for structure, Vale for prose
- [ ] 5. Add CI: build + lint on every PR touching docs; preview deployment
- [ ] 6. Add publish job on main (confirm the deploy target with the user)
- [ ] 7. Set versioning policy if the product ships majors
- [ ] 8. Document the docs workflow in AGENTS.md / CONTRIBUTING.md

Scaffolding and installs need network (npm create, npx, pip install). In a Codex sandbox with network off, request approval or prepare the files and leave the install for the user, saying so in the report.

Repository layout

docs/                    # content (Markdown/MDX), one folder per section
  getting-started/
  guides/
  reference/
static/ or public/       # images, downloads
.vale.ini                # prose lint config
styles/                  # Vale styles and project vocabulary
  config/vocabularies/Project/accept.txt
.markdownlint-cli2.jsonc
.github/workflows/docs.yml
  • Keep content separate from site code so writers do not touch React or Astro files.
  • Images next to the page or in one static/img/<section>/ tree; name them by content (webhook-retry-timeline.png), always with alt text.
  • One Markdown file per URL. File and folder names become slugs; renames break links, so add redirects when you move pages.

Lint configuration

# .vale.ini
StylesPath = styles
MinAlertLevel = suggestion
Vocab = Project
Packages = Google, write-good

[*.{md,mdx}]
BasedOnStyles = Vale, Google
Google.Headings = NO          # turn off rules that fight the house style
vale sync                     # downloads packages listed above (network)
vale docs/
npx markdownlint-cli2 "docs/**/*.md"

Add product names, acronyms and code terms to styles/config/vocabularies/Project/accept.txt (one per line, regex allowed) instead of disabling the spelling rule.

CI pipeline (GitHub Actions, Docusaurus or Starlight)

name: docs
on:
  pull_request:
    paths: ['docs/**', 'website/**', 'package.json', '.github/workflows/docs.yml']
  push:
    branches: [main]
permissions:
  contents: read
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npx markdownlint-cli2 "docs/**/*.md"
      - uses: errata-ai/vale-action@v2
        with: { files: docs }
      - run: npm run build          # fails on broken links when configured to throw
      - uses: actions/upload-pages-artifact@v4
        if: github.ref == 'refs/heads/main'
        with: { path: build }        # Starlight/Astro: dist
  deploy:
    if: github.ref == 'refs/heads/main'
    needs: build
    runs-on: ubuntu-latest
    permissions: { pages: write, id-token: write }
    environment: { name: github-pages, url: '${{ steps.d.outputs.page_url }}' }
    steps:
      - id: d
        uses: actions/deploy-pages@v4

Pin action majors to whatever is current when you write the workflow, and pin by SHA if the repo’s security policy requires it. The deploy job publishes to the public internet: add or change it only after the user confirms the target (GitHub Pages, Netlify, Vercel, Cloudflare Pages) and that the content is meant to be public.

PR previews: Netlify, Vercel and Cloudflare Pages create them per PR through their Git integration; Mintlify generates preview deployments for PRs when the GitHub app is installed. Prefer the host’s integration over custom preview jobs.

LayerToolCatches
Internal links during buildPlatform setting (onBrokenLinks: 'throw', mkdocs build --strict, starlight-links-validator, mint broken-links)Moved or renamed pages, bad anchors
External linkslychee on a schedule, not on every PRRotted third-party URLs

Run external link checks nightly or weekly; third-party outages should not block merges.

lychee --offline docs/        # local files and anchors only, no network
lychee --max-retries 2 --accept 200,429 docs/   # full check, needs network

Versioning policy

  • Version docs only when users run several majors in production. Otherwise one “latest” set plus a changelog is cheaper and less confusing.
  • Cut a docs version at release time, never before: npm run docusaurus docs:version 2.0 snapshots the current docs/ into versioned_docs/version-2.0/.
  • Fix typos in every maintained version; backport only factual corrections to old ones.
  • Drop versions that are out of support and redirect them to the latest.
  • Show an “unreleased” banner on the next docs so readers do not use unshipped APIs.

Gotchas

  • MDX is JSX: bare <, { and unclosed HTML tags in Markdown break the build. Escape or wrap in code spans.
  • Moving or renaming files without redirects breaks inbound links from search engines and other sites. Add redirects in the same PR.
  • Generated reference (API, CLI) committed by hand drifts. Generate it in the build or in a CI job that fails when the output differs.
  • Versioned docs multiply build time and search noise; each version is a full copy.
  • Search indexing (Algolia DocSearch) needs a crawler config; local search plugins are simpler for small sites.
  • Making a whole workflow pass by setting link checks to warn. Fix or explicitly exclude the specific link instead.

Verification

  • npm run build (or platform equivalent) passes locally and fails when you deliberately break one internal link; revert the break afterwards.
  • vale docs/ and markdownlint-cli2 run clean, or remaining alerts are listed.
  • CI runs on a test PR and shows a preview link.
  • Every page reachable from navigation; no orphans (compare sidebar entries to files).

Report

**Platform:** Astro Starlight (kept existing), Node 22
**Build gate:** starlight-links-validator added; build now fails on 3 broken links, all fixed
**Lint:** Vale (Google + Project vocab, 41 accepted terms), markdownlint-cli2
**CI:** .github/workflows/docs.yml, PR build + lint; deploy job prepared but disabled pending your confirmation of GitHub Pages
**Versioning:** not enabled; single latest docs (one supported major)
**Not run:** vale sync and npm ci (network off in sandbox)

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Docs as Code"
  short_description: "Docs that ship in the same PR as the code"
  default_prompt: "$docs-as-code Set up a Docusaurus docs site in this repo with link checking in CI"
references/platforms.md155 lines, 5.6 KB
# Platform notes: Docusaurus, Starlight, Mintlify, MkDocs

Read only the section for the platform in use. Versions move quickly; check the project's
lockfile and the platform's release notes before copying a config key, and prefer the
pinned version in `package.json` over `@latest`.

## Docusaurus 3

Scaffold (network): `npx create-docusaurus@latest website classic --typescript`

Key config in `docusaurus.config.ts`:

```ts
import type { Config } from '@docusaurus/types';

const config: Config = {
  title: 'Acme Docs',
  url: 'https://docs.acme.dev',
  baseUrl: '/',
  onBrokenLinks: 'throw',
  onBrokenAnchors: 'throw',
  markdown: {
    format: 'detect',              // .md parsed as CommonMark, .mdx as MDX
    hooks: { onBrokenMarkdownLinks: 'throw' },
  },
  presets: [
    ['classic', {
      docs: {
        routeBasePath: '/',          // docs-only site
        sidebarPath: './sidebars.ts',
        editUrl: 'https://github.com/acme/acme/edit/main/website/',
        versions: { current: { label: 'Next', banner: 'unreleased' } },
      },
      blog: false,
    }],
  ],
};
export default config;
```

- Older 3.x releases accept `onBrokenMarkdownLinks` at the top level; recent 3.x versions
  deprecate that in favour of `markdown.hooks.onBrokenMarkdownLinks`. Use whichever the
  installed version documents; the build warns if you pick the deprecated one.
- `markdown.format: 'detect'` avoids MDX parsing errors in plain `.md` files that contain
  `<` or `{`.
- Versioning: `npm run docusaurus docs:version 2.0` copies `docs/` to
  `versioned_docs/version-2.0/` and the sidebar to `versioned_sidebars/`. Edits to old
  versions go in those folders, not in `docs/`.
- Redirects: `@docusaurus/plugin-client-redirects` with `redirects: [{ from, to }]`.
  Client-side only; set server redirects too if the host supports them.
- Search: Algolia DocSearch (apply for the free program for open source) or a local
  search plugin for private or small sites.
- Build output: `build/`. Preview locally with `npm run serve`.

## Astro Starlight

Scaffold (network): `npm create astro@latest -- --template starlight`

```js
// astro.config.mjs
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import starlightLinksValidator from 'starlight-links-validator';

export default defineConfig({
  site: 'https://docs.acme.dev',
  integrations: [
    starlight({
      title: 'Acme Docs',
      editLink: { baseUrl: 'https://github.com/acme/acme/edit/main/' },
      plugins: [starlightLinksValidator()],
      sidebar: [
        { label: 'Start here', items: ['getting-started', 'installation'] },
        { label: 'Guides', autogenerate: { directory: 'guides' } },
        { label: 'Reference', autogenerate: { directory: 'reference' } },
      ],
    }),
  ],
});
```

```ts
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';

export const collections = {
  docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
};
```

- Content lives in `src/content/docs/`; the file path is the URL.
- Search: Pagefind runs at build time, nothing to configure. It only indexes the built
  site, so `astro dev` shows no search results; test with `astro build && astro preview`.
- `starlight-links-validator` (community plugin, install it) fails the build on broken
  internal links and anchors.
- Versioning: none built in. Options are a community plugin, separate deployments per
  major on subdomains, or a single latest site.
- Redirects: `redirects` in `astro.config.mjs` (Astro core).
- Build output: `dist/`.

## Mintlify

Install the CLI (network): `npm i -g mint`. The older `mintlify` package name is legacy.

```bash
mint dev             # local preview at localhost:3000
mint broken-links    # internal link check; run from the folder containing docs.json
```

`docs.json` replaced `mint.json` as the config file. Minimal shape:

```json
{
  "$schema": "https://mintlify.com/docs.json",
  "theme": "mint",
  "name": "Acme",
  "colors": { "primary": "#0F766E" },
  "navigation": {
    "groups": [
      { "group": "Get started", "pages": ["index", "quickstart"] },
      { "group": "API reference", "openapi": "openapi.json" }
    ]
  }
}
```

- Pages are `.mdx` with frontmatter (`title`, `description`). Page paths in `navigation`
  omit the extension.
- OpenAPI: point a group or tab at the spec to generate endpoint pages with a playground.
  Pair with the api-documentation skill for spec quality.
- Versions: `navigation.versions` arrays, each with its own groups.
- Deployment happens through Mintlify's GitHub app on push; CI in your repo should still
  run `mint broken-links` on PRs. A folder with a Python `.venv` inside the docs root can
  confuse the CLI; keep docs in their own directory.
- Some features (custom domains on certain plans, analytics, AI assistant) depend on the
  paid tier. Confirm with the user before relying on them.

## MkDocs (escape hatch)

For Python projects already using it:

```bash
pip install mkdocs-material          # or the project's pinned docs requirements
mkdocs serve
mkdocs build --strict                # warnings (broken links, missing nav files) fail the build
```

- `validation:` options in `mkdocs.yml` (MkDocs 1.6+) control how strictly links, anchors
  and nav entries are checked.
- Versioning with `mike` (`mike deploy 2.0 latest --update-aliases`) publishes each
  version to a `gh-pages` branch; this pushes to the remote, so confirm before running it.
- Check the current maintenance status of the theme you rely on before starting a new
  site on it; prefer the platforms above for greenfield projects.

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem