Adding the workflows after I broke #802

This commit is contained in:
Aaron Powell
2026-02-26 10:54:16 +11:00
parent 85a538b92b
commit 1f2afa4336
5 changed files with 603 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
---
name: 'OSPO Organization Health Report'
description: 'Comprehensive weekly health report for a GitHub organization. Surfaces stale issues/PRs, merge time analysis, contributor leaderboards, and actionable items needing human attention.'
labels: ['ospo', 'reporting', 'org-health']
on:
schedule:
- cron: "0 10 * * 1"
workflow_dispatch:
inputs:
organization:
description: "GitHub organization to report on"
type: string
required: true
permissions:
contents: read
issues: read
pull-requests: read
actions: read
engine: copilot
tools:
github:
toolsets:
- repos
- issues
- pull_requests
- orgs
bash: true
safe-outputs:
create-issue:
max: 1
title-prefix: "[Org Health] "
timeout-minutes: 60
network:
allowed:
- defaults
- python
---
You are an expert GitHub organization analyst. Your job is to produce a
comprehensive weekly health report for your GitHub organization
(provided via workflow input).
## Primary Goal
**Surface issues and PRs that need human attention**, celebrate wins, and
provide actionable metrics so maintainers can prioritize their time.
---
## Step 1 — Determine the Organization
```
ORG = inputs.organization OR "my-org"
PERIOD_DAYS = 30
SINCE = date 30 days ago (ISO 8601)
STALE_ISSUE_DAYS = 60
STALE_PR_DAYS = 30
60_DAYS_AGO = date 60 days ago (ISO 8601)
30_DAYS_AGO = date 30 days ago (ISO 8601, same as SINCE)
```
## Step 2 — Gather Organization-Wide Aggregates (Search API)
Use GitHub search APIs for fast org-wide counts. These are efficient and
avoid per-repo iteration for basic aggregates.
Collect the following using search queries:
| Metric | Search Query |
|--------|-------------|
| Total open issues | `org:<ORG> is:issue is:open` |
| Total open PRs | `org:<ORG> is:pr is:open` |
| Issues opened (last 30d) | `org:<ORG> is:issue created:>={SINCE}` |
| Issues closed (last 30d) | `org:<ORG> is:issue is:closed closed:>={SINCE}` |
| PRs opened (last 30d) | `org:<ORG> is:pr created:>={SINCE}` |
| PRs merged (last 30d) | `org:<ORG> is:pr is:merged merged:>={SINCE}` |
| PRs closed unmerged (last 30d) | `org:<ORG> is:pr is:closed is:unmerged closed:>={SINCE}` |
| Stale issues (60+ days) | `org:<ORG> is:issue is:open updated:<={60_DAYS_AGO}` |
| Stale PRs (30+ days) | `org:<ORG> is:pr is:open updated:<={30_DAYS_AGO}` |
**Performance tip:** Add 12 second delays between search API calls to
stay well within rate limits.
## Step 3 — Stale Issues & PRs (Heat Scores)
For stale issues and stale PRs found above, retrieve the top results and
sort them by **heat score** (comment count). The heat score helps
maintainers prioritize: a stale issue with many comments signals community
interest that is going unaddressed.
- **Stale issues**: Retrieve up to 50, sort by `comments` descending,
keep top 10. For each, record: repo, number, title, days since last
update, comment count (heat score), author, labels.
- **Stale PRs**: Same approach — retrieve up to 50, sort by `comments`
descending, keep top 10.
## Step 4 — PR Merge Time Analysis
From the PRs merged in the last 30 days (Step 2), retrieve a sample of
recently merged PRs (up to 100). For each, calculate:
```
merge_time = merged_at - created_at (in hours)
```
Then compute percentiles:
- **p50** (median merge time)
- **p75**
- **p95**
Use bash with Python for percentile calculations:
```bash
python3 -c "
import json, sys
times = json.loads(sys.stdin.read())
times.sort()
n = len(times)
if n == 0:
print('No data')
else:
p50 = times[int(n * 0.50)]
p75 = times[int(n * 0.75)]
p95 = times[int(n * 0.95)] if n >= 20 else times[-1]
print(f'p50={p50:.1f}h, p75={p75:.1f}h, p95={p95:.1f}h')
"
```
## Step 5 — First Response Time
For issues and PRs opened in the last 30 days, sample up to 50 of each.
For each item, find the first comment (excluding the author). Calculate:
```
first_response_time = first_comment.created_at - item.created_at (in hours)
```
Report median first response time for issues and PRs separately.
## Step 6 — Repository Activity & Contributor Leaderboard
### Top 10 Active Repos
List all non-archived repos in the org. For each, count pushes / commits /
issues+PRs opened in the last 30 days. Sort by total activity, keep top 10.
### Contributor Leaderboard
From the top 10 active repos, aggregate commit authors over the last 30
days. Rank by commit count, keep top 10. Award:
- 🥇 for #1
- 🥈 for #2
- 🥉 for #3
### Inactive Repos
Repos with 0 pushes, 0 issues, 0 PRs in the last 30 days. List them
(name + last push date) so the org can decide whether to archive.
## Step 7 — Health Alerts & Trends
Compute velocity indicators and assign status:
| Indicator | 🟢 Green | 🟡 Yellow | 🔴 Red |
|-----------|----------|-----------|--------|
| Issue close rate | closed ≥ opened | closed ≥ 70% opened | closed < 70% opened |
| PR merge rate | merged ≥ opened | merged ≥ 60% opened | merged < 60% opened |
| Median merge time | < 24h | 2472h | > 72h |
| Median first response | < 24h | 2472h | > 72h |
| Stale issue count | < 10 | 1050 | > 50 |
| Stale PR count | < 5 | 520 | > 20 |
## Step 8 — Wins & Shoutouts
Celebrate positive signals:
- PRs merged with fast turnaround (< 4 hours)
- Issues closed quickly (< 24 hours from open to close)
- Top contributors (from leaderboard)
- Repos with zero stale items
## Step 9 — Compose the Report
Create a single issue in the org's `.github` repository (or the most
appropriate central repo) with the title:
```
[Org Health] Weekly Report — <DATE>
```
The issue body should include these sections in order:
1. **Header** — org name, period, generation date
2. **🚨 Health Alerts** — table of indicators with 🟢/🟡/🔴 status and values
3. **🏆 Wins & Shoutouts** — fast merges, quick closes, top contributors
4. **📋 Stale Issues** — top 10 by heat score (repo, issue, days stale, comment count, labels)
5. **📋 Stale PRs** — top 10 by heat score (repo, PR, days stale, comment count, author)
6. **⏱️ PR Merge Time** — p50, p75, p95 percentiles
7. **⚡ First Response Time** — median for issues and PRs
8. **📊 Top 10 Active Repos** — sorted by total activity (issues + PRs + commits)
9. **👥 Contributor Leaderboard** — top 10 by commits with 🥇🥈🥉
10. **😴 Inactive Repos** — repos with 0 activity in 30 days
Use markdown tables for all data sections.
## Important Notes
- **Update the organization name** in the frontmatter before use.
- If any API call fails, note it in the report and continue with available
data. Do not let a single failure block the entire report.
- Keep the issue body under 65,000 characters (GitHub issue body limit).
- All times should be reported in hours. Convert to days only if > 72 hours.
- Use the `safe-outputs` constraint: only create 1 issue, with title
prefixed `[Org Health] `.