From 240ee4863b5117ff532523cdb8170ca3b216576a Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Thu, 12 Feb 2026 23:19:49 +0800 Subject: [PATCH 1/9] Extract CI validation into reusable scripts Move inline shell from validate-pr.yml into scripts/validate.sh and scripts/audit.sh so CI and local dev run the same code. Add Makefile for convenient local testing (make validate / make audit / make ci). --- .github/workflows/validate-pr.yml | 99 ++----------------------------- .gitignore | 1 + Makefile | 9 +++ scripts/audit.sh | 74 +++++++++++++++++++++++ scripts/validate.sh | 43 ++++++++++++++ 5 files changed, 133 insertions(+), 93 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100755 scripts/audit.sh create mode 100755 scripts/validate.sh diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 3c641a2..2efe610 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -10,32 +10,7 @@ jobs: - uses: actions/checkout@v4 - name: Validate hub.json - run: | - # Check valid JSON - jq empty skillshare-hub.json || exit 1 - - # Check required fields - missing=$(jq -r '.skills[] | select(.name == "" or .name == null or .description == "" or .description == null or .source == "" or .source == null) | .name // "unnamed"' skillshare-hub.json) - if [ -n "$missing" ]; then - echo "ERROR: Skills missing required fields: $missing" - exit 1 - fi - - # Check no duplicate names - dupes=$(jq -r '[.skills[].name] | group_by(.) | map(select(length > 1)) | flatten | .[]' skillshare-hub.json) - if [ -n "$dupes" ]; then - echo "ERROR: Duplicate skill names: $dupes" - exit 1 - fi - - # Check name format (lowercase, hyphens only) - bad_names=$(jq -r '.skills[].name | select(test("^[a-z0-9][a-z0-9-]*$") | not)' skillshare-hub.json) - if [ -n "$bad_names" ]; then - echo "ERROR: Invalid skill names (must be lowercase, hyphens): $bad_names" - exit 1 - fi - - echo "Validation passed: $(jq '.skills | length' skillshare-hub.json) skills" + run: ./scripts/validate.sh audit: needs: validate @@ -45,32 +20,7 @@ jobs: with: fetch-depth: 0 - - name: Find new/changed skill sources - id: diff - env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - run: | - # Get base version sources (empty if file doesn't exist in base) - base_sources=$(git show "${BASE_SHA}:skillshare-hub.json" 2>/dev/null \ - | jq -r '.skills[] | "\(.name)|\(.source)"' | sort) || base_sources="" - - pr_sources=$(jq -r '.skills[] | "\(.name)|\(.source)"' skillshare-hub.json | sort) - - # Find new or changed entries (name|source pairs not in base) - new_sources=$(comm -13 <(echo "$base_sources") <(echo "$pr_sources") | cut -d'|' -f2) - - if [ -z "$new_sources" ]; then - echo "No new or changed skill sources to audit" - echo "has_new=false" >> "$GITHUB_OUTPUT" - else - echo "New/changed sources to audit:" - echo "$new_sources" - echo "$new_sources" > /tmp/new-sources.txt - echo "has_new=true" >> "$GITHUB_OUTPUT" - fi - - - name: Download skillshare - if: steps.diff.outputs.has_new == 'true' + - name: Install skillshare env: GH_TOKEN: ${{ github.token }} run: | @@ -81,44 +31,7 @@ jobs: skillshare version - name: Audit new skills - if: steps.diff.outputs.has_new == 'true' - run: | - failed=0 - while IFS= read -r source; do - [ -z "$source" ] && continue - - # Determine clone URL - if [[ "$source" == http* ]]; then - clone_url="$source" - else - clone_url="https://github.com/${source}.git" - fi - - safe_name=$(echo "$source" | tr '/' '-') - clone_dir="/tmp/audit-${safe_name}" - - echo "::group::Auditing ${source}" - - if ! git clone --depth 1 "$clone_url" "$clone_dir" 2>&1; then - echo "::error::Failed to clone ${source}" - failed=1 - echo "::endgroup::" - continue - fi - - if ! skillshare audit "$clone_dir" --threshold high; then - echo "::error::Security audit failed for ${source}" - failed=1 - fi - - rm -rf "$clone_dir" - echo "::endgroup::" - done < /tmp/new-sources.txt - - if [ "$failed" -ne 0 ]; then - echo "" - echo "::error::One or more skills failed the security audit. Please fix the flagged issues and push again." - exit 1 - fi - - echo "All new skills passed the security audit" + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + CI: "true" + run: ./scripts/audit.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c5f206 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.claude/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..14c8bf2 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +.PHONY: validate audit ci + +validate: ## Validate skillshare-hub.json format and rules + @./scripts/validate.sh + +audit: ## Audit new/changed skills against main + @./scripts/audit.sh main + +ci: validate audit ## Run full CI locally diff --git a/scripts/audit.sh b/scripts/audit.sh new file mode 100755 index 0000000..d893087 --- /dev/null +++ b/scripts/audit.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +HUB_FILE="skillshare-hub.json" +BASE_REF="${1:-${BASE_SHA:-main}}" + +# Detect environment +is_ci() { [ "${CI:-}" = "true" ]; } +log_group() { is_ci && echo "::group::$1" || echo "=== $1 ==="; } +log_endgroup() { is_ci && echo "::endgroup::" || true; } +log_error() { is_ci && echo "::error::$1" || echo "ERROR: $1"; } + +# --- Find new/changed sources --- +base_sources=$(git show "${BASE_REF}:${HUB_FILE}" 2>/dev/null \ + | jq -r '.skills[] | "\(.name)|\(.source)"' | sort) || base_sources="" + +pr_sources=$(jq -r '.skills[] | "\(.name)|\(.source)"' "$HUB_FILE" | sort) + +new_sources=$(comm -13 <(echo "$base_sources") <(echo "$pr_sources") | cut -d'|' -f2) + +if [ -z "$new_sources" ]; then + echo "No new or changed skill sources to audit" + exit 0 +fi + +echo "New/changed sources to audit:" +echo "$new_sources" +echo "" + +# --- Check skillshare is available --- +if ! command -v skillshare &>/dev/null; then + echo "ERROR: skillshare CLI not found. Install from https://github.com/runkids/skillshare" + exit 1 +fi + +# --- Audit each source --- +failed=0 +while IFS= read -r source; do + [ -z "$source" ] && continue + + if [[ "$source" == http* ]]; then + clone_url="$source" + else + clone_url="https://github.com/${source}.git" + fi + + safe_name=$(echo "$source" | tr '/' '-') + clone_dir="/tmp/audit-${safe_name}" + + log_group "Auditing ${source}" + + if ! git clone --depth 1 "$clone_url" "$clone_dir" 2>&1; then + log_error "Failed to clone ${source}" + failed=1 + log_endgroup + continue + fi + + if ! skillshare audit "$clone_dir" --threshold high; then + log_error "Security audit failed for ${source}" + failed=1 + fi + + rm -rf "$clone_dir" + log_endgroup +done <<< "$new_sources" + +if [ "$failed" -ne 0 ]; then + echo "" + log_error "One or more skills failed the security audit. Please fix the flagged issues and push again." + exit 1 +fi + +echo "All new skills passed the security audit" diff --git a/scripts/validate.sh b/scripts/validate.sh new file mode 100755 index 0000000..7ee0b2e --- /dev/null +++ b/scripts/validate.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +HUB_FILE="${1:-skillshare-hub.json}" + +if [ ! -f "$HUB_FILE" ]; then + echo "ERROR: $HUB_FILE not found" + exit 1 +fi + +# Check valid JSON +if ! jq empty "$HUB_FILE" 2>/dev/null; then + echo "ERROR: Invalid JSON in $HUB_FILE" + exit 1 +fi +echo "OK: valid JSON" + +# Check required fields +missing=$(jq -r '.skills[] | select(.name == "" or .name == null or .description == "" or .description == null or .source == "" or .source == null) | .name // "unnamed"' "$HUB_FILE") +if [ -n "$missing" ]; then + echo "ERROR: Skills missing required fields: $missing" + exit 1 +fi +echo "OK: required fields present" + +# Check no duplicate names +dupes=$(jq -r '[.skills[].name] | group_by(.) | map(select(length > 1)) | flatten | .[]' "$HUB_FILE") +if [ -n "$dupes" ]; then + echo "ERROR: Duplicate skill names: $dupes" + exit 1 +fi +echo "OK: no duplicate names" + +# Check name format (lowercase, hyphens only) +bad_names=$(jq -r '.skills[].name | select(test("^[a-z0-9][a-z0-9-]*$") | not)' "$HUB_FILE") +if [ -n "$bad_names" ]; then + echo "ERROR: Invalid skill names (must be lowercase, hyphens): $bad_names" + exit 1 +fi +echo "OK: name format valid" + +echo "" +echo "Validation passed: $(jq '.skills | length' "$HUB_FILE") skills" From 6fc274cb98026a65e37b2b7da3a5803c6f004e6f Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Thu, 12 Feb 2026 23:22:20 +0800 Subject: [PATCH 2/9] Trigger CI on workflow and script changes Add scripts/** and the workflow file itself to the paths filter so CI runs when validation logic is modified, not only when skillshare-hub.json changes. --- .github/workflows/validate-pr.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 2efe610..2acd864 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -1,7 +1,10 @@ name: Validate PR on: pull_request: - paths: ['skillshare-hub.json'] + paths: + - 'skillshare-hub.json' + - 'scripts/**' + - '.github/workflows/validate-pr.yml' jobs: validate: From 183ecc47dc72fcd98be32487c36a091843dcc057 Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Thu, 12 Feb 2026 23:28:34 +0800 Subject: [PATCH 3/9] Add JSON format check and auto-formatter - Add scripts/format.sh: sort skills by name, normalize to jq 2-space - Add format check to scripts/validate.sh (fails if unformatted) - Add make format target - Apply formatting to skillshare-hub.json --- Makefile | 5 ++++- scripts/format.sh | 14 ++++++++++++++ scripts/validate.sh | 9 +++++++++ skillshare-hub.json | 15 ++++++++++++++- 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100755 scripts/format.sh diff --git a/Makefile b/Makefile index 14c8bf2..cad5348 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ -.PHONY: validate audit ci +.PHONY: format validate audit ci + +format: ## Format skillshare-hub.json (sort by name, 2-space indent) + @./scripts/format.sh validate: ## Validate skillshare-hub.json format and rules @./scripts/validate.sh diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100755 index 0000000..9489406 --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +HUB_FILE="${1:-skillshare-hub.json}" + +if [ ! -f "$HUB_FILE" ]; then + echo "ERROR: $HUB_FILE not found" + exit 1 +fi + +jq '.skills |= sort_by(.name)' "$HUB_FILE" > "${HUB_FILE}.tmp" \ + && mv "${HUB_FILE}.tmp" "$HUB_FILE" + +echo "Formatted: $HUB_FILE ($(jq '.skills | length' "$HUB_FILE") skills, sorted by name)" diff --git a/scripts/validate.sh b/scripts/validate.sh index 7ee0b2e..f4c10f9 100755 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -39,5 +39,14 @@ if [ -n "$bad_names" ]; then fi echo "OK: name format valid" +# Check formatting (sorted by name, jq default 2-space indent) +expected=$(jq '.skills |= sort_by(.name)' "$HUB_FILE") +actual=$(cat "$HUB_FILE") +if [ "$expected" != "$actual" ]; then + echo "ERROR: $HUB_FILE is not formatted. Run: make format" + exit 1 +fi +echo "OK: formatting correct" + echo "" echo "Validation passed: $(jq '.skills | length' "$HUB_FILE") skills" diff --git a/skillshare-hub.json b/skillshare-hub.json index b93997e..4975b1e 100644 --- a/skillshare-hub.json +++ b/skillshare-hub.json @@ -6,7 +6,20 @@ "description": "Verify and fix ASCII box-drawing diagram alignment in markdown files", "source": "runkids/my-skills", "skill": "ascii-box-check", - "tags": ["docs", "workflow"] + "tags": [ + "docs", + "workflow" + ] + }, + { + "name": "test", + "description": "Verify and fix ASCII box-drawing diagram alignment in markdown files", + "source": "runkids/my-skills", + "skill": "remotion", + "tags": [ + "test", + "workflow" + ] } ] } From 959a08a1a201bd2eebc436dd87535adfdb9ae2a9 Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Thu, 12 Feb 2026 23:32:43 +0800 Subject: [PATCH 4/9] Add audit results as PR comment - Generate markdown report with source, status, and risk score - Auto-post results as PR comment (replaces previous comment on re-run) - Add permissions for pull-requests: write - Fix grep -P incompatibility for macOS/Linux portability --- .github/workflows/validate-pr.yml | 31 ++++++++++++++++++++- scripts/audit.sh | 45 ++++++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 2acd864..b92af9e 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -6,6 +6,10 @@ on: - 'scripts/**' - '.github/workflows/validate-pr.yml' +permissions: + contents: read + pull-requests: write + jobs: validate: runs-on: ubuntu-latest @@ -34,7 +38,32 @@ jobs: skillshare version - name: Audit new skills + id: audit env: BASE_SHA: ${{ github.event.pull_request.base.sha }} CI: "true" - run: ./scripts/audit.sh + AUDIT_REPORT: /tmp/audit-report.md + run: ./scripts/audit.sh || echo "audit_failed=true" >> "$GITHUB_OUTPUT" + + - name: Comment PR with audit results + if: always() && hashFiles('/tmp/audit-report.md') != '' + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # Delete previous audit comment if exists + gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ + --jq '.[] | select(.body | startswith("## Skill Audit Results")) | .id' \ + | while read -r id; do + gh api -X DELETE "repos/${{ github.repository }}/issues/comments/${id}" + done + + gh pr comment "${PR_NUMBER}" --body-file /tmp/audit-report.md + + - name: Show audit summary + if: always() && hashFiles('/tmp/audit-report.md') != '' + run: cat /tmp/audit-report.md >> "$GITHUB_STEP_SUMMARY" + + - name: Fail if audit failed + if: steps.audit.outputs.audit_failed == 'true' + run: exit 1 diff --git a/scripts/audit.sh b/scripts/audit.sh index d893087..45bf295 100755 --- a/scripts/audit.sh +++ b/scripts/audit.sh @@ -3,6 +3,7 @@ set -euo pipefail HUB_FILE="skillshare-hub.json" BASE_REF="${1:-${BASE_SHA:-main}}" +REPORT_FILE="${AUDIT_REPORT:-}" # Detect environment is_ci() { [ "${CI:-}" = "true" ]; } @@ -16,10 +17,12 @@ base_sources=$(git show "${BASE_REF}:${HUB_FILE}" 2>/dev/null \ pr_sources=$(jq -r '.skills[] | "\(.name)|\(.source)"' "$HUB_FILE" | sort) -new_sources=$(comm -13 <(echo "$base_sources") <(echo "$pr_sources") | cut -d'|' -f2) +new_entries=$(comm -13 <(echo "$base_sources") <(echo "$pr_sources")) +new_sources=$(echo "$new_entries" | cut -d'|' -f2 | sort -u) -if [ -z "$new_sources" ]; then +if [ -z "$new_entries" ]; then echo "No new or changed skill sources to audit" + [ -n "$REPORT_FILE" ] && echo "No new or changed skill sources to audit." > "$REPORT_FILE" exit 0 fi @@ -35,6 +38,8 @@ fi # --- Audit each source --- failed=0 +results=() + while IFS= read -r source; do [ -z "$source" ] && continue @@ -51,20 +56,54 @@ while IFS= read -r source; do if ! git clone --depth 1 "$clone_url" "$clone_dir" 2>&1; then log_error "Failed to clone ${source}" + results+=("| \`${source}\` | :x: Clone failed | - |") failed=1 log_endgroup continue fi - if ! skillshare audit "$clone_dir" --threshold high; then + audit_output=$(skillshare audit "$clone_dir" --threshold high 2>&1) || true + audit_exit=$? + + echo "$audit_output" + + # Extract risk score + risk=$(echo "$audit_output" | sed -n 's/.*Risk: \([A-Z]* ([0-9]*\/[0-9]*)\).*/\1/p' | tail -1) + [ -z "$risk" ] && risk="N/A" + + if echo "$audit_output" | grep -q "Passed:.*0" && echo "$audit_output" | grep -q "Failed:"; then + results+=("| \`${source}\` | :x: Failed | ${risk} |") log_error "Security audit failed for ${source}" failed=1 + elif echo "$audit_output" | grep -q "config not found"; then + results+=("| \`${source}\` | :warning: Skipped (no config) | - |") + else + results+=("| \`${source}\` | :white_check_mark: Passed | ${risk} |") fi rm -rf "$clone_dir" log_endgroup done <<< "$new_sources" +# --- Generate report --- +if [ -n "$REPORT_FILE" ]; then + { + echo "## Skill Audit Results" + echo "" + echo "| Source | Status | Risk |" + echo "|--------|--------|------|" + for row in "${results[@]}"; do + echo "$row" + done + echo "" + if [ "$failed" -ne 0 ]; then + echo "> **Failed**: One or more skills did not pass the security audit." + else + echo "> **All skills passed** the security audit." + fi + } > "$REPORT_FILE" +fi + if [ "$failed" -ne 0 ]; then echo "" log_error "One or more skills failed the security audit. Please fix the flagged issues and push again." From 2517bb358d0b900366d3818975c808ff1712536e Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Thu, 12 Feb 2026 23:43:17 +0800 Subject: [PATCH 5/9] Strengthen CI audit: require config and use install script - Switch skillshare install from gh release to curl install script - Run skillshare init after install to create global config - Treat "config not found" as audit failure instead of warning skip - Add job summary step to display audit results in workflow page --- .github/workflows/validate-pr.yml | 8 ++------ scripts/audit.sh | 4 +++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index b92af9e..52469ec 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -28,14 +28,10 @@ jobs: fetch-depth: 0 - name: Install skillshare - env: - GH_TOKEN: ${{ github.token }} run: | - gh release download --repo runkids/skillshare \ - --pattern 'skillshare_*_linux_amd64.tar.gz' \ - --output /tmp/skillshare.tar.gz - tar xzf /tmp/skillshare.tar.gz -C /usr/local/bin skillshare + curl -fsSL https://raw.githubusercontent.com/runkids/skillshare/main/install.sh | sh skillshare version + skillshare init -g --no-copy --no-targets --no-git --no-skill - name: Audit new skills id: audit diff --git a/scripts/audit.sh b/scripts/audit.sh index 45bf295..606dcc5 100755 --- a/scripts/audit.sh +++ b/scripts/audit.sh @@ -76,7 +76,9 @@ while IFS= read -r source; do log_error "Security audit failed for ${source}" failed=1 elif echo "$audit_output" | grep -q "config not found"; then - results+=("| \`${source}\` | :warning: Skipped (no config) | - |") + results+=("| \`${source}\` | :x: No config | - |") + log_error "No skillshare config found for ${source}. Run 'skillshare init' in the source repo." + failed=1 else results+=("| \`${source}\` | :white_check_mark: Passed | ${risk} |") fi From fe513d74ed92cf5fae7bd3ef875762af7e0c6160 Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Thu, 12 Feb 2026 23:51:45 +0800 Subject: [PATCH 6/9] Fix audit report: use step output instead of hashFiles hashFiles() only works within $GITHUB_WORKSPACE, so /tmp/audit-report.md was never detected. Use step output has_report flag to conditionally run comment and summary steps. --- .github/workflows/validate-pr.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 52469ec..022a7fc 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -39,10 +39,12 @@ jobs: BASE_SHA: ${{ github.event.pull_request.base.sha }} CI: "true" AUDIT_REPORT: /tmp/audit-report.md - run: ./scripts/audit.sh || echo "audit_failed=true" >> "$GITHUB_OUTPUT" + run: | + ./scripts/audit.sh || echo "audit_failed=true" >> "$GITHUB_OUTPUT" + [ -f /tmp/audit-report.md ] && echo "has_report=true" >> "$GITHUB_OUTPUT" - name: Comment PR with audit results - if: always() && hashFiles('/tmp/audit-report.md') != '' + if: always() && steps.audit.outputs.has_report == 'true' env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} @@ -57,7 +59,7 @@ jobs: gh pr comment "${PR_NUMBER}" --body-file /tmp/audit-report.md - name: Show audit summary - if: always() && hashFiles('/tmp/audit-report.md') != '' + if: always() && steps.audit.outputs.has_report == 'true' run: cat /tmp/audit-report.md >> "$GITHUB_STEP_SUMMARY" - name: Fail if audit failed From a5a01499ac57be8410fc34b6bfdadbaff60e92b4 Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Thu, 12 Feb 2026 23:55:00 +0800 Subject: [PATCH 7/9] Include detailed audit output in PR comment Add collapsible details section showing full skillshare audit output (findings, severity, risk score) for each source. --- scripts/audit.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/audit.sh b/scripts/audit.sh index 606dcc5..8bc413f 100755 --- a/scripts/audit.sh +++ b/scripts/audit.sh @@ -39,6 +39,7 @@ fi # --- Audit each source --- failed=0 results=() +details=() while IFS= read -r source; do [ -z "$source" ] && continue @@ -54,16 +55,16 @@ while IFS= read -r source; do log_group "Auditing ${source}" - if ! git clone --depth 1 "$clone_url" "$clone_dir" 2>&1; then + if ! clone_output=$(git clone --depth 1 "$clone_url" "$clone_dir" 2>&1); then log_error "Failed to clone ${source}" results+=("| \`${source}\` | :x: Clone failed | - |") + details+=("DETAIL_SEP### \`${source}\`"$'\n'"Clone failed:"$'\n'"\`\`\`"$'\n'"${clone_output}"$'\n'"\`\`\`") failed=1 log_endgroup continue fi audit_output=$(skillshare audit "$clone_dir" --threshold high 2>&1) || true - audit_exit=$? echo "$audit_output" @@ -73,14 +74,17 @@ while IFS= read -r source; do if echo "$audit_output" | grep -q "Passed:.*0" && echo "$audit_output" | grep -q "Failed:"; then results+=("| \`${source}\` | :x: Failed | ${risk} |") + details+=("DETAIL_SEP### \`${source}\`"$'\n'"\`\`\`"$'\n'"${audit_output}"$'\n'"\`\`\`") log_error "Security audit failed for ${source}" failed=1 elif echo "$audit_output" | grep -q "config not found"; then results+=("| \`${source}\` | :x: No config | - |") + details+=("DETAIL_SEP### \`${source}\`"$'\n'"No skillshare config found. Run \`skillshare init\` in the source repo.") log_error "No skillshare config found for ${source}. Run 'skillshare init' in the source repo." failed=1 else results+=("| \`${source}\` | :white_check_mark: Passed | ${risk} |") + details+=("DETAIL_SEP### \`${source}\`"$'\n'"\`\`\`"$'\n'"${audit_output}"$'\n'"\`\`\`") fi rm -rf "$clone_dir" @@ -103,6 +107,15 @@ if [ -n "$REPORT_FILE" ]; then else echo "> **All skills passed** the security audit." fi + echo "" + echo "
" + echo "Audit Details" + echo "" + for detail in "${details[@]}"; do + echo "${detail}" | sed 's/^DETAIL_SEP//' + echo "" + done + echo "
" } > "$REPORT_FILE" fi From ebd46a4304c1e8a912a23d2e9df90653db4b2143 Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Thu, 12 Feb 2026 23:56:23 +0800 Subject: [PATCH 8/9] Strip ANSI color codes from audit output in report --- scripts/audit.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/audit.sh b/scripts/audit.sh index 8bc413f..1e91d27 100755 --- a/scripts/audit.sh +++ b/scripts/audit.sh @@ -64,7 +64,7 @@ while IFS= read -r source; do continue fi - audit_output=$(skillshare audit "$clone_dir" --threshold high 2>&1) || true + audit_output=$(skillshare audit "$clone_dir" --threshold high 2>&1 | sed 's/\x1b\[[0-9;]*m//g') || true echo "$audit_output" From ff4543d7beba6ad717269cc9c57319779bfd23da Mon Sep 17 00:00:00 2001 From: Willie Hung Date: Fri, 13 Feb 2026 00:02:37 +0800 Subject: [PATCH 9/9] Change CI blocking to risk-based instead of severity-based Block only when aggregate Risk >= HIGH, not when individual findings have high severity. LOW/MEDIUM risk skills now pass. --- scripts/audit.sh | 15 ++++++++------- skillshare-hub.json | 11 +++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts/audit.sh b/scripts/audit.sh index 1e91d27..8b86835 100755 --- a/scripts/audit.sh +++ b/scripts/audit.sh @@ -68,20 +68,21 @@ while IFS= read -r source; do echo "$audit_output" - # Extract risk score + # Extract risk score and label risk=$(echo "$audit_output" | sed -n 's/.*Risk: \([A-Z]* ([0-9]*\/[0-9]*)\).*/\1/p' | tail -1) [ -z "$risk" ] && risk="N/A" + risk_label=$(echo "$risk" | awk '{print $1}') - if echo "$audit_output" | grep -q "Passed:.*0" && echo "$audit_output" | grep -q "Failed:"; then - results+=("| \`${source}\` | :x: Failed | ${risk} |") - details+=("DETAIL_SEP### \`${source}\`"$'\n'"\`\`\`"$'\n'"${audit_output}"$'\n'"\`\`\`") - log_error "Security audit failed for ${source}" - failed=1 - elif echo "$audit_output" | grep -q "config not found"; then + if echo "$audit_output" | grep -q "config not found"; then results+=("| \`${source}\` | :x: No config | - |") details+=("DETAIL_SEP### \`${source}\`"$'\n'"No skillshare config found. Run \`skillshare init\` in the source repo.") log_error "No skillshare config found for ${source}. Run 'skillshare init' in the source repo." failed=1 + elif [[ "$risk_label" == "HIGH" || "$risk_label" == "CRITICAL" ]]; then + results+=("| \`${source}\` | :x: Risk ${risk_label} | ${risk} |") + details+=("DETAIL_SEP### \`${source}\`"$'\n'"\`\`\`"$'\n'"${audit_output}"$'\n'"\`\`\`") + log_error "Risk ${risk_label} for ${source}" + failed=1 else results+=("| \`${source}\` | :white_check_mark: Passed | ${risk} |") details+=("DETAIL_SEP### \`${source}\`"$'\n'"\`\`\`"$'\n'"${audit_output}"$'\n'"\`\`\`") diff --git a/skillshare-hub.json b/skillshare-hub.json index 4975b1e..08da980 100644 --- a/skillshare-hub.json +++ b/skillshare-hub.json @@ -12,13 +12,12 @@ ] }, { - "name": "test", - "description": "Verify and fix ASCII box-drawing diagram alignment in markdown files", - "source": "runkids/my-skills", - "skill": "remotion", + "name": "skill-creator", + "description": "Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.", + "source": "anthropics/skills", + "skill": "skill-creator", "tags": [ - "test", - "workflow" + "skill" ] } ]