Phase 3 — Dual-output docs site with Starlight
IMPL 0003: Phase 3 — Dual-output docs site with Starlight
Section titled “IMPL 0003: Phase 3 — Dual-output docs site with Starlight”Status: Completed Author: Donald Gifford Date: 2026-05-31
- Objective
- Scope
- Implementation Phases
- File Changes
- Testing Plan
- Dependencies
- Resolved Decisions
- References
Objective
Section titled “Objective”Implement the dual-output docs site: keep the existing MkDocs setup intact for
Backstage TechDocs and add an Astro + Starlight pipeline that renders the same
docs/ tree as a modern public site served from https://claudelint.dev via
Cloudflare Pages.
Implements: DESIGN-0003 — refer there for architecture, deploy decisions (Cloudflare Pages + Pagefind + claudelint.dev), and rollback semantics.
In Scope
Section titled “In Scope”- New
site/directory housing the Astro + Starlight project (astro.config.mjs,package.json, Astro shell pages). - Starlight integration reads from the shared root
docs/tree (no doc relocation). - Node toolchain pinned via
mise.toml;package-lock.jsoncommitted. - CommonMark audit of existing docs;
markdownlint-cli2config extended to block MkDocs-only syntax. - New
.github/workflows/docs.ymlrunningastro buildon PRs and pushes tomainthat touchdocs/**,site/**,astro.config.mjs,package.json, orpackage-lock.json. - Cloudflare Pages project bound to this repo’s
mainbranch with PR preview deploys. - DNS cutover:
claudelint.dev→ Cloudflare Pages project. - Mermaid render parity verified across MkDocs and Starlight.
- README and CONTRIBUTING updated to document the dual-pipeline workflow.
- CLAUDE.md updated with the new
site/directory and docs commands.
Out of Scope
Section titled “Out of Scope”- Replacing or migrating MkDocs (Backstage requirement; explicit non-goal in DESIGN-0003).
- Versioned docs, blog, or MDX/React components (deferred per DESIGN-0003 non-goals).
- Teaching
doczto emit a Starlight nav config (deferred; filesystem auto-discovery covers v1). - Algolia DocSearch (Pagefind only for v1).
- Migration to MkDocs 2.0 or Zensical (future ADR/RFC).
Implementation Phases
Section titled “Implementation Phases”Each phase builds on the previous one. A phase is complete when all its tasks are checked off and its success criteria are met.
Phase 1 — Scaffold the Starlight project
Section titled “Phase 1 — Scaffold the Starlight project”Stand up a working Astro + Starlight skeleton in site/ that anyone can run
locally with npm run dev and see the default Starlight landing page. No
content wiring yet — the goal is to prove the toolchain works.
- Pin Node LTS in
mise.toml(# renovate:annotation included so Renovate tracks it). - Create
site/directory. - Run
npm create astro@latest site -- --template starlight --yes --no-install(or equivalent) inside the worktree. - Move/rewrite generated files so the project lives at
site/(Astro project root) withpackage.jsonatsite/package.json. - Add
.gitignoreentries:site/node_modules/,site/dist/,site/.astro/,site/.astro-cache/. - Pin Astro +
@astrojs/starlightinsite/package.jsonwith caret ranges; commitsite/package-lock.json. - Add a Renovate config entry (or rely on the default
npmmanager) so Astro/Starlight bumps land as PRs. (default npm manager handles it per Q8) - Add npm scripts:
npm run dev,npm run build,npm run preview,npm run check. - Run
cd site && npm install && npm run devlocally to verify the default Starlight site renders athttp://localhost:4321. (verified vianpm run check+npm run build; dev server starts cleanly) - Add a
justfilerecipe (just docs-dev) that wrapscd site && npm run devso contributors don’t have to remember the directory.
Success Criteria
Section titled “Success Criteria”just docs-dev(orcd site && npm run dev) serves the default Starlight site onhttp://localhost:4321with no errors.cd site && npm run buildproduces asite/dist/directory with HTML.cd site && npm run check(Astro’s type-check) passes.- No Node-toolchain artifacts (
node_modules/,dist/) are committed.
Phase 2 — Wire shared docs/ source into Starlight
Section titled “Phase 2 — Wire shared docs/ source into Starlight”Configure Starlight to read Markdown from the existing root docs/ tree, not
from site/src/content/docs/. The sidebar should auto-discover docs and group
them by type so the navigation is sensible without a hand-maintained config.
- Configure Astro’s content collection (or
srcDir) inastro.config.mjsto point at../docs/fromsite/. (final approach: symlinksite/src/content/docs -> ../../../docs+ the bundleddocsLoader(). The first attempt usedglob({ base: '../docs' }), which serves pages but breaks sidebar autogenerate — Starlight strips a hardcodedsrc/content/docs/prefix from each entry’s filePath before matching groups. See the comment insite/src/content.config.ts.) - Configure Starlight’s
sidebarfor filesystem auto-discovery, grouping by directory (rfc/,adr/,design/,impl/,plan/,investigation/). (Starlight v0.39 requiresitems: [{ autogenerate }]wrapping. Later evolved into the shipped five-group layout — Install / Rules / Development / Reference / Changelog — with the six docz type groups nested under Development; see CLAUDE.md §Sidebar structure.) - Verify each doc type’s README renders as the group landing page. (renders
at
/<type>/readme/; sidebar autogenerate exposes it) - Verify all existing RFC/ADR/DESIGN/IMPL/PLAN/INV docs are reachable from the sidebar.
- Confirm
<!--toc:start-->/<!--toc:end-->blocks (docz-generated) don’t render as visible content in Starlight. (HTML comments pass through, invisible) - Confirm relative cross-doc links (e.g.
[DESIGN-0001](../design/0001-...)from an IMPL doc) resolve correctly in Starlight. (custom remark pluginsite/src/plugins/remark-md-link-rewriter.mjsrewrites.mdrelative links to absolute Starlight routes) - Add a
docs/index.mdlanding page (or wire Starlight to use the existingdocs/README.md) as the public site homepage. (started as the existingdocs/index.md+ minimaltitle:frontmatter; later rewritten from the README pitch — Install / Quickstart / Output formats / Linted artifacts. A hero + CardGrid upgrade is designed in DESIGN-0004.) - Add
editLinkconfig so each doc page links to its GitHub source for “Edit on GitHub”. - Spot-check one doc per type by browsing locally; fix any render issues. (verified via build output + grep on rendered HTML)
Success Criteria
Section titled “Success Criteria”- Sidebar shows all six doc-type groups (RFC, ADR, DESIGN, IMPL, PLAN, Investigation) with all docs listed under each.
- Clicking any doc renders its Markdown content with frontmatter title as the H1 and the body rendered correctly.
- The site homepage (root
/) shows the landing page content (not a 404). - “Edit on GitHub” link on a doc page resolves to the correct file in the repo.
- No relative-link breakage observed during spot checks.
Phase 3 — Markdown audit + CommonMark lint enforcement
Section titled “Phase 3 — Markdown audit + CommonMark lint enforcement”Sweep the existing docs/ tree for MkDocs-specific Markdown syntax (!!! note,
???, pymdownx features) that Starlight won’t render. Convert any findings to
CommonMark + GFM. Then extend markdownlint-cli2 to block reintroduction at
lint time.
-
grep -RE '^\s*!!!|^\s*\?\?\?' docs/— flag admonitions. (no hits) -
grep -R 'pymdownx' docs/— flag pymdownx-specific syntax. (no hits) - Convert admonitions to standard
> [!NOTE]/> [!WARNING](GFM) or plain blockquotes. (none needed) - Convert any tabs/snippets to plain Markdown. (none present)
- Verify Mermaid fenced blocks (
```mermaid ... ```) render in both MkDocs and Starlight after picking a plugin. (deferred to the Phase 6 parity check. As of 2026-06-08 neither pipeline has Mermaid wired — norehype-mermaidinsite/package.json, nopymdownx.superfencesinmkdocs.yml— and no doc contains a Mermaid block yet. Wire both plugins before the first Mermaid diagram lands.) - Update
.markdownlint.json/.markdownlint-cli2.yamlconfig to enforce CommonMark + GFM only (no MkDocs extensions). (.markdownlint.yaml+ custom grep checks injust lint-md; only legitimate relaxations are MD025front_matter_title: ""for docz title convention and MD024siblings_only: truefor repeated phase sub-headings) - Add
just lint-mdrecipe (or fold intojust lint) runningmarkdownlint-cli2ondocs/**/*.md. (runs markdownlint plus three regex grep checks for!!!,???, andpymdownx) - Run
markdownlint-cli2 --fixondocs/; commit any auto-fixed nits. - Manually fix any remaining violations. (MD040 language hints, MD034 bare
URLs, MD038 space-in-code, MD046 mixed code-block style, MD051 broken
anchors, MD018
#issueat line start — fixed in source rather than disabling rules)
Success Criteria
Section titled “Success Criteria”grep -RE '^\s*!!!|^\s*\?\?\?|pymdownx' docs/returns zero hits.markdownlint-cli2 'docs/**/*.md'exits 0 with the CommonMark profile.- All existing docs render in the local Starlight dev server with no visible syntax artifacts.
- MkDocs still builds locally (
mkdocs build --strict) after the syntax conversions.
Phase 4 — CI workflow for Starlight builds
Section titled “Phase 4 — CI workflow for Starlight builds”Add a new GitHub Actions workflow that runs astro check + astro build on PRs
and pushes to main that touch the docs surface. Start non-blocking so it
doesn’t gate work while we shake out parity issues, then flip to required after
two clean PRs.
- Create
.github/workflows/docs.yml. - Triggers:
pushtomainandpull_requesttomainwithpathsfilter ondocs/**,site/**,astro.config.mjs,package.json,package-lock.json,.github/workflows/docs.yml. (paths cover docs/, site/, .markdownlint.yaml, justfile, mise.toml, the workflow itself; astro config + package.json live under site/) - Steps: checkout,
actions/setup-node@v6reading version frommise.toml(orpackage.jsonengines),npm ci,npm run check,npm run build. (usesjdx/mise-action@v4to pin Node frommise.toml— single source of truth — rather than a separate setup-node step) - Cache
~/.npmandsite/node_modulesto keep CI fast. (actions/cache@v4 keyed on package-lock.json) - Upload
site/dist/as a workflow artifact for inspection on failure. (actions/upload-artifact@v4, 7-day retention) - Add a markdown-lint step (
markdownlint-cli2ondocs/**/*.md). (separatelint-mdjob runningjust lint-md) - Also: extend
.github/labeler.ymlso PRs touchingsite/**get thedocumentationlabel. - First-pass: do not add to branch protection. Observe two PRs running clean. (superseded — Donald promoted the checks directly after PR #40’s clean run rather than waiting for a second PR)
- After two clean runs: add
Docs / build(or chosen job name) as a required status check. (done —Docs / Lint Markdown+Docs / Build Starlightadded tomainbranch protection via the dashboard, per PR #40’s task list) - Update CLAUDE.md “Git / PR conventions” section to mention the docs check.
(added in 6fdacb2 —
Docs CIbullet in CLAUDE.md L126)
Success Criteria
Section titled “Success Criteria”- A PR that adds or edits any file under
docs/runs the new workflow and surfaces build success/failure. - A PR that touches only Go code skips the workflow (paths filter working).
npm run checkandnpm run buildboth pass for the currentmainstate.- After two clean runs, the workflow is a required check in branch protection.
Phase 5 — Cloudflare Pages deploy + PR previews
Section titled “Phase 5 — Cloudflare Pages deploy + PR previews”Stand up the Cloudflare Pages project, wire it to the repo, configure build
settings, and verify production + preview deploys work end-to-end. At this point
the site is live at the Pages-assigned subdomain (e.g. claudelint.pages.dev),
not yet at claudelint.dev.
Mostly done (2026-06-08). Donald created the Pages project via the dashboard while PR #40 was open; preview deploys are live (verified at
docs-site.claudelint.pages.dev) and theCloudflare Pagescheck passes on the PR. Remaining: confirm the Node pin + production-branch settings in the dashboard, the first production deploy (fires when PR #40 merges tomain), and optionally saving the API secrets.
- In Cloudflare dashboard: create a Pages project named
claudelintbound to thedonaldgifford/claudelintGitHub repo. (exists — theCloudflare Pagescheck on PR #40 links to the project dashboard) - Configure build settings: framework preset = Astro, build command =
cd site && npm install && npm run build, build output directory =site/dist, root directory = repo root. (working — preview builds succeed and serve the full site) - Set Node version env var in Cloudflare Pages to match
mise.tomlNode pin (currently22.20.0). (set via dashboard, per PR #40’s task list) - Set production branch to
main. (confirmed — the post-merge production deploy fired off PR #40’s merge commit) - Enable preview deployments for all branches with open PRs. (working —
per-commit preview URLs like
115f2e1f.claudelint.pages.devplus thedocs-site.claudelint.pages.devbranch alias) - Trigger first deploy by pushing a no-op to
main(or by manually deploying); verify the Pages-assigned subdomain works. (fired off PR #40’s merge —claudelint.pages.devandclaudelint.devboth serve 200, verified 2026-07-09) - Open a test PR that touches a doc; verify Cloudflare Pages auto-comments
with the preview URL. (PR #40 itself — the
Cloudflare Pagesstatus check carries the deploy link) - Verify Pagefind search works on the deployed preview (Cloudflare may need
an extra build step to run
pagefind; Starlight handles this by default but double-check). (verified —/pagefind/pagefind.jsserves 200 on the branch alias; search UI returns results in the preview) - Save the Cloudflare Pages project ID and account ID as repo secrets
(
CLOUDFLARE_ACCOUNT_ID,CLOUDFLARE_API_TOKEN) in case we want a custom deploy workflow later. (not saved as of 2026-06-08 —gh secret listshows neither; optional until we need a custom deploy workflow)
Success Criteria
Section titled “Success Criteria”https://claudelint.pages.dev(or assigned Pages subdomain) serves the rendered docs site.- A PR touching
docs/produces an automatic preview deploy with a unique URL. - Pagefind search returns results for queries like “MCP” or “marketplace” against the deployed site.
- No build errors in the Cloudflare Pages build log for the latest production deploy.
Phase 6 — Cutover to claudelint.dev + polish
Section titled “Phase 6 — Cutover to claudelint.dev + polish”Point claudelint.dev at the Cloudflare Pages project, verify HTTPS, and do
final documentation/onboarding work so future contributors know about the
dual-pipeline setup.
- In Cloudflare DNS for
claudelint.dev: add the Pages project as a custom domain (Cloudflare handles cert provisioning automatically). (done via dashboard, per PR #40’s task list) - Verify
https://claudelint.devresolves to the Starlight site with a valid cert. (verified 2026-07-09 after PR #40 merged — site, Pagefind, rules reference, and in-site changelog all serve 200 over HTTPS) - Test redirect behavior: HTTP → HTTPS,
www.claudelint.dev→claudelint.dev(or whichever direction you prefer). (HTTP→HTTPS works;www.claudelint.devhas no DNS record as of 2026-07-09 — Donald to decide whether to add a www → apex redirect or leave www unresolvable) - Confirm
mkdocs build --strictstill passes against currentdocs/. (passes via newjust docs-mkdocs-checkrecipe — runsmkdocs build --strict -d build/mkdocsvia uvx; fixed one pre-existing broken cross-link inimpl/0001(../../RELEASE.md→ absolute GitHub URL) along the way; documentedsite_dircollision gotcha in CLAUDE.md) - Mermaid parity check: pick (or create) a doc with a Mermaid block, render
it via both MkDocs and Starlight, compare visually. (deferred — no
Mermaid blocks in
docs/today, and neither pipeline has the plugin wired yet (rehype-mermaid/pymdownx.superfences); do both halves together when the first diagram lands) - Update
README.mdwith a “Documentation” section linking tohttps://claudelint.dev. - Update
CONTRIBUTING.md(or create one) explaining: docs source =docs/; MkDocs and Starlight both read from there;just docs-devfor local Starlight preview;mkdocs servefor local MkDocs preview. (created) - Update
CLAUDE.mdwith: thesite/directory, the newjust docs-devrecipe, the dual-output design pointer, and a note that the markdown lint config enforces CommonMark. - Update IMPL-0003 phase checkboxes as work completes; flip status to “Implemented” when all phases are done. (status flipped to Completed 2026-07-09 after the production deploy verified. Two items remain open by design: the Mermaid parity check (deferred until the first Mermaid block lands — neither pipeline has the plugin wired) and the www-redirect decision + optional Cloudflare API secrets (Donald’s call). Landing-page polish continues in DESIGN-0004.)
- Move DESIGN-0003 status from “Draft” → “Implemented”. (flipped 2026-07-09 alongside this doc’s status)
Success Criteria
Section titled “Success Criteria”https://claudelint.devserves the docs site with a valid TLS cert and acceptable load time.- MkDocs
mkdocs build --strictstill passes; TechDocs feed is unaffected. - Mermaid parity check passes (same fenced syntax renders in both pipelines).
- README links to
https://claudelint.dev. - CONTRIBUTING and CLAUDE.md document the dual-pipeline workflow.
- DESIGN-0003 + IMPL-0003 status moved to Implemented.
File Changes
Section titled “File Changes”| File | Action | Description |
|---|---|---|
site/astro.config.mjs | Create | Astro + Starlight integration config; points content collection at ../docs/; configures editLink, sidebar auto-discovery, Mermaid plugin. |
site/package.json | Create | Astro + @astrojs/starlight + Mermaid plugin pinned with caret ranges; npm scripts (dev, build, preview, check). |
site/package-lock.json | Create | Committed lockfile for reproducible installs. |
site/src/content.config.ts | Create | Astro content collections config (if needed alongside srcDir approach). |
site/src/pages/ | Create | Astro shell pages: index landing page (if not using docs/README.md). |
.gitignore | Modify | Add site/node_modules/, site/dist/, site/.astro/. |
mise.toml | Modify | Pin Node LTS with Renovate annotation. |
.github/workflows/docs.yml | Create | PR + push CI workflow for Astro build + markdownlint. |
.markdownlint.json (or .markdownlint-cli2.yaml) | Modify | Enforce CommonMark + GFM; disallow MkDocs-specific syntax. |
justfile | Modify | Add docs-dev, docs-build, docs-check, lint-md recipes. |
docs/index.md (or wired docs/README.md) | Create/Modify | Public-site landing page content. |
docs/**/*.md | Modify | Convert any MkDocs-only Markdown to CommonMark. |
README.md | Modify | Add “Documentation” section pointing to claudelint.dev. |
CONTRIBUTING.md | Create/Modify | Document dual-pipeline doc workflow. |
CLAUDE.md | Modify | Document site/, new just recipes, lint config, and dual-output setup. |
Testing Plan
Section titled “Testing Plan”- Local dev (Phase 1):
cd site && npm run devserves Starlight athttp://localhost:4321with no errors. - Build smoke (Phase 1, 4):
cd site && npm run buildproduces a non-emptysite/dist/. - Content wiring (Phase 2): Sidebar enumerates all docs; each doc renders correctly; cross-doc relative links resolve.
- Markdown lint (Phase 3):
markdownlint-cli2 'docs/**/*.md'exits 0 with the CommonMark profile. - MkDocs parity (Phase 3, 6):
mkdocs build --strictsucceeds against the modifieddocs/tree. - CI (Phase 4): A PR touching
docs/triggers the new workflow; a Go-only PR does not. - Cloudflare preview (Phase 5): PR comment lists a preview URL; URL serves the modified docs.
- Production deploy (Phase 5, 6): Pages subdomain →
claudelint.devafter DNS cutover; valid TLS; Pagefind search returns results. - Mermaid parity (Phase 6): A test doc with a Mermaid block renders identically (visually equivalent) in MkDocs Material and Starlight.
- Rollback drill (Phase 6, optional): Verify that disabling the docs workflow or revoking the Cloudflare custom domain doesn’t impact MkDocs / TechDocs build behavior.
Dependencies
Section titled “Dependencies”- External services: Cloudflare account access with
claudelint.devregistered (Donald has this). - Tooling: Node LTS (added to
mise.tomlin Phase 1). - Existing tooling:
mkdocs,markdownlint-cli2,just,mise(all already present). - GitHub repo secrets:
CLOUDFLARE_ACCOUNT_ID+CLOUDFLARE_API_TOKEN(added in Phase 5, used only if we ever move off the auto-integration). - Blocking work: none — IMPL-0003 can proceed on the
docs/sitebranch independently of any Go work.
Resolved Decisions
Section titled “Resolved Decisions”All Open Questions resolved 2026-06-01.
- Q1 — Node toolchain location: Isolated
site/subdirectory with its ownpackage.json+ lockfile. Keeps Node out of the Go-centric repo root; CI path filters stay unambiguous. - Q2 — Node version pinning:
mise.tomlwith# renovate:annotation. Matches the rest of the repo’s tool management; Renovate keeps it current. - Q3 — Mermaid renderer:
rehype-mermaidvia@astrojs/markdown-remark. Server-side rendering, no client JS payload, same```mermaidfenced syntax MkDocs uses. - Q4 — CI blocking behavior: Non-required for the first two PRs after merge; flip to required check in branch protection once both come back clean. Avoids the “first run failed, can’t merge anything” trap.
- Q5 — Sidebar grouping: Filesystem auto-discovery, grouped by doc type
(RFC, ADR, DESIGN, IMPL, PLAN, Investigation), docs ordered numerically within
each group. Mirrors
docs/<type>/README.md. - Q6 — Landing page: Promote
docs/README.mdas the Starlight homepage via root-page config. Single Markdown source feeds both MkDocs and Starlight. - Q7 — Edit-link target:
editLinkpoints athttps://github.com/donaldgifford/claudelint/edit/main/{path}so readers can propose edits via GitHub’s web editor. - Q8 — Renovate: Default npm manager handles Astro/Starlight bumps; no custom group config.
References
Section titled “References”- DESIGN-0003 — the design this implements
- Starlight docs — Astro integration
- Astro Content Collections
— mechanism for reading shared
docs/ - Pagefind — default Starlight search
- Cloudflare Pages — deploy target
- rehype-mermaid — proposed Mermaid plugin (Q3)
- MkDocs 2.0 announcement — squidfunk, 2026-02-18 — context for the broader docs-tooling shift
- IMPL-0001, IMPL-0002 — phase-driven implementation tracking pattern this doc follows