From 0c0367c38fe6a974b78940dd54178bc351573ced Mon Sep 17 00:00:00 2001 From: Seonghee Lee Date: Mon, 6 Apr 2026 12:04:52 -0700 Subject: [PATCH 1/4] [ci pipeline] add ci pipeline to verify skill counts match source repos Signed-off-by: Seonghee Lee --- .github/workflows/ci.yml | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..f92ac17c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 +# Copyright (c) 2026 NVIDIA Corporation. All rights reserved. + +name: Skill Catalog Sync Check + +on: + schedule: + - cron: "0 8 * * 1" # every Monday at 08:00 UTC + workflow_dispatch: # allow manual trigger + pull_request: + branches: [main] + +permissions: + contents: read + issues: write + +jobs: + check-skills: + name: Verify skill counts match source repos + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check skill directories in source repos + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + STATUS=0 + + # Each entry: "owner/repo ref skills_path expected_count label" + REPOS=( + "NVIDIA/cuopt main skills 19 cuOpt" + "NVIDIA/TensorRT-LLM main .claude/skills 4 TensorRT-LLM" + "NVIDIA/Model-Optimizer main .claude/skills 4 Model-Optimizer" + "NVIDIA/Megatron-LM main .claude/skills 2 Megatron-Core" + "NVIDIA-NeMo/Megatron-Bridge main skills 8 Megatron-Bridge" + "NVIDIA-AI-Blueprints/nemotron-voice-agent main .agents/skills 1 Nemotron-Voice-Agent" + "NVIDIA-NeMo/Gym main .claude/skills 1 NeMo-Gym" + "NVIDIA-NeMo/Evaluator main packages/nemo-evaluator-launcher/.claude/skills 4 NeMo-Evaluator" + ) + + echo "## Skill Catalog Sync Report" + echo "" + printf "| %-24s | %-10s | %-10s | %-6s |\n" "Product" "Expected" "Actual" "Status" + printf "| %-24s | %-10s | %-10s | %-6s |\n" "------------------------" "----------" "----------" "------" + + for entry in "${REPOS[@]}"; do + read -r owner_repo ref skills_path expected label <<< "$entry" + + actual=$(gh api \ + "repos/${owner_repo}/contents/${skills_path}?ref=${ref}" \ + --jq '[.[] | select(.type == "dir")] | length' 2>/dev/null || echo "ERR") + + if [ "$actual" = "ERR" ]; then + status_icon="ERROR" + STATUS=1 + elif [ "$actual" -eq "$expected" ]; then + status_icon="OK" + else + status_icon="DRIFT" + STATUS=1 + fi + + printf "| %-24s | %-10s | %-10s | %-6s |\n" "$label" "$expected" "$actual" "$status_icon" + done + + echo "" + if [ "$STATUS" -ne 0 ]; then + echo "::error::Skill counts have drifted — update README.md to match source repos." + exit 1 + else + echo "All skill counts match." + fi From b178c41196ee25cc8c3440f96044445dc47dda77 Mon Sep 17 00:00:00 2001 From: Seonghee Lee Date: Mon, 6 Apr 2026 12:10:02 -0700 Subject: [PATCH 2/4] Fix to daily schedule Signed-off-by: Seonghee Lee --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f92ac17c..e30939c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ name: Skill Catalog Sync Check on: schedule: - - cron: "0 8 * * 1" # every Monday at 08:00 UTC + - cron: "0 8 * * *" # every day at 08:00 UTC workflow_dispatch: # allow manual trigger pull_request: branches: [main] From fa2659782bf9c9fd577e5eaf6995d352e7951952 Mon Sep 17 00:00:00 2001 From: Seonghee Lee Date: Mon, 6 Apr 2026 12:12:27 -0700 Subject: [PATCH 3/4] Auto create PR when skill counts drift from source repos Signed-off-by: Seonghee Lee --- .github/workflows/ci.yml | 72 +++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e30939c9..850cd30b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,8 @@ on: branches: [main] permissions: - contents: read - issues: write + contents: write + pull-requests: write jobs: check-skills: @@ -21,13 +21,14 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Check skill directories in source repos + - name: Check skill directories and update README if drifted + id: sync env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail - STATUS=0 + DRIFTED=0 # Each entry: "owner/repo ref skills_path expected_count label" REPOS=( @@ -41,10 +42,11 @@ jobs: "NVIDIA-NeMo/Evaluator main packages/nemo-evaluator-launcher/.claude/skills 4 NeMo-Evaluator" ) - echo "## Skill Catalog Sync Report" - echo "" - printf "| %-24s | %-10s | %-10s | %-6s |\n" "Product" "Expected" "Actual" "Status" - printf "| %-24s | %-10s | %-10s | %-6s |\n" "------------------------" "----------" "----------" "------" + REPORT="## Skill Catalog Sync Report\n\n" + REPORT+="| Product | Expected | Actual | Status |\n" + REPORT+="|---------|----------|--------|--------|\n" + + declare -A UPDATES for entry in "${REPOS[@]}"; do read -r owner_repo ref skills_path expected label <<< "$entry" @@ -54,22 +56,54 @@ jobs: --jq '[.[] | select(.type == "dir")] | length' 2>/dev/null || echo "ERR") if [ "$actual" = "ERR" ]; then - status_icon="ERROR" - STATUS=1 + REPORT+="| $label | $expected | ERROR | :x: |\n" + DRIFTED=1 elif [ "$actual" -eq "$expected" ]; then - status_icon="OK" + REPORT+="| $label | $expected | $actual | :white_check_mark: |\n" else - status_icon="DRIFT" - STATUS=1 + REPORT+="| $label | $expected | $actual | :warning: DRIFT |\n" + UPDATES["$label"]="$expected:$actual" + DRIFTED=1 fi - - printf "| %-24s | %-10s | %-10s | %-6s |\n" "$label" "$expected" "$actual" "$status_icon" done - echo "" - if [ "$STATUS" -ne 0 ]; then + echo -e "$REPORT" + echo "drifted=$DRIFTED" >> "$GITHUB_OUTPUT" + + if [ "$DRIFTED" -eq 0 ]; then + echo "All skill counts match." + exit 0 + fi + + # On PRs, just fail — don't try to auto-fix + if [ "${{ github.event_name }}" = "pull_request" ]; then echo "::error::Skill counts have drifted — update README.md to match source repos." exit 1 - else - echo "All skill counts match." fi + + # On schedule/dispatch, update README.md with corrected counts + echo "Updating README.md with corrected skill counts..." + for label in "${!UPDATES[@]}"; do + IFS=':' read -r old_count new_count <<< "${UPDATES[$label]}" + # Match the skills count column in the Available Skills table row for this product + sed -i -E "s/(\*\*${label}\*\*[^|]*\|[^|]*\| *)${old_count}( *\|)/\1${new_count}\2/" README.md + done + + if git diff --quiet README.md; then + echo "No changes to README.md after sed — manual update may be needed." + exit 1 + fi + + echo -e "$REPORT" > /tmp/pr-body.md + + - name: Create pull request with updated counts + if: steps.sync.outputs.drifted == '1' && github.event_name != '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/pr-body.md + labels: automated From c6890340f987e8d0ebff6e1080228c4e18cecd30 Mon Sep 17 00:00:00 2001 From: Seonghee Lee Date: Tue, 7 Apr 2026 10:35:35 -0700 Subject: [PATCH 4/4] Update CI to fix issues, use hyphenated names Signed-off-by: Seonghee Lee --- .github/workflows/ci.yml | 144 +++++++++++++++++++++++++++------------ README.md | 12 ++-- 2 files changed, 105 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 850cd30b..cc7db8c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,45 +1,45 @@ # SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 # Copyright (c) 2026 NVIDIA Corporation. All rights reserved. -name: Skill Catalog Sync Check +name: Agent Skills Sync Check on: schedule: - cron: "0 8 * * *" # every day at 08:00 UTC - workflow_dispatch: # allow manual trigger + workflow_dispatch: pull_request: branches: [main] -permissions: - contents: write - pull-requests: write - 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 and update README if drifted + - name: Check skill directories in source repos id: sync env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.PRIVATE_REPO_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | set -euo pipefail DRIFTED=0 - # Each entry: "owner/repo ref skills_path expected_count label" + # Format: "display_name|owner/repo|ref|skills_path[+extra_path]|expected_count" REPOS=( - "NVIDIA/cuopt main skills 19 cuOpt" - "NVIDIA/TensorRT-LLM main .claude/skills 4 TensorRT-LLM" - "NVIDIA/Model-Optimizer main .claude/skills 4 Model-Optimizer" - "NVIDIA/Megatron-LM main .claude/skills 2 Megatron-Core" - "NVIDIA-NeMo/Megatron-Bridge main skills 8 Megatron-Bridge" - "NVIDIA-AI-Blueprints/nemotron-voice-agent main .agents/skills 1 Nemotron-Voice-Agent" - "NVIDIA-NeMo/Gym main .claude/skills 1 NeMo-Gym" - "NVIDIA-NeMo/Evaluator main packages/nemo-evaluator-launcher/.claude/skills 4 NeMo-Evaluator" + "cuOpt|NVIDIA/cuopt|main|skills|19" + "TensorRT-LLM|NVIDIA/TensorRT-LLM|main|.claude/skills|4" + "Model-Optimizer|NVIDIA/Model-Optimizer|main|.claude/skills|4" + "Megatron-Core|NVIDIA/Megatron-LM|main|.claude/skills|2" + "Megatron-Bridge|NVIDIA-NeMo/Megatron-Bridge|main|skills|8" + "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" @@ -48,15 +48,37 @@ jobs: declare -A UPDATES - for entry in "${REPOS[@]}"; do - read -r owner_repo ref skills_path expected label <<< "$entry" - - actual=$(gh api \ - "repos/${owner_repo}/contents/${skills_path}?ref=${ref}" \ - --jq '[.[] | select(.type == "dir")] | length' 2>/dev/null || echo "ERR") + 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 + } - if [ "$actual" = "ERR" ]; then - REPORT+="| $label | $expected | ERROR | :x: |\n" + 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" @@ -68,36 +90,68 @@ jobs: done echo -e "$REPORT" + echo -e "$REPORT" > /tmp/sync-report.md echo "drifted=$DRIFTED" >> "$GITHUB_OUTPUT" - if [ "$DRIFTED" -eq 0 ]; then - echo "All skill counts match." - exit 0 - fi - - # On PRs, just fail — don't try to auto-fix - if [ "${{ github.event_name }}" = "pull_request" ]; then + if [ "$DRIFTED" -ne 0 ]; then echo "::error::Skill counts have drifted — update README.md to match source repos." - exit 1 + + # Persist updates for the fix job + for label in "${!UPDATES[@]}"; do + echo "${label}=${UPDATES[$label]}" + done > /tmp/updates.txt fi - # On schedule/dispatch, update README.md with corrected counts - echo "Updating README.md with corrected skill counts..." - for label in "${!UPDATES[@]}"; do - IFS=':' read -r old_count new_count <<< "${UPDATES[$label]}" - # Match the skills count column in the Available Skills table row for this product - sed -i -E "s/(\*\*${label}\*\*[^|]*\|[^|]*\| *)${old_count}( *\|)/\1${new_count}\2/" README.md - done + - 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 "No changes to README.md after sed — manual update may be needed." + echo "::error::No changes to README.md after update — manual fix may be needed." exit 1 fi - echo -e "$REPORT" > /tmp/pr-body.md + echo "README.md updated:" + git diff README.md - - name: Create pull request with updated counts - if: steps.sync.outputs.drifted == '1' && github.event_name != 'pull_request' + - name: Create pull request uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -105,5 +159,5 @@ jobs: branch: auto/sync-skill-counts delete-branch: true title: "fix: sync skill counts with source repos" - body-path: /tmp/pr-body.md + body-path: /tmp/sync-report.md labels: automated diff --git a/README.md b/README.md index c51ae19b..9d728c9f 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,9 @@ That's it — the skill activates automatically the next time your agent encount |---------|-------------|:------:|-----------------| | **cuOpt** | GPU-accelerated optimization — vehicle routing, linear programming, quadratic programming, installation, server deployment, and developer tools. | 19 | [`NVIDIA/cuopt/skills/`](https://github.com/NVIDIA/cuopt/tree/main/skills) | | **TensorRT-LLM** | LLM inference optimization — model onboarding to AutoDeploy, CI pipeline failure analysis, and test failure diagnostics. | 4 | [`NVIDIA/TensorRT-LLM/.claude/skills/`](https://github.com/NVIDIA/TensorRT-LLM/tree/main/.claude/skills) | -| **Model Optimizer** | Model optimization — quantization, sparsity, and distillation for efficient inference. | 4 | [`NVIDIA/Model-Optimizer/.claude/skills/`](https://github.com/NVIDIA/Model-Optimizer/tree/main/.claude/skills) | -| **Megatron Core** | Large-scale distributed training — model parallelism, pipeline parallelism, and mixed precision. | 2 | [`NVIDIA/Megatron-LM/.claude/skills/`](https://github.com/NVIDIA/Megatron-LM/tree/main/.claude/skills) | -| **Megatron Bridge** | Bridge between NeMo and Megatron — data processing, model conversion, and training utilities. | 8 | [`NVIDIA-NeMo/Megatron-Bridge/skills/`](https://github.com/NVIDIA-NeMo/Megatron-Bridge/tree/main/skills) | +| **Model-Optimizer** | Model optimization — quantization, sparsity, and distillation for efficient inference. | 4 | [`NVIDIA/Model-Optimizer/.claude/skills/`](https://github.com/NVIDIA/Model-Optimizer/tree/main/.claude/skills) | +| **Megatron-Core** | Large-scale distributed training — model parallelism, pipeline parallelism, and mixed precision. | 2 | [`NVIDIA/Megatron-LM/.claude/skills/`](https://github.com/NVIDIA/Megatron-LM/tree/main/.claude/skills) | +| **Megatron-Bridge** | Bridge between NeMo and Megatron — data processing, model conversion, and training utilities. | 8 | [`NVIDIA-NeMo/Megatron-Bridge/skills/`](https://github.com/NVIDIA-NeMo/Megatron-Bridge/tree/main/skills) | | **Nemotron Voice Agent** | Real-time conversational AI — deploy speech-to-speech voice agents on Workstation, Jetson Thor, or Cloud NIMs. | 1 | [`nemotron-voice-agent/.agents/skills/`](https://github.com/NVIDIA-AI-Blueprints/nemotron-voice-agent/tree/main/.agents/skills) | | **NeMo Gym** | RL training environments — add benchmarks, resources servers, agent wiring, and reward profiling. | 1 | [`NVIDIA-NeMo/Gym/.claude/skills/`](https://github.com/NVIDIA-NeMo/Gym/tree/main/.claude/skills) | | **NeMo Evaluator** | LLM evaluation — launch evaluations, access MLflow results, NeMo Evaluator Launcher assistant, and bring-your-own benchmarks. | 4 | [`NVIDIA-NeMo/Evaluator/.claude/skills/`](https://github.com/NVIDIA-NeMo/Evaluator/tree/main/packages/nemo-evaluator-launcher/.claude/skills) ([+1](https://github.com/NVIDIA-NeMo/Evaluator/tree/main/packages/nemo-evaluator/.claude/skills)) | @@ -64,9 +64,9 @@ For skill-related issues, feature requests, new skill ideas, discussions, and co |---------|--------|-------------|--------------|----------| | **cuOpt** | [Issues](https://github.com/NVIDIA/cuopt/issues) | [Discussions](https://github.com/NVIDIA/cuopt/discussions) | [Contributing](https://github.com/NVIDIA/cuopt/blob/main/CONTRIBUTING.md) | [Security](https://github.com/NVIDIA/cuopt/blob/main/SECURITY.md) | | **TensorRT-LLM** | [Issues](https://github.com/NVIDIA/TensorRT-LLM/issues) | [Discussions](https://github.com/NVIDIA/TensorRT-LLM/discussions) | [Contributing](https://github.com/NVIDIA/TensorRT-LLM/blob/main/CONTRIBUTING.md) | [Security](https://github.com/NVIDIA/TensorRT-LLM/blob/main/SECURITY.md) | -| **Model Optimizer** | [Issues](https://github.com/NVIDIA/Model-Optimizer/issues) | — | [Contributing](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md) | [Security](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md) | -| **Megatron Core** | [Issues](https://github.com/NVIDIA/Megatron-LM/issues) | [Discussions](https://github.com/NVIDIA/Megatron-LM/discussions) | [Contributing](https://github.com/NVIDIA/Megatron-LM/blob/main/CONTRIBUTING.md) | — | -| **Megatron Bridge** | [Issues](https://github.com/NVIDIA-NeMo/Megatron-Bridge/issues) | [Discussions](https://github.com/NVIDIA-NeMo/Megatron-Bridge/discussions) | [Contributing](https://github.com/NVIDIA-NeMo/Megatron-Bridge/blob/main/CONTRIBUTING.md) | — | +| **Model-Optimizer** | [Issues](https://github.com/NVIDIA/Model-Optimizer/issues) | — | [Contributing](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md) | [Security](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md) | +| **Megatron-Core** | [Issues](https://github.com/NVIDIA/Megatron-LM/issues) | [Discussions](https://github.com/NVIDIA/Megatron-LM/discussions) | [Contributing](https://github.com/NVIDIA/Megatron-LM/blob/main/CONTRIBUTING.md) | — | +| **Megatron-Bridge** | [Issues](https://github.com/NVIDIA-NeMo/Megatron-Bridge/issues) | [Discussions](https://github.com/NVIDIA-NeMo/Megatron-Bridge/discussions) | [Contributing](https://github.com/NVIDIA-NeMo/Megatron-Bridge/blob/main/CONTRIBUTING.md) | — | | **Nemotron Voice Agent** | [Issues](https://github.com/NVIDIA-AI-Blueprints/nemotron-voice-agent/issues) | [Discussions](https://github.com/NVIDIA-AI-Blueprints/nemotron-voice-agent/discussions) | [Contributing](https://github.com/NVIDIA-AI-Blueprints/nemotron-voice-agent/blob/main/CONTRIBUTING.md) | [Security](https://github.com/NVIDIA-AI-Blueprints/nemotron-voice-agent/blob/main/SECURITY.md) | | **NeMo Gym** | [Issues](https://github.com/NVIDIA-NeMo/Gym/issues) | [Discussions](https://github.com/NVIDIA-NeMo/Gym/discussions) | [Contributing](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md) | [Security](https://github.com/NVIDIA-NeMo/Gym/blob/main/SECURITY.md) | | **NeMo Evaluator** | [Issues](https://github.com/NVIDIA-NeMo/Evaluator/issues) | [Discussions](https://github.com/NVIDIA-NeMo/Evaluator/discussions) | [Contributing](https://github.com/NVIDIA-NeMo/Evaluator/blob/main/CONTRIBUTING.md) | [Security](https://github.com/NVIDIA-NeMo/Evaluator/blob/main/SECURITY.md) |