Help us improve
Share bugs, ideas, or general feedback.
From redhat-contribution-report
Evaluates contributions to open source projects by building a contributor roster from Red Hat LDAP org traversal, a GitHub organization's member list, or both, then measuring GitHub contributions, maintainership, governance roles, and roadmap influence. Supports multiple projects in a single evaluation run.
npx claudepluginhub maxamillion/agentskill-redhat-contribution-report --plugin redhat-contribution-reportHow this skill is triggered — by the user, by Claude, or both
Slash command
/redhat-contribution-report:redhat-contribution-reportThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Evaluate contributions to one or more open source projects by:
assets/audit-validate.pyassets/github-org-roster.pyassets/governance-file-scanner.pyassets/kpi1-pr-analysis.pyassets/kpi1-workflow-detect.pyassets/scoring-rubric.jsonassets/username-batch-resolve.pyreferences/DATA-SOURCES.mdreferences/LDAP-GUIDE.mdreferences/REPORT-TEMPLATE.mdreferences/RESEARCH-PROMPTS.mdGenerates brag documents from GitHub activity using LLM APIs (OpenAI/Anthropic). Useful for performance reviews, promotion packets, and self-assessments.
Searches GitHub code search for public repos that import a given SDK or library, classifies repos by adopter type, scores adoption signal, and outputs a ranked lead report. Use when asked to find SDK users or track adoption.
Analyzes git commit history for engineering retrospectives, tracking work patterns, code quality metrics, trends, per-person breakdowns, shipping streaks, and actionable improvements. Use for 'retro', weekly reviews, or 'what did we ship'.
Share bugs, ideas, or general feedback.
Evaluate contributions to one or more open source projects by:
LDAP only (Red Hat manager email):
/redhat-contribution-report shuels@redhat.com kubeflow/kubeflow kserve/kserve
GitHub org only:
/redhat-contribution-report kserve kserve/kserve kubeflow/kubeflow
Both (LDAP + GitHub org):
/redhat-contribution-report shuels@redhat.com kserve kubeflow/kubeflow kserve/kserve
$ARGUMENTS = [manager_email] [github_org] <project1> [project2] [project3] ...
Arguments are classified by pattern — no flags needed:
@ → manager_email (LDAP org leader email)/ → project (GitHub owner/repo)github_org (validated via gh api orgs/{name})At least one of manager_email or github_org must be present, plus at least one project.
shuels@redhat.com)kserve). Members become the contributor roster.owner/repo format. If only a project name is given (e.g., kubeflow), attempt to resolve it via gh search reposThree modes:
| Mode | Arguments | Roster Source |
|---|---|---|
| LDAP only | email project... | Red Hat LDAP org tree |
| GitHub org only | org project... | GitHub org member list |
| Both | email org project... | LDAP + GitHub org (merged, deduplicated) |
Execute these phases sequentially. Do not skip phases.
Parse $ARGUMENTS by classifying each argument:
@ → manager_email/ → add to projects listgithub_org (validate below)At least one of manager_email or github_org must be present, plus at least one project.
Validate candidate github_org (if present):
gh api orgs/{github_org} --jq '.login'
If 404, this is not a valid org — treat it as a bare project name and attempt repo search instead.
If any project lacks an owner/ prefix, resolve it:
gh search repos "{project_name}" --limit 5 --json fullName,description,stargazersCount
Select the most likely match and confirm with the user.
Determine the mode and print a summary:
manager_email only → LDAP only modegithub_org only → GitHub org only modePrint: Mode: {mode} | Manager: {email or "none"} | Org: {org or "none"} | Projects: {list}
Run prerequisite checks:
GitHub CLI authentication (always required):
gh auth status
If not authenticated, stop and tell the user to run gh auth login.
Kerberos ticket (only if manager_email is present):
klist
If no valid TGT, stop and tell the user to run kinit.
LDAP connectivity (only if manager_email is present):
ldapsearch -LLL -Y GSSAPI -H ldap://ldap.corp.redhat.com -b ou=users,dc=redhat,dc=com '(mail=MANAGER_EMAIL)' uid cn 2>&1 | head -5
If this fails, warn the user. Refer to references/LDAP-GUIDE.md for the fallback strategy.
Validate each project exists:
gh repo view OWNER/REPO --json name,owner,url
If a project is not found, remove it from the list and warn the user.
Create output directory:
mkdir -p reports/
Conditional: Skip this phase entirely if no manager_email was provided. When skipped, create the initial roster JSON:
mkdir -p reports/tmp
Then use the Write tool to save an empty roster to reports/tmp/employee-roster.json:
{
"generated_at": "YYYY-MM-DDTHH:MM:SSZ",
"manager": null,
"roster_source": "github-org",
"total_employees": 0,
"resolved_count": 0,
"resolution_coverage_pct": 0.0,
"employees": []
}
Then skip directly to Phase 2.5.
When manager_email is present, proceed with LDAP enumeration:
Refer to references/LDAP-GUIDE.md for detailed LDAP query patterns and attribute documentation.
All LDAP queries MUST use GSSAPI authentication (-Y GSSAPI). Never use simple auth (-x).
Find the manager's LDAP entry:
ldapsearch -LLL -Y GSSAPI -H ldap://ldap.corp.redhat.com \
-b ou=users,dc=redhat,dc=com \
'(mail=MANAGER_EMAIL)' \
uid cn mail title rhatSocialURL
Record the manager's uid.
Discover available GitHub-related attributes: Run a broad attribute query on the manager's entry to discover any GitHub-specific fields:
ldapsearch -LLL -Y GSSAPI -H ldap://ldap.corp.redhat.com \
-b ou=users,dc=redhat,dc=com \
'(mail=MANAGER_EMAIL)' '*' '+' 2>/dev/null | grep -i -E 'github|social|git'
Note any additional attributes found beyond rhatSocialURL.
Recursively find all reports (BFS traversal):
Initialize a queue with the manager's uid. For each uid in the queue:
ldapsearch -LLL -Y GSSAPI -H ldap://ldap.corp.redhat.com \
-b ou=users,dc=redhat,dc=com \
'(manager=uid=CURRENT_UID,ou=users,dc=redhat,dc=com)' \
uid cn mail title rhatSocialURL
uid — if an employee is already in the roster, skip (avoids duplicates from dotted-line reporting or circular references)depth field tracking the BFS level (manager = 0, direct reports = 1, etc.)uid to the queue for further traversalBuild the employee roster: For each employee, create an entry with:
name (from cn)uid (from uid)email (from mail)title (from title)github_username (parsed from rhatSocialURL or other discovered attribute, or null)github_resolution_method (ldap if resolved, null if not)Report roster statistics:
If the org exceeds 500 employees, warn the user that this is a very large scope and ask if they want to continue or narrow the search.
Write the employee roster to a JSON file for sub-agent consumption:
mkdir -p reports/tmp
Then use the Write tool to save the roster to reports/tmp/employee-roster.json with this schema:
{
"generated_at": "YYYY-MM-DDTHH:MM:SSZ",
"manager": {"name": "...", "uid": "...", "email": "..."},
"roster_source": "ldap",
"total_employees": 125,
"resolved_count": 40,
"resolution_coverage_pct": 32.0,
"employees": [
{
"name": "Jane Doe",
"uid": "jdoe",
"email": "jdoe@redhat.com",
"title": "Senior Software Engineer",
"github_username": "janedoe",
"github_resolution_method": "ldap",
"github_resolution_tier": 1,
"depth": 2,
"source": "ldap"
}
]
}
github_resolution_tier to 1 for LDAP-resolved usernames, null for unresolvedgithub_username to null for employees without LDAP resolutionroster_source to "ldap" (will become "both" if Phase 2.5 merges GitHub org members)source to "ldap" on each employee entryConditional: Skip this phase if no github_org was provided.
Run the GitHub org roster script to fetch org members and build (or merge into) the roster:
python3 {assets_dir}/github-org-roster.py \
--org {github_org} \
--output reports/tmp/employee-roster.json \
{--merge if Phase 2 already wrote the file (i.e., manager_email was present)}
Use --merge when both manager_email and github_org are present (LDAP + GitHub org mode). The script will:
gh api orgs/{org}/members --paginategh api users/{login}github_username (case-insensitive), mark matches as source: "both", append new members as source: "github-org", keep LDAP-only entries as source: "ldap"roster_source: "github-org", manager: nulltotal_employees, resolved_count, resolution_coverage_pctReport the roster size and source mode to the user after this phase completes.
Review the roster JSON (reports/tmp/employee-roster.json):
github_resolution_tier: 1)github_resolution_tier: 1) with github_resolution_method: "github-org"github_username: null) will be resolved by the centralized Username Resolution Agent in Phase 3.5Report the current resolution state to the user, adapted for the active mode:
Conditional: Skip this phase if all employees already have GitHub usernames resolved (e.g., GitHub-org-only mode where 100% have usernames). Check the roster: if zero employees have github_username: null, skip to Phase 4.
When active, launch a single dedicated sub-agent to resolve GitHub usernames for employees who lack resolved usernames. In combined mode, only LDAP-sourced employees without usernames need resolution. This runs once before KPI evaluation, so all KPI agents benefit from the same resolved roster.
Read the Username Resolution Agent prompt template from references/RESEARCH-PROMPTS.md.
Prepare the prompt by substituting:
{roster_path} with reports/tmp/employee-roster.json{project_list} with a comma-separated list of all target projects (e.g., kubeflow/kubeflow, kserve/kserve){workdir} with reports/tmp{assets_dir} with the absolute path to redhat-contribution-report/skills/redhat-contribution-report/assetsLaunch the sub-agent using Task with subagent_type: general-purpose and max_turns: 8.
Wait for this agent to complete before proceeding to Phase 4. The agent runs a batch Python script that:
@redhat.com emailsgh search commits --author-emailgh search users with strict acceptance criteriareports/tmp/employee-roster.json in place with resolutionsreports/tmp/username-resolutions.mdAfter the agent completes, report the updated resolution coverage to the user.
Read the 5 KPI prompt templates from references/RESEARCH-PROMPTS.md.
Read the scoring rubric from assets/scoring-rubric.json.
Create working directories for each project's intermediate files:
mkdir -p reports/tmp/{owner}-{repo}/
Run this for every project before dispatching sub-agents. These directories hold raw API output and checkpoint files.
Compute the evaluation window cutoff date (6 months ago from today):
cutoff_date=$(date -d '6 months ago' +%Y-%m-%d)
For each KPI prompt template, prepare the prompt by substituting:
{owner} and {repo} with the project's owner and repository name{workdir} with the working directory path: reports/tmp/{owner}-{repo}{cutoff_date} with the computed 6-month-ago date in YYYY-MM-DD format{roster_path} with reports/tmp/employee-roster.json{assets_dir} with the absolute path to redhat-contribution-report/skills/redhat-contribution-report/assetsDo NOT substitute {employee_roster} or embed the roster inline. Sub-agents access the roster file via {roster_path} inside python3 scripts. The roster is never loaded into agent conversation context.
Launch 5 Task sub-agents per project, ALL IN PARALLEL in a single message. Use subagent_type: general-purpose and max_turns: 8 for all KPIs. For N projects, this means 5N Task calls in a single message.
| Agent | KPI | Focus |
|---|---|---|
| KPI 1 | PR/Commit Contributions | PRs, commits, code contributions authored or co-authored by roster employees |
| KPI 2 | Release Management | Release managers who are roster employees |
| KPI 3 | Maintainer/Reviewer/Approver Roles | Roster employees in OWNERS, CODEOWNERS, MAINTAINERS, or similar governance files |
| KPI 4 | Roadmap Influence | Enhancement proposals, roadmap features, or design docs led by roster employees |
| KPI 5 | Leadership Roles | TAC, steering committee, advisory board, or other governance body positions held by roster employees |
All KPI agents use max_turns: 8. Heavy computation (workflow detection, PR verification, governance file scanning) is handled by standalone Python scripts in {assets_dir}, keeping agent turns minimal.
Each agent writes its results to a checkpoint file in {workdir}/ and returns only a 1-line status message. This keeps orchestrator context minimal.
Refer to references/DATA-SOURCES.md for the specific gh CLI commands each sub-agent should use.
Collect the output from all sub-agents. There are 5 sub-agents per project (5N total for N projects), each returning a 1-line status message. Detailed results are in checkpoint files.
Step 1: Read the updated roster from reports/tmp/employee-roster.json (updated by the Phase 3.5 Username Resolution Agent).
Step 2: Read the username resolution log from reports/tmp/username-resolutions.md.
Step 3: For each project, read the 5 KPI checkpoint files from reports/tmp/{owner}-{repo}/:
kpi1-pr-contributions.md — KPI 1 resultskpi2-release-management.md — KPI 2 resultskpi3-maintainership.md — KPI 3 resultskpi4-roadmap-influence.md — KPI 4 resultskpi5-leadership.md — KPI 5 resultsStep 4: Handle missing checkpoints. If a checkpoint file does not exist (agent failed or was rate-limited), mark that KPI as "Not evaluated" with score 1 and confidence "Not Found". Note which KPIs were missing in the Data Quality section.
Step 5: Build per-project Employee Contribution Maps using python3 to scan checkpoint files:
python3 -c "
import json, re, os
roster = json.load(open('reports/tmp/employee-roster.json'))
workdir = 'reports/tmp/{owner}-{repo}'
kpi_files = ['kpi1-pr-contributions.md','kpi2-release-management.md','kpi3-maintainership.md','kpi4-roadmap-influence.md','kpi5-leadership.md']
gh_users = {e['github_username'].lower(): e['name'] for e in roster['employees'] if e.get('github_username')}
for i, f in enumerate(kpi_files, 1):
path = os.path.join(workdir, f)
if os.path.exists(path):
content = open(path).read()
found = [u for u in gh_users if u in content.lower()]
for u in found:
print(f'{gh_users[u]} | @{u} | KPI {i}')
else:
print(f'KPI {i}: checkpoint missing')
"
For each project, check if a kpi1-metadata.json file exists in the working directory:
python3 -c "
import json, os, subprocess
workdir = 'reports/tmp/{owner}-{repo}'
meta_path = os.path.join(workdir, 'kpi1-metadata.json')
if not os.path.exists(meta_path):
print('No kpi1-metadata.json found — skipping spot-check')
exit(0)
metadata = json.load(open(meta_path))
print(f'Workflow type: {metadata[\"workflow_type\"]}')
print(f'RH verified total (metadata): {metadata[\"rh_verified_total\"]}')
# Read the checkpoint file to extract the reported RH PR count
checkpoint_path = os.path.join(workdir, 'kpi1-pr-contributions.md')
if os.path.exists(checkpoint_path):
content = open(checkpoint_path).read()
print(f'Checkpoint file exists: yes')
else:
print('WARNING: kpi1-pr-contributions.md checkpoint missing')
if metadata['workflow_type'] == 'non-standard':
print(f'Non-standard workflow detected — cross-validation active')
print(f' Merged: {metadata[\"rh_merged_count\"]}, Landed: {metadata[\"rh_landed_count\"]}')
print(f' Total verified: {metadata[\"rh_verified_total\"]} ({metadata[\"rh_pct\"]}%)')
"
If the workflow type is non-standard, spot-check 2-3 individual PRs from the top RH contributor by verifying their merge status:
gh pr view {pr_number} --repo {owner}/{repo} --json state,mergedAt,closedAt,number
If the checkpoint file's reported RH PR count diverges from metadata.rh_verified_total, flag it for manual review and add a note to the Data Quality section of the report.
assets/scoring-rubric.json against the sub-agent's own reported data. If a score appears inconsistent with the data (e.g., score of 4 but data shows < 10% PR contribution), adjust to match the rubric and note the correction.After collecting all results:
reports/tmp/employee-roster.json (resolution_coverage_pct field).Read the report template from references/REPORT-TEMPLATE.md.
Generate the final report by:
Computing today's date:
date +%Y-%m-%d
Assembling the report following the template structure:
Applying scores using the rubric from assets/scoring-rubric.json
Writing the report:
# File path: reports/YYYY-MM-DD-redhat-contribution-eval.md
Use the Write tool to save the report.
Run a final validation of the generated report against checkpoint files, the scoring rubric, and live GitHub data.
Read the Auditor Agent prompt template from references/RESEARCH-PROMPTS.md.
Prepare the prompt by substituting:
{report_path} with the report file path from Phase 6 (e.g., reports/YYYY-MM-DD-redhat-contribution-eval.md){roster_path} with reports/tmp/employee-roster.json{workdir} with reports/tmp{assets_dir} with the absolute path to redhat-contribution-report/skills/redhat-contribution-report/assets{projects} with a comma-separated list of all target projects (e.g., kubeflow/kubeflow,kserve/kserve)Launch a single Task sub-agent with subagent_type: general-purpose and max_turns: 10.
Wait for the agent to complete. Report the audit results to the user:
Clean up intermediate files:
rm -rf reports/tmp/
Inform the user of the report location and summarize key findings.
@redhat.com). All metrics marked reduced confidence. No org scoping possible. Only applies when manager_email is present.references/LDAP-GUIDE.md — LDAP connection, attributes, traversal algorithm, and fallback strategiesreferences/DATA-SOURCES.md — All gh CLI commands organized by KPI with parsing guidancereferences/REPORT-TEMPLATE.md — Complete markdown template for the output reportreferences/RESEARCH-PROMPTS.md — Sub-agent prompt template with variable substitution instructionsassets/scoring-rubric.json — Machine-readable scoring thresholds for all 5 KPIs (1-5 scale)