From 19151a8e7c9899effbb8fab4f3d947c11852427e Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Thu, 28 May 2026 13:09:35 -0400 Subject: [PATCH 1/2] chore: simplify skills publishing pipeline Removes the catalog-skills allowlist, exporter script, drift gate, and nightly refresh automation. Publishing a user-facing skill is now: copy .agents/skills/nemoclaw-user-/ into skills/, commit, comment /nvskills-ci, merge. NVSkills CI signs one skill per PR (the existing timeout constraint), and skills/ itself is the source of truth for what is published. Drift between source (.agents/skills/) and published (skills/) is now verified on demand by asking an agent to diff the two trees, rather than enforced on every commit. The previous gate forced allowlist and export to stay in lockstep, which conflicts with NVSkills CI's per-skill signing cadence and required staged migration PRs to bypass the hook. Removed: - .agents/catalog-skills.yaml (allowlist) - scripts/export-catalog-skills.py (exporter + checker) - test/catalog-skills-export.test.ts (test for the exporter) - .github/workflows/catalog-skills-refresh.yaml (nightly refresh bot) - .github/pr-bodies/catalog-skills-refresh.md (bot PR body template) - .pre-commit-config.yaml catalog-skills-export hook - .github/workflows/pr.yaml "Verify catalog skills export" step - .github/CODEOWNERS entry for the deleted allowlist Replaced: - .github/catalog-skills-signing-flow.md rewritten as a 40-line manual workflow doc (cp -r, /nvskills-ci, merge). This unblocks the per-skill migration PRs (#4438 and the 9 to follow) that are currently blocked by the gate's allowlist-vs-export equality check. Signed-off-by: Julie Yaunches --- .agents/catalog-skills.yaml | 40 -- .github/CODEOWNERS | 1 - .github/catalog-skills-signing-flow.md | 96 ++-- .github/pr-bodies/catalog-skills-refresh.md | 15 - .github/workflows/catalog-skills-refresh.yaml | 142 ------ .github/workflows/pr.yaml | 3 - .pre-commit-config.yaml | 8 - scripts/export-catalog-skills.py | 460 ------------------ test/catalog-skills-export.test.ts | 210 -------- 9 files changed, 32 insertions(+), 943 deletions(-) delete mode 100644 .agents/catalog-skills.yaml delete mode 100644 .github/pr-bodies/catalog-skills-refresh.md delete mode 100644 .github/workflows/catalog-skills-refresh.yaml delete mode 100755 scripts/export-catalog-skills.py delete mode 100644 test/catalog-skills-export.test.ts 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..ada814599b 100644 --- a/.github/catalog-skills-signing-flow.md +++ b/.github/catalog-skills-signing-flow.md @@ -1,79 +1,47 @@ -# 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 +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 commit -am "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. -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("