diff --git a/.agents/catalog-skills.yaml b/.agents/catalog-skills.yaml deleted file mode 100644 index 94616a5f33..0000000000 --- a/.agents/catalog-skills.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Explicit allowlist for NemoClaw skills exported to the NVIDIA Verified Skills catalog. -# Keep this file deterministic: no timestamps, no generated comments, and no implicit globs. -# The export is regenerated with: python3 scripts/export-catalog-skills.py -version: 1 -source: .agents/skills -export: skills -include: - - skill: nemoclaw-skills-guide - rationale: Public index for user-facing NemoClaw skills. - - skill: nemoclaw-user-agent-skills - rationale: Public user documentation skill. - - skill: nemoclaw-user-configure-inference - rationale: Public user documentation skill. - - skill: nemoclaw-user-configure-security - rationale: Public user documentation skill. - - skill: nemoclaw-user-deploy-remote - rationale: Public user documentation skill. - - skill: nemoclaw-user-get-started - rationale: Public user documentation skill. - - skill: nemoclaw-user-manage-policy - rationale: Public user documentation skill. - - skill: nemoclaw-user-manage-sandboxes - rationale: Public user documentation skill. - - skill: nemoclaw-user-monitor-sandbox - rationale: Public user documentation skill. - - skill: nemoclaw-user-overview - rationale: Public user documentation skill. - - skill: nemoclaw-user-reference - rationale: Public user documentation skill. -exclude: - - pattern: nemoclaw-maintainer-* - rationale: Internal maintainer workflows are not catalog/customer-facing. - - pattern: nemoclaw-contributor-* - rationale: Contributor workflows are repo-local until explicitly approved for catalog publication. -metadata: - minNemoClawVersion: "0.1.0" - testedNemoClawVersion: "0.1.0" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f826c36a7c..36d30b0982 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -31,7 +31,6 @@ /spark-install.md @NVIDIA/nemoclaw-engineer # ── Agent skills catalog ── -/.agents/catalog-skills.yaml @NVIDIA/nemoclaw-maintainer @NVIDIA/nemoclaw-engineer /.agents/skills/ @NVIDIA/nemoclaw-maintainer @NVIDIA/nemoclaw-engineer /skills/ @NVIDIA/nemoclaw-maintainer @NVIDIA/nemoclaw-engineer diff --git a/.github/catalog-skills-signing-flow.md b/.github/catalog-skills-signing-flow.md index ba560b0926..773dc99808 100644 --- a/.github/catalog-skills-signing-flow.md +++ b/.github/catalog-skills-signing-flow.md @@ -1,79 +1,51 @@ -# NemoClaw catalog skills signing flow +# Publishing a NemoClaw skill to the NVIDIA Verified Skills catalog -This diagram shows the required sequence for publishing NemoClaw user-facing skills into the NVIDIA Verified Skills catalog through the generated `skills/` export. +The `skills/` directory at the repo root is the NVSkills CI watched location. +Whatever lives there is what gets signed and published. There is no +allowlist, manifest, or generator script — adding a skill to the catalog +means copying the source skill into `skills/` and pushing it through +NVSkills CI signing. -```mermaid -sequenceDiagram - autonumber - actor Maintainer as Human maintainer - participant Source as NemoClaw source
.agents/skills + .agents/catalog-skills.yaml - participant Exporter as scripts/export-catalog-skills.py - participant Export as Generated export
skills - participant PRCI as PR workflow
CI / Pull Request - participant Refresh as Skills / Catalog Refresh workflow - participant PR as Same-repo refresh PR - participant NVSkills as NVSkills CI signer - participant Main as NVIDIA/NemoClaw main - participant Target as NVIDIA/skills sync +## Add a skill to the catalog - Note over Source,Export: Implementation PR path added by issue #4282 - Maintainer->>Source: Curate catalog-safe skills in .agents/catalog-skills.yaml - Maintainer->>Exporter: Run python3 scripts/export-catalog-skills.py - Exporter->>Export: Copy allowlisted skills as real files
write catalog-metadata.json
preserve skill.oms.sig + skill-card.md if present - Maintainer->>PRCI: Open implementation or content PR - PRCI->>Exporter: python3 scripts/export-catalog-skills.py --check --allow-missing - Exporter-->>PRCI: Pass before first export exists;
after refresh PR, fail if skills is stale or hand-edited - Maintainer->>Main: Merge reviewed PR after checks pass - - Note over Refresh,PR: Post-merge refresh automation added by this PR - Maintainer->>Refresh: Optional manual workflow_dispatch
dry_run=true first - Refresh->>Exporter: Regenerate export and show diff only - Refresh-->>Maintainer: No branch or PR created in dry run - Maintainer->>Refresh: Run dry_run=false when ready
optionally request_nvskills_ci=true - Refresh->>Exporter: Regenerate export - Exporter->>Export: Update generated files if source changed - Refresh->>PR: Create/update automation/catalog-skills-refresh PR
with export diff - - alt request_nvskills_ci=true and bot is accepted - Refresh->>PR: Comment /nvskills-ci - else bot rejected or manual process preferred - Maintainer->>PR: Comment /nvskills-ci manually - end - - NVSkills->>PR: Push signing artifacts
skill.oms.sig + skill-card.md - PRCI->>Exporter: Re-run --check; signer artifacts are preserved - Maintainer->>PR: Review generated export and signing artifacts - Maintainer->>Main: Merge signed refresh PR - Target->>Main: Sync configured NemoClaw catalog path - Target->>Target: Keep only skills with skill.oms.sig and skill-card.md +```bash +mkdir -p skills +cp -R .agents/skills/nemoclaw-user- skills/ +git add skills/nemoclaw-user- +git commit -m "chore(skills): publish nemoclaw-user-" ``` -## Human handoff points +Open the PR, comment `/nvskills-ci`, wait for the signing job to push back +`skill.oms.sig` and `skill-card.md`, then merge. Repeat per skill — NVSkills +CI signs one at a time. -These are the manual review and approval points in the catalog signing flow. +## Update an already-published skill -- Curate `.agents/catalog-skills.yaml` when public skill scope changes. -- Review the generated `skills/` diff in the same PR as the allowlist/source update. -- Manually comment `/nvskills-ci` if the workflow bot cannot request signing. -- Review and merge the signer-updated PR before expecting `NVIDIA/skills` to sync the signed skills. +```bash +rm -rf skills/nemoclaw-user- +cp -R .agents/skills/nemoclaw-user- skills/ +git add -A skills/nemoclaw-user- +git commit -m "chore(skills): refresh nemoclaw-user-" +``` -## Workflow steps added in this PR +The `skill.oms.sig` from the previous signing is removed by the `rm -rf`, +so NVSkills CI will re-sign on the next `/nvskills-ci` comment. Use +`git add -A` so newly added files in the refreshed skill are staged +alongside removals tracked by `git commit -a`. -These checks and workflow steps automate export freshness while keeping signing under maintainer control. +## Spot-checking for drift -- `CI / Pull Request` runs `python3 scripts/export-catalog-skills.py --check --allow-missing` so this infrastructure PR can merge before the first generated export, while later export PRs still reject stale or hand-edited files. -- `Skills / Catalog Refresh` supports: - - `dry_run=true` to regenerate and report changes without pushing. - - `dry_run=false` to create or update `automation/catalog-skills-refresh`. - - `request_nvskills_ci=true` to attempt the `/nvskills-ci` comment after opening/updating the PR. - - scheduled no-op/refresh behavior using the same exporter. +Source (`/.agents/skills/`) and published (`/skills/`) can drift if a +source-side edit lands without a corresponding refresh PR. To check, ask +an agent to compare every subdirectory of `skills/` against its counterpart +under `.agents/skills/` and report any file content differences (ignoring +`skill.oms.sig` and `skill-card.md`). -## Next Steps +## What goes in the catalog -- Review the exporter implementation in [`scripts/export-catalog-skills.py`](../scripts/export-catalog-skills.py). -- Update the catalog allowlist in [`.agents/catalog-skills.yaml`](../.agents/catalog-skills.yaml) when public skill scope changes. -- Review generated export diffs under `skills/` in the refresh PR before requesting or accepting signing artifacts. -- Check the workflow definitions in [`.github/workflows/pr.yaml`](workflows/pr.yaml) and [`.github/workflows/catalog-skills-refresh.yaml`](workflows/catalog-skills-refresh.yaml). +Only customer-facing skills, identified by the `nemoclaw-user-*` naming +convention. Internal skills (`nemoclaw-maintainer-*`, `nemoclaw-contributor-*`) +must not be copied into `skills/`. diff --git a/.github/pr-bodies/catalog-skills-refresh.md b/.github/pr-bodies/catalog-skills-refresh.md deleted file mode 100644 index c7bdef9185..0000000000 --- a/.github/pr-bodies/catalog-skills-refresh.md +++ /dev/null @@ -1,15 +0,0 @@ - - - -# Catalog Skills Refresh - -## Summary - -- Regenerates `skills/` from `.agents/catalog-skills.yaml` and `.agents/skills/`. -- Keeps the NVIDIA Verified Skills catalog export deterministic and reviewable. - -## Validation - -- `python3 scripts/export-catalog-skills.py --check` - -After maintainer review, request signing by commenting `/nvskills-ci` on this PR if the workflow did not do so automatically. diff --git a/.github/workflows/catalog-skills-refresh.yaml b/.github/workflows/catalog-skills-refresh.yaml deleted file mode 100644 index 57bd6d2634..0000000000 --- a/.github/workflows/catalog-skills-refresh.yaml +++ /dev/null @@ -1,142 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -name: Skills / Catalog Refresh - -on: - workflow_dispatch: - inputs: - dry_run: - description: "Regenerate and report changes without pushing or opening a PR" - type: boolean - required: true - default: true - request_nvskills_ci: - description: "Comment /nvskills-ci on the refresh PR after opening/updating it" - type: boolean - required: true - default: false - schedule: - - cron: "17 10 * * *" - -permissions: - contents: write - pull-requests: write - issues: write - -concurrency: - group: catalog-skills-refresh - cancel-in-progress: false - -jobs: - refresh: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - # Keep the write-capable token out of git config while repository - # code (exporter, etc.) runs. Push credentials are configured - # narrowly inside the create/update step via `gh auth setup-git`. - persist-credentials: false - - - name: Configure git author - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Preserve signer artifacts from existing refresh branch - env: - BRANCH: automation/catalog-skills-refresh - run: | - set -euo pipefail - # If NVSkills CI has already pushed signing artifacts (skill.oms.sig, - # skill-card.md) to the refresh branch, overlay that branch's - # skills/ into the working tree before regenerating. The - # exporter (scripts/export-catalog-skills.py) auto-preserves those - # files when an existing export tree is present, so this overlay is - # what keeps signer artifacts from being dropped on force-push. - if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then - echo "Existing $BRANCH found; overlaying skills/ to preserve NVSkills artifacts" - git fetch --depth 1 origin "$BRANCH":"refs/remotes/origin/$BRANCH" - # Use `git cat-file -e` instead of `git ls-tree -d`: the latter can - # exit 0 with empty output when the tree exists but the path does - # not, which would otherwise wipe skills/ and then fail at - # checkout. cat-file -e fails when the object is absent. - if git cat-file -e "origin/$BRANCH:skills" 2>/dev/null; then - rm -rf skills - git checkout "origin/$BRANCH" -- skills - fi - else - echo "No existing $BRANCH; running fresh export from main" - fi - - - name: Regenerate catalog skills export - run: python3 scripts/export-catalog-skills.py - - - name: Check for changes - id: diff - run: | - # Mark any new files under the export paths as intent-to-add so - # `git diff` reports them. Without this, an empty `skills/` - # tree on main causes a freshly-exported catalog to look unchanged - # and the workflow short-circuits without opening a PR. - git add --intent-to-add -- .agents/catalog-skills.yaml skills - if git diff --quiet -- .agents/catalog-skills.yaml skills; then - echo "changed=false" >> "$GITHUB_OUTPUT" - echo "No catalog skill export changes detected." - else - echo "changed=true" >> "$GITHUB_OUTPUT" - git diff --stat -- .agents/catalog-skills.yaml skills - fi - - - name: Stop after dry run - if: ${{ (github.event_name == 'workflow_dispatch' && inputs.dry_run) || (github.event_name == 'schedule' && steps.diff.outputs.changed != 'true') }} - run: | - if [[ "${{ steps.diff.outputs.changed }}" == "true" ]]; then - echo "Dry run detected catalog skill export changes; no branch or PR was created." - else - echo "Catalog skill export is already current." - fi - - - name: Create or update refresh pull request - id: cpr - if: ${{ steps.diff.outputs.changed == 'true' && (github.event_name == 'schedule' || !inputs.dry_run) }} - env: - GH_TOKEN: ${{ github.token }} - BRANCH: automation/catalog-skills-refresh - run: | - set -euo pipefail - # Install the GITHUB_TOKEN as a git credential helper at push time - # only. Combined with persist-credentials: false on checkout, this - # keeps the write-capable token out of git config while exporter - # code runs. - gh auth setup-git - - git checkout -B "$BRANCH" - git add .agents/catalog-skills.yaml skills - git commit -m "chore(skills): refresh catalog export" - git push --force-with-lease origin "$BRANCH" - - PR_NUMBER="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty')" - if [[ -z "$PR_NUMBER" ]]; then - PR_URL="$(gh pr create \ - --head "$BRANCH" \ - --base main \ - --title "chore(skills): refresh catalog export" \ - --body-file .github/pr-bodies/catalog-skills-refresh.md \ - --label documentation \ - --label CI/CD)" - PR_NUMBER="${PR_URL##*/}" - fi - echo "pull-request-number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - - - name: Request NVSkills signing - if: ${{ steps.cpr.outputs.pull-request-number != '' && github.event_name == 'workflow_dispatch' && inputs.request_nvskills_ci && !inputs.dry_run }} - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} - run: | - gh pr comment "$PR_NUMBER" --body "/nvskills-ci" diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 9f169813a3..5b8c8e9184 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -51,9 +51,6 @@ jobs: - name: Verify platform matrix is in sync run: python3 scripts/generate-platform-docs.py --check - - name: Verify catalog skills export is in sync - run: python3 scripts/export-catalog-skills.py --check --allow-missing - test-e2e-ollama-proxy: needs: [checks, changes] if: needs.changes.outputs.code == 'true' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 57a1d9ed93..583f66a2a3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -160,14 +160,6 @@ repos: pass_filenames: false priority: 10 - - id: catalog-skills-export - name: Verify catalog skills export - entry: python3 scripts/export-catalog-skills.py --check --allow-missing - language: system - files: ^(\.agents/catalog-skills\.yaml|\.agents/skills/.*|skills/.*|scripts/export-catalog-skills\.py)$ - pass_filenames: false - priority: 10 - - id: env-var-docs name: NEMOCLAW_* env-var documentation gate entry: npx tsx scripts/check-env-var-docs.ts diff --git a/scripts/export-catalog-skills.py b/scripts/export-catalog-skills.py deleted file mode 100755 index cde3dcc9be..0000000000 --- a/scripts/export-catalog-skills.py +++ /dev/null @@ -1,460 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Export catalog-safe NemoClaw skills to the NVSkills watched directory. - -The repository source of truth remains `.agents/skills/`. This script copies the -checked-in allowlist from `.agents/catalog-skills.yaml` into `skills/` -using deterministic ordering and metadata so CI can detect stale or hand-edited -catalog exports. -""" - -from __future__ import annotations - -import argparse -import filecmp -import fnmatch -import hashlib -import json -import os -import shutil -import subprocess -import sys -import tempfile -from dataclasses import dataclass -from pathlib import Path -from typing import Any - -REPO_ROOT = Path(__file__).resolve().parents[1] -DEFAULT_ALLOWLIST = Path(".agents/catalog-skills.yaml") -GENERATED_HEADER = """ - - -# Generated NemoClaw Catalog Skills - -This directory is generated from `.agents/catalog-skills.yaml` and `.agents/skills/`. -Do not edit files here directly. The exporter preserves NVSkills signing artifacts (`skill.oms.sig` and `skill-card.md`) when regenerating an already-signed export. - -To update this export, edit the source skills or allowlist, then run: - -```bash -python3 scripts/export-catalog-skills.py -``` - -CI verifies the directory with: - -```bash -python3 scripts/export-catalog-skills.py --check -``` -""" - -PRESERVED_SIGNING_FILES = {"skill.oms.sig", "skill-card.md"} -MARKDOWN_EXTENSIONS = {".md", ".mdx"} - - -def strip_markdown_spdx_preamble(text: str) -> str: - """Remove generated SPDX comments from catalog Markdown exports.""" - lines = text.splitlines(keepends=True) - idx = 0 - prefix: list[str] = [] - if lines and lines[0].strip() == "---": - idx = 1 - while idx < len(lines): - idx += 1 - if lines[idx - 1].strip() == "---": - break - prefix = lines[:idx] - if idx < len(lines) and not lines[idx].strip(): - idx += 1 - while idx < len(lines) and lines[idx].startswith("