diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8a176e09a2..9191849322d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,8 @@ + + # Contributing to NVIDIA NemoClaw Thank you for your interest in contributing to NVIDIA NemoClaw. This guide covers how to set up your development environment, run tests, and submit changes. @@ -76,8 +81,10 @@ These are the primary `make` and `npm` targets for day-to-day development: | `npm run typecheck:cli` | Type-check CLI TypeScript using `tsconfig.cli.json` (`bin/`, `scripts/`, `src/`, `test/`, `nemoclaw-blueprint/scripts/`) | | `npm test` | Run root-level tests (`test/*.test.js`) | | `cd nemoclaw && npm test` | Run plugin unit tests (Vitest) | -| `make docs` | Validate Fern documentation | -| `make docs-live` | Serve Fern docs locally with auto-rebuild | +| `npm run docs` | Validate Fern documentation with the pinned Fern CLI version | +| `npm run docs:live` | Serve Fern docs locally with auto-rebuild | +| `npm run docs:preview:watch` | Publish branch-based Fern previews when docs files change | +| `npm run docs:deps` | Print the pinned Fern CLI version used by docs commands | | `npx prek run --all-files` | Run all hooks from `.pre-commit-config.yaml` — see below | ### Git hooks (prek) @@ -106,7 +113,7 @@ Run the docs and hook checks instead: ```bash npx prek run --all-files -make docs +npm run docs ``` Leave `npm test` unchecked in the PR verification checklist unless you actually ran it. @@ -144,10 +151,13 @@ During release prep, run that skill first, make any doc version bumps, regenerat To build and preview docs locally: ```console -$ make docs # validate the Fern docs -$ make docs-live # serve Fern docs locally with auto-rebuild +$ npm run docs # validate Fern docs with the pinned Fern CLI version +$ npm run docs:live # serve Fern docs locally with auto-rebuild +$ npm run docs:preview:watch # publish branch-based Fern previews on file changes ``` +Use these npm scripts when validating docs for a PR. + See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for the full style guide and writing conventions. ### Doc-to-Skills Pipeline @@ -177,7 +187,7 @@ Follow these steps to submit a pull request. 1. Create a feature branch from `main`. 2. Make your changes with tests. -3. Run the relevant checks. For code changes, run `make check` and `npm test`. For doc-only changes, run `npx prek run --all-files` and `make docs`. +3. Run the relevant checks. For code changes, run `make check` and `npm test`. For doc-only changes, run `npx prek run --all-files` and `npm run docs`. 4. Open a PR. ### Commit Messages diff --git a/Makefile b/Makefile index 999a63d0abd..a7e4629b033 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: check lint format format-biome lint-ts format-ts check-installer-hash docs docs-deps docs-strict docs-live docs-clean +.PHONY: check lint format format-biome lint-ts format-ts check-installer-hash docs docs-deps docs-strict docs-live docs-preview-watch docs-clean check: npm run check @@ -38,5 +38,8 @@ docs-strict: docs-live: npm run docs:live +docs-preview-watch: + npm run docs:preview:watch + docs-clean: npm run docs:clean diff --git a/README.md b/README.md index 1fe76b97813..a17be1d085e 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,23 @@ Refer to the following pages on the official documentation website for more info | [CLI Commands](https://docs.nvidia.com/nemoclaw/latest/reference/commands.html) | Full NemoClaw CLI command reference. | | [Troubleshooting](https://docs.nvidia.com/nemoclaw/latest/reference/troubleshooting.html) | Common issues and resolution steps. | +### Build Docs Locally + +The public documentation site is built with Fern. +The repo pins the Fern CLI version in `fern/fern.config.json`. +Use the npm scripts so every docs command uses that pinned version. + +```bash +npm run docs +npm run docs:live +``` + +To publish a branch-based Fern preview whenever docs files change, run: + +```bash +npm run docs:preview:watch +``` + ## Project Structure The following directories make up the NemoClaw repository. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 27273a99951..d49187c564c 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,3 +1,8 @@ + + # Contributing to NemoClaw Documentation This guide covers how to write, edit, and review documentation for NemoClaw. If you change code that affects user-facing behavior, update the relevant docs in the same PR. @@ -103,18 +108,36 @@ For full usage details and all flags, see the docstring at the top of `scripts/d Verify the docs are built correctly by building them and checking the output. -The public site is built with Fern. To validate the Fern configuration and migrated MDX pages, run: +The public site is built with Fern. +The repo pins the Fern CLI version in `fern/fern.config.json`. +Use the npm scripts so every docs command uses that pinned version. + +To print the pinned Fern CLI version, run: + +```console +$ npm run docs:deps +``` + +To validate the Fern configuration and migrated MDX pages, run: ```console -$ make docs +$ npm run docs ``` To serve the docs locally and automatically rebuild on changes, run: ```console -$ make docs-live +$ npm run docs:live ``` +To publish a branch-based Fern preview whenever docs files change, run: + +```console +$ npm run docs:preview:watch +``` + +The preview watcher uses the current Git branch name as the Fern preview ID and watches the `docs/` and `fern/` directories. + Fern `.mdx` pages are the source for generated user skills. Legacy `.md` pages may remain temporarily for parity checks, but release-prep skill generation should pass `--doc-platform fern-mdx`. ## Doc-Only PR Verification @@ -124,7 +147,7 @@ Before opening a doc-only PR, run: ```console $ npx prek run --all-files -$ make docs +$ npm run docs ``` Leave `npm test` unchecked in the PR verification checklist unless you actually ran it. @@ -264,7 +287,7 @@ Use these consistently: 1. Create a branch following the project convention. 2. Make your changes. -3. Build locally with `make docs` and verify the output. +3. Build locally with `npm run docs` and verify the output. 4. Open a PR with `docs:` as the conventional commit type. ```text diff --git a/docs/_components/BadgeLinks.tsx b/docs/_components/BadgeLinks.tsx index 8a23ff894a5..eefe8e4414c 100644 --- a/docs/_components/BadgeLinks.tsx +++ b/docs/_components/BadgeLinks.tsx @@ -1,9 +1,16 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + /** * Badge links for GitHub, License, project status, Discord, etc. * Uses a flex wrapper to display badges horizontally and hides Fern's * external-link icon that otherwise stacks under each badge image. * Requires the `.badge-links` CSS rule from main.css. */ +declare const React: unknown; + export type BadgeItem = { href: string; src: string; @@ -15,10 +22,37 @@ export function BadgeLinks({ badges = [] }: { badges?: BadgeItem[] }) { return null; } return ( -
+
{badges.map((b) => ( - - {b.alt} + + {b.alt} ))}
diff --git a/docs/_components/CommandTerminal.tsx b/docs/_components/CommandTerminal.tsx new file mode 100644 index 00000000000..99f56d46e74 --- /dev/null +++ b/docs/_components/CommandTerminal.tsx @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +declare const React: unknown; + +export function CommandTerminal({ command }: { command: string }) { + return ( +
+
+ + + +
+
+ $ + {command} +
+
+ ); +} + +function dotStyle(background: string) { + return { + background, + borderRadius: "50%", + display: "inline-block", + height: "12px", + width: "12px", + }; +} diff --git a/docs/index.mdx b/docs/index.mdx index 4706d0c759f..3c5e589744c 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -9,6 +9,7 @@ position: 1 --- import { BadgeLinks } from "./_components/BadgeLinks"; +import { CommandTerminal } from "./_components/CommandTerminal"; -
-
- - - -
-
-
- $ - curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -
-
-
+ @@ -69,7 +58,11 @@ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -The Fern migration is starting with the About section while the remaining MyST pages stay in place as the legacy source for generated user skills and parity checks. +For getting started guidance, see [Quickstart](/get-started/quickstart). + +Learn about the NemoClaw architecture in [Architecture Overview](/about/how-it-works), [Ecosystem](/about/ecosystem), and [Architecture Details](/reference/architecture). + +Find NemoClaw user skills for your host AI coding assistant in [Agent Skills](/resources/agent-skills). --- diff --git a/fern/components/CurrentRelease.tsx b/fern/components/CurrentRelease.tsx new file mode 100644 index 00000000000..c0f89a24604 --- /dev/null +++ b/fern/components/CurrentRelease.tsx @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Inline component that renders the current docs version (e.g. "26.02"). + * Parses the version from the URL path (/v26.02/...) at runtime. + * Use in headers or prose: # NeMo Curator Release Notes + */ +export function CurrentRelease() { + if (typeof window !== "undefined") { + const match = window.location.pathname.match(/\/v(\d+\.\d+)(?:\/|$)/); + if (match) return {match[1]}; + } + return 26.02; +} diff --git a/fern/components/CustomFooter.tsx b/fern/components/CustomFooter.tsx new file mode 100644 index 00000000000..f64cd77c879 --- /dev/null +++ b/fern/components/CustomFooter.tsx @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Custom footer for NVIDIA docs (Fern native header/footer). + * Markup and class names match the original custom-app footer 1:1 so that + * fern/main.css (footer + Built with Fern styles) applies correctly: + * dark mode logo, responsive layout, and Built with Fern tooltip. + */ +export default function CustomFooter() { + const currentYear = new Date().getFullYear(); + const logoUrl = + "https://fern-image-hosting.s3.us-east-1.amazonaws.com/nvidia/NVIDIA_Logo_0.svg"; + + return ( + + ); +} diff --git a/fern/docs.yml b/fern/docs.yml index 0a177b1b014..3482a3ac6c3 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -9,6 +9,8 @@ instances: title: NVIDIA NemoClaw +global-theme: nvidia + announcement: message: "🔔 NVIDIA NemoClaw is alpha software. APIs and behavior may change without notice. Do not use in production." @@ -33,9 +35,6 @@ theme: footer-nav: minimal logo: - dark: ./assets/NVIDIA_dark.svg - light: ./assets/NVIDIA_light.svg - height: 20 href: /nemoclaw/latest right-text: NemoClaw @@ -55,6 +54,7 @@ navbar-links: experimental: mdx-components: - ../docs/_components + - ./components basepath-aware: true versions: diff --git a/fern/fern.config.json b/fern/fern.config.json index 83a8e22b923..e386dca7e3c 100644 --- a/fern/fern.config.json +++ b/fern/fern.config.json @@ -1,4 +1,4 @@ { "organization": "nvidia", - "version": "5.23.3" + "version": "5.30.0" } diff --git a/package.json b/package.json index 3d62b4e9bc7..676878c8932 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "docs:deps": "node -p \"require('./fern/fern.config.json').version\" | xargs -I {} npx --yes fern-api@{} --version", "docs:strict": "FERN_VERSION=$(node -p \"require('./fern/fern.config.json').version\") && cd fern && npx --yes \"fern-api@${FERN_VERSION}\" check", "docs:live": "FERN_VERSION=$(node -p \"require('./fern/fern.config.json').version\") && cd fern && npx --yes \"fern-api@${FERN_VERSION}\" docs dev", + "docs:preview:watch": "node scripts/watch-fern-preview.mjs", "docs:clean": "rm -rf .fern-cache fern/.fern-cache docs/_build", "prepare": "if command -v tsc >/dev/null 2>&1 || [ -x node_modules/.bin/tsc ]; then npm run build:cli; fi && (npm install --omit=dev --ignore-scripts 2>/dev/null || true) && if [ -d .git ]; then bash scripts/npm-link-or-shim.sh; if command -v prek >/dev/null 2>&1; then prek install; else echo \"Skipping git hook setup (prek not installed)\"; fi; fi", "prepublishOnly": "git describe --tags --match 'v*' | sed 's/^v//' > .version && test -s .version && cd nemoclaw && env -u npm_config_global -u npm_config_prefix -u npm_config_omit npm install --ignore-scripts && ./node_modules/.bin/tsc" diff --git a/scripts/watch-fern-preview.mjs b/scripts/watch-fern-preview.mjs new file mode 100755 index 00000000000..f0b2fe8939d --- /dev/null +++ b/scripts/watch-fern-preview.mjs @@ -0,0 +1,198 @@ +#!/usr/bin/env node +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { spawn, spawnSync } from "node:child_process"; +import { existsSync, readdirSync, readFileSync, statSync, watch } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const fernRoot = path.join(repoRoot, "fern"); +const watchRoots = ["docs", "fern"]; +const ignoredDirectoryNames = new Set([".fern-cache", ".git", "_build", "node_modules"]); +const debounceMs = 500; + +const fernVersion = JSON.parse( + readFileSync(path.join(repoRoot, "fern", "fern.config.json"), "utf8"), +).version; + +const branchName = currentBranchName(); +let running = false; +let pending = false; +let debounceTimer; +let currentChild; +const watchers = new Map(); + +console.log(`Using Fern preview id: ${branchName}`); +console.log(`Watching: ${watchRoots.join(", ")}`); + +for (const root of watchRoots) { + watchDirectoryTree(path.join(repoRoot, root)); +} + +runFernGenerate("initial run"); + +process.on("SIGINT", () => { + closeWatchers(); + currentChild?.kill("SIGINT"); + process.exit(130); +}); + +process.on("SIGTERM", () => { + closeWatchers(); + currentChild?.kill("SIGTERM"); + process.exit(143); +}); + +function currentBranchName() { + const result = spawnSync("git", ["branch", "--show-current"], { + cwd: repoRoot, + encoding: "utf8", + }); + const branch = result.stdout.trim(); + + if (result.status !== 0 || branch.length === 0) { + console.error("Could not determine the current Git branch name."); + process.exit(1); + } + + return branch; +} + +function watchDirectoryTree(directory) { + if (!existsSync(directory) || watchers.has(directory)) { + return; + } + + try { + watchers.set( + directory, + watch(directory, { persistent: true }, (_eventType, filename) => { + if (filename) { + const changedPath = path.join(directory, filename.toString()); + if (shouldIgnorePath(changedPath)) { + return; + } + addWatcherForNewDirectory(changedPath); + } + scheduleRun(); + }), + ); + } catch (error) { + if (!isIgnorableWatchError(error)) { + throw error; + } + return; + } + + let entries; + try { + entries = readdirSync(directory, { withFileTypes: true }); + } catch (error) { + if (!isIgnorableWatchError(error)) { + throw error; + } + return; + } + + for (const entry of entries) { + if (!entry.isDirectory()) { + continue; + } + + const childDirectory = path.join(directory, entry.name); + if (!shouldIgnorePath(childDirectory)) { + watchDirectoryTree(childDirectory); + } + } +} + +function addWatcherForNewDirectory(changedPath) { + if (!existsSync(changedPath)) { + return; + } + + try { + const stats = statSync(changedPath); + if (stats.isDirectory()) { + watchDirectoryTree(changedPath); + } + } catch (error) { + if (!isIgnorableWatchError(error)) { + throw error; + } + } +} + +function isIgnorableWatchError(error) { + return error?.code === "ENOENT" || error?.code === "EPERM"; +} + +function shouldIgnorePath(candidatePath) { + return candidatePath.split(path.sep).some((part) => ignoredDirectoryNames.has(part)); +} + +function scheduleRun() { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => runFernGenerate("file change"), debounceMs); +} + +function runFernGenerate(reason) { + if (running) { + pending = true; + return; + } + + running = true; + pending = false; + + const args = [ + "--yes", + `fern-api@${fernVersion}`, + "generate", + "--docs", + "--preview", + "--id", + branchName, + "--force", + ]; + + console.log(`\n[${new Date().toLocaleTimeString()}] Running Fern (${reason})`); + console.log(`cd fern && npx ${args.join(" ")}`); + + const child = spawn("npx", args, { + cwd: fernRoot, + stdio: "inherit", + }); + currentChild = child; + + child.on("error", (error) => { + running = false; + currentChild = undefined; + console.error(`Failed to start Fern preview generation: ${error.message}`); + }); + + child.on("exit", (code, signal) => { + running = false; + currentChild = undefined; + + if (signal) { + console.log(`Fern stopped by signal ${signal}.`); + } else if (code === 0) { + console.log("Fern preview generation completed."); + } else { + console.error(`Fern preview generation failed with exit code ${code}.`); + } + + if (pending) { + runFernGenerate("queued file change"); + } + }); +} + +function closeWatchers() { + for (const watcher of watchers.values()) { + watcher.close(); + } +}