diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index a6eed8be..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,164 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 -# Copyright (c) 2026 NVIDIA Corporation. All rights reserved. - -name: Agent Skills Sync Check - -on: - schedule: - - cron: "0 8 * * *" # every day at 08:00 UTC - workflow_dispatch: - pull_request: - branches: [main] - -jobs: - check-skills: - name: Verify skill counts match source repos - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - drifted: ${{ steps.sync.outputs.drifted }} - steps: - - uses: actions/checkout@v4 - - - name: Check skill directories in source repos - id: sync - env: - GH_TOKEN: ${{ secrets.PRIVATE_REPO_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - - DRIFTED=0 - - # Format: "display_name|owner/repo|ref|skills_path[+extra_path]|expected_count" - REPOS=( - "CUDA-Q|NVIDIA/cuda-quantum|main|.claude/skills|1" - "cuOpt|NVIDIA/cuopt|main|skills|19" - "TensorRT-LLM|NVIDIA/TensorRT-LLM|main|.claude/skills|20" - "Model-Optimizer|NVIDIA/Model-Optimizer|main|.claude/skills|5" - "Megatron-Core|NVIDIA/Megatron-LM|main|.claude/skills|3" - "Megatron-Bridge|NVIDIA-NeMo/Megatron-Bridge|main|skills|9" - "Nemotron Voice Agent|NVIDIA-AI-Blueprints/nemotron-voice-agent|main|.agents/skills|1" - "NeMo Gym|NVIDIA-NeMo/Gym|main|.claude/skills|1" - "NeMo Evaluator|NVIDIA-NeMo/Evaluator|main|packages/nemo-evaluator-launcher/.claude/skills+packages/nemo-evaluator/.claude/skills|4" - ) - - REPORT="## Skill Catalog Sync Report\n\n" - REPORT+="| Product | Expected | Actual | Status |\n" - REPORT+="|---------|----------|--------|--------|\n" - - declare -A UPDATES - - count_skills_in_path() { - local repo="$1" ref="$2" path="$3" - local result - result=$(gh api "repos/${repo}/contents/${path}?ref=${ref}" \ - --jq '[.[] | select(.type == "dir")] | length' 2>&1) - if [ $? -ne 0 ]; then - echo "ERR:${result}" - else - echo "$result" - fi - } - - for entry in "${REPOS[@]}"; do - IFS='|' read -r label owner_repo ref skills_paths expected <<< "$entry" - - # Sum skill counts across all paths (split on +) - actual=0 - error_msg="" - IFS='+' read -ra PATHS <<< "$skills_paths" - for spath in "${PATHS[@]}"; do - count=$(count_skills_in_path "$owner_repo" "$ref" "$spath") - if [[ "$count" == ERR:* ]]; then - error_msg="${count#ERR:}" - break - fi - actual=$((actual + count)) - done - - if [ -n "$error_msg" ]; then - REPORT+="| $label | $expected | ERROR | :x: ${error_msg} |\n" - echo "::error::${label}: ${error_msg}" - DRIFTED=1 - elif [ "$actual" -eq "$expected" ]; then - REPORT+="| $label | $expected | $actual | :white_check_mark: |\n" - else - REPORT+="| $label | $expected | $actual | :warning: DRIFT |\n" - UPDATES["$label"]="$expected:$actual" - DRIFTED=1 - fi - done - - echo -e "$REPORT" - echo -e "$REPORT" > /tmp/sync-report.md - echo "drifted=$DRIFTED" >> "$GITHUB_OUTPUT" - - if [ "$DRIFTED" -ne 0 ]; then - echo "::error::Skill counts have drifted — update README.md to match source repos." - - # Persist updates for the fix job - for label in "${!UPDATES[@]}"; do - echo "${label}=${UPDATES[$label]}" - done > /tmp/updates.txt - fi - - - name: Upload sync artifacts - if: steps.sync.outputs.drifted == '1' - uses: actions/upload-artifact@v4 - with: - name: sync-data - path: | - /tmp/sync-report.md - /tmp/updates.txt - - - name: Fail check on drift - if: steps.sync.outputs.drifted == '1' - run: exit 1 - - fix-drift: - name: Create PR with corrected skill counts - needs: check-skills - if: needs.check-skills.outputs.drifted == '1' && github.event_name != 'pull_request' - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - uses: actions/checkout@v4 - - - name: Download sync artifacts - uses: actions/download-artifact@v4 - with: - name: sync-data - path: /tmp - - - name: Update README.md with corrected counts - run: | - set -euo pipefail - - while IFS='=' read -r label counts; do - IFS=':' read -r old_count new_count <<< "$counts" - # Escape spaces in label for sed; match bold product name in table row - escaped_label=$(echo "$label" | sed 's/ /[[:space:]]/g') - sed -i -E "s/(\*\*${escaped_label}\*\*[^|]*\|[^|]*\| *)${old_count}( *\|)/\1${new_count}\2/" README.md - done < /tmp/updates.txt - - if git diff --quiet README.md; then - echo "::error::No changes to README.md after update — manual fix may be needed." - exit 1 - fi - - echo "README.md updated:" - git diff README.md - - - name: Create pull request - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "fix: update skill counts to match source repos" - branch: auto/sync-skill-counts - delete-branch: true - title: "fix: sync skill counts with source repos" - body-path: /tmp/sync-report.md - labels: automated diff --git a/.github/workflows/sync-skills.yml b/.github/workflows/sync-skills.yml index bffd18ed..6a22e8bf 100644 --- a/.github/workflows/sync-skills.yml +++ b/.github/workflows/sync-skills.yml @@ -2,13 +2,11 @@ # Copyright (c) 2026 NVIDIA Corporation. All rights reserved. # # Sync skills from product repos into the nvidia/skills catalog. -# Runs on a schedule (twice daily) and can be triggered manually. -# Each product block sparse-checkouts only the skills/ directory, -# then copies those skills into the catalog tree. +# Reads product definitions from products.yml — teams onboard by +# adding an entry there, not by editing this workflow. # # Requires a PAT (SKILLS_SYNC_PAT) with read access to all product -# repos, stored as a repository secret. The default GITHUB_TOKEN -# only has access to this repo and will 403 on private product repos. +# repos, stored as a repository secret. name: Sync Skills from Product Repos @@ -36,224 +34,69 @@ jobs: with: ref: main - - name: Initialize sync log - run: truncate -s 0 /tmp/synced-products.txt + - name: Verify yq is available + run: yq --version - # ── cuOpt ────────────────────────────────────────────── - - name: Checkout cuOpt - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA/cuopt - ref: main - path: .tmp/cuopt - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - skills/ - - - name: Copy cuOpt skills into catalog - run: | - if [ -d ".tmp/cuopt/skills" ] && [ -n "$(ls -A .tmp/cuopt/skills)" ]; then - mkdir -p skills/cuopt - rsync -a --delete .tmp/cuopt/skills/ skills/cuopt/ - echo "- cuOpt" >> /tmp/synced-products.txt - else - echo "⚠ cuopt checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── TensorRT-LLM ────────────────────────────────────── - - name: Checkout TensorRT-LLM - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA/TensorRT-LLM - ref: main - path: .tmp/TensorRT-LLM - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - .claude/skills/ - - - name: Copy TensorRT-LLM skills into catalog - run: | - if [ -d ".tmp/TensorRT-LLM/.claude/skills" ] && [ -n "$(ls -A .tmp/TensorRT-LLM/.claude/skills)" ]; then - mkdir -p skills/TensorRT-LLM - rsync -a --delete .tmp/TensorRT-LLM/.claude/skills/ skills/TensorRT-LLM/ - echo "- TensorRT-LLM" >> /tmp/synced-products.txt - else - echo "⚠ TensorRT-LLM checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── Model-Optimizer ──────────────────────────────────── - - name: Checkout Model-Optimizer - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA/Model-Optimizer - ref: main - path: .tmp/Model-Optimizer - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - .claude/skills/ - - - name: Copy Model-Optimizer skills into catalog - run: | - if [ -d ".tmp/Model-Optimizer/.claude/skills" ] && [ -n "$(ls -A .tmp/Model-Optimizer/.claude/skills)" ]; then - mkdir -p skills/Model-Optimizer - rsync -a --delete .tmp/Model-Optimizer/.claude/skills/ skills/Model-Optimizer/ - echo "- Model-Optimizer" >> /tmp/synced-products.txt - else - echo "⚠ Model-Optimizer checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── CUDA-Q ─────────────────────────────────────────────── - - name: Checkout CUDA-Q - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA/cuda-quantum - ref: main - path: .tmp/cuda-quantum - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - .claude/skills/ - - - name: Copy CUDA-Q skills into catalog - run: | - if [ -d ".tmp/cuda-quantum/.claude/skills" ] && [ -n "$(ls -A .tmp/cuda-quantum/.claude/skills)" ]; then - mkdir -p skills/CUDA-Q - rsync -a --delete .tmp/cuda-quantum/.claude/skills/ skills/CUDA-Q/ - echo "- CUDA-Q" >> /tmp/synced-products.txt - else - echo "⚠ CUDA-Q checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── Megatron-Core ────────────────────────────────────── - - name: Checkout Megatron-LM - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA/Megatron-LM - ref: main - path: .tmp/Megatron-LM - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - .claude/skills/ - - - name: Copy Megatron-Core skills into catalog - run: | - if [ -d ".tmp/Megatron-LM/.claude/skills" ] && [ -n "$(ls -A .tmp/Megatron-LM/.claude/skills)" ]; then - mkdir -p skills/Megatron-Core - rsync -a --delete .tmp/Megatron-LM/.claude/skills/ skills/Megatron-Core/ - echo "- Megatron-Core" >> /tmp/synced-products.txt - else - echo "⚠ Megatron-Core checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── Megatron-Bridge ──────────────────────────────────── - - name: Checkout Megatron-Bridge - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA-NeMo/Megatron-Bridge - ref: main - path: .tmp/Megatron-Bridge - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - skills/ - - - name: Copy Megatron-Bridge skills into catalog - run: | - if [ -d ".tmp/Megatron-Bridge/skills" ] && [ -n "$(ls -A .tmp/Megatron-Bridge/skills)" ]; then - mkdir -p skills/Megatron-Bridge - rsync -a --delete .tmp/Megatron-Bridge/skills/ skills/Megatron-Bridge/ - echo "- Megatron-Bridge" >> /tmp/synced-products.txt - else - echo "⚠ Megatron-Bridge checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── Nemotron Voice Agent ─────────────────────────────── - - name: Checkout nemotron-voice-agent - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA-AI-Blueprints/nemotron-voice-agent - ref: main - path: .tmp/nemotron-voice-agent - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - .agents/skills/ - - - name: Copy nemotron-voice-agent skills into catalog - run: | - if [ -d ".tmp/nemotron-voice-agent/.agents/skills" ] && [ -n "$(ls -A .tmp/nemotron-voice-agent/.agents/skills)" ]; then - mkdir -p skills/nemotron-voice-agent - rsync -a --delete .tmp/nemotron-voice-agent/.agents/skills/ skills/nemotron-voice-agent/ - echo "- Nemotron Voice Agent" >> /tmp/synced-products.txt - else - echo "⚠ nemotron-voice-agent checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── NeMo Gym ─────────────────────────────────────────── - - name: Checkout NeMo Gym - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA-NeMo/Gym - ref: main - path: .tmp/NeMo-Gym - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - .claude/skills/ - - - name: Copy NeMo Gym skills into catalog + - name: Sync skills from product repos + env: + GH_TOKEN: ${{ secrets.SKILLS_SYNC_PAT }} run: | - if [ -d ".tmp/NeMo-Gym/.claude/skills" ] && [ -n "$(ls -A .tmp/NeMo-Gym/.claude/skills)" ]; then - mkdir -p skills/NeMo-Gym - rsync -a --delete .tmp/NeMo-Gym/.claude/skills/ skills/NeMo-Gym/ - echo "- NeMo Gym" >> /tmp/synced-products.txt - else - echo "⚠ NeMo-Gym checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── NeMo Evaluator Launcher ───────────────────────────── - - name: Checkout NeMo Evaluator - continue-on-error: true - uses: actions/checkout@v4 - with: - repository: NVIDIA-NeMo/Evaluator - ref: main - path: .tmp/NeMo-Evaluator - token: ${{ secrets.SKILLS_SYNC_PAT }} - sparse-checkout: | - packages/nemo-evaluator-launcher/.claude/skills/ - packages/nemo-evaluator/.claude/skills/ - - - name: Copy NeMo Evaluator Launcher skills into catalog - run: | - src=".tmp/NeMo-Evaluator/packages/nemo-evaluator-launcher/.claude/skills" - if [ -d "$src" ] && [ -n "$(ls -A "$src")" ]; then - mkdir -p skills/NeMo-Evaluator-Launcher - rsync -a --delete "$src/" skills/NeMo-Evaluator-Launcher/ - echo "- NeMo Evaluator Launcher" >> /tmp/synced-products.txt - else - echo "⚠ NeMo-Evaluator-Launcher checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── NeMo Evaluator ──────────────────────────────────── - - name: Copy NeMo Evaluator skills into catalog - run: | - src=".tmp/NeMo-Evaluator/packages/nemo-evaluator/.claude/skills" - if [ -d "$src" ] && [ -n "$(ls -A "$src")" ]; then - mkdir -p skills/NeMo-Evaluator - rsync -a --delete "$src/" skills/NeMo-Evaluator/ - echo "- NeMo Evaluator" >> /tmp/synced-products.txt - else - echo "⚠ NeMo-Evaluator checkout empty or missing — skipping to preserve existing catalog" - fi - - # ── Cleanup and commit ───────────────────────────────── - - name: Clean up temp checkouts - run: rm -rf .tmp + set -euo pipefail + truncate -s 0 /tmp/synced-products.txt + + # Configure git to use the token via credential helper + # so it never appears in clone URLs or error output + git config --global credential.helper '!f() { echo "username=x-access-token"; echo "password=${GH_TOKEN}"; }; f' + + product_count=$(yq '.products | length' products.yml) + + for i in $(seq 0 $((product_count - 1))); do + name=$(yq -r ".products[$i].name" products.yml) + repo=$(yq -r ".products[$i].repo" products.yml) + ref=$(yq -r ".products[$i].ref // \"main\"" products.yml) + + echo "── $name ──" + + # Clone with sparse checkout + tmp_dir=".tmp/$(echo "$repo" | tr '/' '-')" + if ! git clone --depth 1 --filter=blob:none --sparse \ + "https://github.com/${repo}.git" \ + "$tmp_dir" -b "$ref" 2>/dev/null; then + echo " ⚠ Clone failed for $repo — skipping" + continue + fi + + # Collect all skill paths for sparse checkout + skill_count=$(yq -r ".products[$i].skills | length" products.yml) + sparse_paths="" + for j in $(seq 0 $((skill_count - 1))); do + path=$(yq -r ".products[$i].skills[$j].path" products.yml) + sparse_paths="$sparse_paths $path" + done + (cd "$tmp_dir" && git sparse-checkout set $sparse_paths 2>/dev/null) || true + + # Copy each skill path to its catalog directory + synced=false + for j in $(seq 0 $((skill_count - 1))); do + path=$(yq -r ".products[$i].skills[$j].path" products.yml) + catalog_dir=$(yq -r ".products[$i].skills[$j].catalog_dir" products.yml) + src="$tmp_dir/$path" + + if [ -d "$src" ] && [ -n "$(ls -A "$src" 2>/dev/null)" ]; then + mkdir -p "skills/$catalog_dir" + rsync -a --delete "$src/" "skills/$catalog_dir/" + synced=true + echo " ✓ $path → skills/$catalog_dir/" + else + echo " ⚠ $path empty or missing — skipping to preserve existing catalog" + fi + done + + $synced && echo "- $name" >> /tmp/synced-products.txt + echo "" + done + + rm -rf .tmp - name: Fail if nothing synced run: | @@ -265,23 +108,18 @@ jobs: - name: Build sync summary id: summary run: | - if [ -s /tmp/synced-products.txt ]; then - products=$(cat /tmp/synced-products.txt | tr '\n' ',' | sed 's/- //g;s/,$//') - echo "title=chore: sync skills ($products)" >> "$GITHUB_OUTPUT" - { - echo 'body<> "$GITHUB_OUTPUT" - else - echo "title=chore: sync skills from product repos" >> "$GITHUB_OUTPUT" - echo "body=Automated sync — no products reported changes." >> "$GITHUB_OUTPUT" - fi + products=$(cat /tmp/synced-products.txt | tr '\n' ',' | sed 's/- //g;s/,$//') + echo "title=chore: sync skills ($products)" >> "$GITHUB_OUTPUT" + { + echo 'body<> "$GITHUB_OUTPUT" - name: Create pull request uses: peter-evans/create-pull-request@v6 @@ -294,6 +132,7 @@ jobs: - name: Open issue on failure if: failure() + continue-on-error: true uses: actions/github-script@v7 with: script: | diff --git a/products.yml b/products.yml new file mode 100644 index 00000000..bc4ef4d7 --- /dev/null +++ b/products.yml @@ -0,0 +1,118 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2026 NVIDIA Corporation. All rights reserved. +# +# Product catalog — single source of truth for NVIDIA Agent Skills. +# +# To onboard a new product: +# 1. Add an entry to this file following the pattern below +# 2. Open a pull request — that's it! +# +# The sync and CI workflows read this file automatically. +# +# Required fields: +# name Display name shown in the README +# repo GitHub repository (owner/repo) +# description One-line description for the README +# skills List of skill locations in the source repo +# path Directory containing skills in the source repo +# catalog_dir Directory name under skills/ in this catalog +# +# Optional fields: +# ref Branch to sync from (default: main) +# links Override default link behavior (see examples below) +# contributing Path to CONTRIBUTING file (default: CONTRIBUTING.md) +# discussions Set to false if repo has no Discussions tab +# security Set to false if repo has no SECURITY.md + +products: + - name: CUDA-Q + repo: NVIDIA/cuda-quantum + description: >- + CUDA Quantum — onboarding guide for installation, test programs, + GPU simulation, QPU hardware, and quantum applications. + skills: + - path: .claude/skills/ + catalog_dir: CUDA-Q + links: + contributing: Contributing.md + + - name: cuOpt + repo: NVIDIA/cuopt + description: >- + GPU-accelerated optimization — vehicle routing, linear programming, + quadratic programming, installation, server deployment, and developer tools. + skills: + - path: skills/ + catalog_dir: cuopt + + - name: TensorRT-LLM + repo: NVIDIA/TensorRT-LLM + description: >- + LLM inference optimization — model onboarding, performance analysis + and optimization, kernel writing, CI diagnostics, code contribution, + and codebase exploration. + skills: + - path: .claude/skills/ + catalog_dir: TensorRT-LLM + + - name: Model-Optimizer + repo: NVIDIA/Model-Optimizer + description: >- + Model optimization — quantization, sparsity, and distillation + for efficient inference. + skills: + - path: .claude/skills/ + catalog_dir: Model-Optimizer + links: + discussions: false + + - name: Megatron-Core + repo: NVIDIA/Megatron-LM + description: >- + Large-scale distributed training — model parallelism, pipeline + parallelism, and mixed precision. + skills: + - path: .claude/skills/ + catalog_dir: Megatron-Core + links: + security: false + + - name: Megatron-Bridge + repo: NVIDIA-NeMo/Megatron-Bridge + description: >- + Bridge between NeMo and Megatron — data processing, model conversion, + and training utilities. + skills: + - path: skills/ + catalog_dir: Megatron-Bridge + links: + security: false + + - name: Nemotron Voice Agent + repo: NVIDIA-AI-Blueprints/nemotron-voice-agent + description: >- + Real-time conversational AI — deploy speech-to-speech voice agents + on Workstation, Jetson Thor, or Cloud NIMs. + skills: + - path: .agents/skills/ + catalog_dir: nemotron-voice-agent + + - name: NeMo Gym + repo: NVIDIA-NeMo/Gym + description: >- + RL training environments — add benchmarks, resources servers, + agent wiring, and reward profiling. + skills: + - path: .claude/skills/ + catalog_dir: NeMo-Gym + + - name: NeMo Evaluator + repo: NVIDIA-NeMo/Evaluator + description: >- + LLM evaluation — launch evaluations, access MLflow results, + NeMo Evaluator Launcher assistant, and bring-your-own benchmarks. + skills: + - path: packages/nemo-evaluator-launcher/.claude/skills/ + catalog_dir: NeMo-Evaluator-Launcher + - path: packages/nemo-evaluator/.claude/skills/ + catalog_dir: NeMo-Evaluator