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("