diff --git a/.github/workflows/enforce-skills-only-diffs.yml b/.github/workflows/enforce-skills-only-diffs.yml new file mode 100644 index 0000000..ffd2bf2 --- /dev/null +++ b/.github/workflows/enforce-skills-only-diffs.yml @@ -0,0 +1,80 @@ +name: Enforce skills-only diffs + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + +permissions: + contents: read + pull-requests: read + +jobs: + verify-bypass: + # Runs only when the bypass label is present. Fails the check unless the + # label was applied by someone with write/maintain/admin permission. + # Defense in depth: even if GitHub label permissions ever drift, the + # bypass can't be abused by contributors. + if: ${{ contains(github.event.pull_request.labels.*.name, 'allow-infra-change') }} + runs-on: ubuntu-latest + steps: + - name: Verify label applier has write permission + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + run: | + set -euo pipefail + + perm=$(gh api "repos/$REPO/collaborators/$ACTOR/permission" --jq '.permission' 2>/dev/null || echo "none") + echo "Actor: $ACTOR, permission: $perm" + + case "$perm" in + admin|maintain|write) echo "Bypass authorized. ✓" ;; + *) + echo "::error::User '$ACTOR' does not have write permission — cannot bypass skills-only check." + exit 1 + ;; + esac + + enforce: + if: ${{ !contains(github.event.pull_request.labels.*.name, 'allow-infra-change') }} + runs-on: ubuntu-latest + steps: + - name: Check changed files + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + + files=$(gh api --paginate "repos/$REPO/pulls/$PR_NUMBER/files" --jq '.[].filename') + + bad=() + while IFS= read -r f; do + [ -z "$f" ] && continue + case "$f" in + *.md) ;; + .claude-plugin/marketplace.json) ;; + *) bad+=("$f") ;; + esac + done <<< "$files" + + if [ ${#bad[@]} -gt 0 ]; then + echo "::error::This PR touches files outside the allowed set." + echo "" + echo "Only these changes are permitted in contributor PRs:" + echo " - *.md files (SKILL.md, references, docs)" + echo " - .claude-plugin/marketplace.json" + echo "" + echo "Disallowed files in this PR:" + for f in "${bad[@]}"; do + echo " - $f" + done + echo "" + echo "If you need to change CI, scripts, LICENSE, or other infrastructure," + echo "a maintainer can add the 'allow-infra-change' label to bypass this check." + exit 1 + fi + + echo "All changed files are within the allowed set. ✓"