diff --git a/.github/labels.yml b/.github/labels.yml index 11ee137..9624369 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -51,6 +51,11 @@ color: "c5def5" description: "State management and persistence" +# ── Meta ───────────────────────────────────────────────────────────────────── +- name: "contributors" + color: "5319e7" + description: "Contributor recognition & governance automation (all-contributors audit, etc.)" + # ── Status ─────────────────────────────────────────────────────────────────── - name: "good first issue" color: "7057ff" diff --git a/.github/workflows/contributors-audit.yml b/.github/workflows/contributors-audit.yml new file mode 100644 index 0000000..f743a15 --- /dev/null +++ b/.github/workflows/contributors-audit.yml @@ -0,0 +1,111 @@ +# Weekly audit of .all-contributorsrc against current reality. +# +# Runs scripts/check_contributors.sh, which checks for: +# 1. Profile URL drift between README.md and .all-contributorsrc +# (failure mode that hit us via #616 → #617 → #620). +# 2. Contributors with merged PRs missing from .all-contributorsrc +# (failure mode that hit us via @yodakanohoshi / @Photon101 / +# @kiwamizamurai being absent at the time of the v0.7.8 cut). +# 3. Contributors eligible for Triage Collaborator per GOVERNANCE.md +# criteria (5+ merged PRs, active last 30 days) who are not yet +# invited (failure mode that hit us when masukai initially +# thought Muawiya-contact still needed elevating — they didn't, +# but the only way to confirm that confidently was a manual +# collaborators check). +# +# When the script reports a mismatch, this workflow opens (or updates) +# a GitHub issue with the detailed report so the gap is acted on +# rather than silently accumulating. + +name: contributors-audit + +on: + schedule: + # Monday 00:30 UTC — slightly offset from the existing nightly / + # weekly schedules in ci.yml / codeql.yml so they don't all queue + # at the same minute. + - cron: '30 0 * * 1' + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Run contributors audit + id: audit + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set +e + OUTPUT=$(bash scripts/check_contributors.sh 2>&1) + EXIT_CODE=$? + set -e + { + echo "exit_code=$EXIT_CODE" + echo "report<> "$GITHUB_OUTPUT" + # Also dump to the Action log for quick inspection. + echo "$OUTPUT" + + - name: Open / update tracking issue when audit finds gaps + if: steps.audit.outputs.exit_code != '0' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TITLE="chore(contributors): weekly audit found gaps in .all-contributorsrc" + BODY=$(cat < + Full report + + \`\`\` + ${{ steps.audit.outputs.report }} + \`\`\` + + + + --- + + *This issue was opened automatically by the \`contributors-audit\` workflow. It will be updated rather than duplicated on subsequent runs.* + EOF + ) + + # Reuse an existing open issue if one already exists; otherwise create. + EXISTING=$(gh issue list \ + --search "in:title \"chore(contributors): weekly audit found gaps\"" \ + --state open \ + --json number \ + -q '.[0].number // empty') + + if [[ -n "$EXISTING" ]]; then + echo "Updating existing audit issue #$EXISTING" + gh issue comment "$EXISTING" --body "$BODY" + else + echo "Opening new audit issue" + # Ensure the labels exist before the issue create — otherwise + # `gh issue create --label` hard-fails if a label is missing + # (e.g. on the very first run, before `make sync-labels` has + # materialised .github/labels.yml). `--force` makes this + # idempotent (update-or-create); .github/labels.yml stays the + # source of truth and sync-labels reconciles colour/description. + gh label create contributors \ + --color "5319e7" \ + --description "Contributor recognition & governance automation (all-contributors audit, etc.)" \ + --force || true + gh issue create \ + --title "$TITLE" \ + --body "$BODY" \ + --label "chore,contributors" + fi diff --git a/Makefile b/Makefile index b63bb12..9f28898 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,9 @@ check-skills: ## Verify .claude/commands/ matches skills (CI gate) check-i18n: ## Check if translated *.{lang}.md files are in sync with English base @bash scripts/check-i18n-sync.sh +check-contributors: ## Audit .all-contributorsrc for drift / missing entries / overdue Triage invitations + @bash scripts/check_contributors.sh + check-changelog: ## Verify every drt-core v* tag has a ## [X.Y.Z] section in CHANGELOG.md @python3 scripts/check_changelog_monotonic.py diff --git a/scripts/check_contributors.sh b/scripts/check_contributors.sh new file mode 100755 index 0000000..1324423 --- /dev/null +++ b/scripts/check_contributors.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash +# +# check_contributors.sh — audit .all-contributorsrc against current reality. +# +# Three checks: +# 1. URL drift — profile URL in .all-contributorsrc differs from the one +# rendered in README.md (typically because the README was hand-edited +# without syncing the source-of-truth). Surfaces the failure mode that +# caused PR #616 → #617 / #620. +# 2. Missing entry — a contributor with at least one merged PR is absent +# from .all-contributorsrc. +# 3. Triage eligibility — a contributor with 5+ merged PRs, active in the +# last 30 days, who is NOT yet a Triage Collaborator per GOVERNANCE.md +# criteria. Surfaces forgotten elevations. +# +# Exit codes: +# 0 — no issues, .all-contributorsrc is in sync +# 1 — one or more issues found; details on stdout +# 2 — script error (gh / jq missing, unauthenticated, etc.) +# +# Local usage: +# make check-contributors +# ./scripts/check_contributors.sh +# +# CI usage (.github/workflows/contributors-audit.yml runs this weekly). + +set -euo pipefail + +REPO="drt-hub/drt" +ALL_CONTRIBUTORS_FILE=".all-contributorsrc" +README_FILE="README.md" +ACTIVE_WINDOW_DAYS=30 +TRIAGE_THRESHOLD=5 + +# --- prerequisites ----------------------------------------------------------- + +require() { + if ! command -v "$1" > /dev/null 2>&1; then + echo "error: '$1' is required but not installed" >&2 + exit 2 + fi +} + +require gh +require jq + +if ! gh auth status > /dev/null 2>&1; then + echo "error: gh CLI not authenticated (run 'gh auth login' or set GITHUB_TOKEN)" >&2 + exit 2 +fi + +if [[ ! -f "$ALL_CONTRIBUTORS_FILE" ]]; then + echo "error: $ALL_CONTRIBUTORS_FILE not found (run from repo root)" >&2 + exit 2 +fi + +# --- helpers ----------------------------------------------------------------- + +header() { + echo + echo "=== $1 ===" +} + +ISSUES=0 + +flag_issue() { + ISSUES=$((ISSUES + 1)) +} + +# --- data: parse .all-contributorsrc ---------------------------------------- + +contributors_json=$(jq -c '.contributors' "$ALL_CONTRIBUTORS_FILE") +logins=$(echo "$contributors_json" | jq -r '.[].login') + +# --- Check 1: URL drift ------------------------------------------------------ + +header "Check 1: profile URL drift (.all-contributorsrc vs README.md)" + +drift_count=0 +while IFS=$'\t' read -r login profile; do + # Find the contributor's row in README.md by matching the avatar+login + # signature and pull the outer of the SAME . + readme_url=$(grep -oE "]*alt=\"[^\"]*\"/>
[^<]+