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

@@ -34,3 +34,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agentic-workflows) for guideline
| Name | Description | Triggers | | Name | Description | Triggers |
| ---- | ----------- | -------- | | ---- | ----------- | -------- |
| [Daily Issues Report](../workflows/daily-issues-report.md) | Generates a daily summary of open issues and recent activity as a GitHub issue | schedule | | [Daily Issues Report](../workflows/daily-issues-report.md) | Generates a daily summary of open issues and recent activity as a GitHub issue | schedule |
| [OSPO Contributors Report](../workflows/ospo-contributors-report.md) | Monthly contributor activity metrics across an organization's repositories. | schedule, workflow_dispatch |
| [OSPO Organization Health Report](../workflows/ospo-org-health.md) | Comprehensive weekly health report for a GitHub organization. Surfaces stale issues/PRs, merge time analysis, contributor leaderboards, and actionable items needing human attention. | schedule, workflow_dispatch |
| [OSPO Stale Repository Report](../workflows/ospo-stale-repos.md) | Identifies inactive repositories in your organization and generates an archival recommendation report. | schedule, workflow_dispatch |
| [OSS Release Compliance Checker](../workflows/ospo-release-compliance-checker.md) | Analyzes a target repository against open source release requirements and posts a detailed compliance report as an issue comment. | issues, workflow_dispatch |

View File

@@ -0,0 +1,140 @@
---
name: 'OSPO Contributors Report'
description: 'Monthly contributor activity metrics across an organization''s repositories.'
labels: ['ospo', 'reporting', 'contributors']
on:
schedule:
- cron: "3 2 1 * *"
workflow_dispatch:
inputs:
organization:
description: "GitHub organization to analyze (e.g. github)"
required: false
type: string
repositories:
description: "Comma-separated list of repos to analyze (e.g. owner/repo1,owner/repo2)"
required: false
type: string
start_date:
description: "Start date for the report period (YYYY-MM-DD)"
required: false
type: string
end_date:
description: "End date for the report period (YYYY-MM-DD)"
required: false
type: string
sponsor_info:
description: "Include GitHub Sponsors information for contributors"
required: false
type: boolean
default: false
permissions:
contents: read
issues: read
pull-requests: read
engine: copilot
tools:
github:
toolsets:
- repos
- issues
- pull_requests
- orgs
- users
bash: true
safe-outputs:
create-issue:
max: 1
title-prefix: "[Contributors Report] "
timeout-minutes: 60
---
# Contributors Report
Generate a contributors report for the specified organization or repositories.
## Step 1: Validate Configuration
Check the workflow inputs. Either `organization` or `repositories` must be provided.
- If **both** are empty and this is a **scheduled run**, default to analyzing all public repositories in the organization that owns the current repository. Determine the org from the `GITHUB_REPOSITORY` environment variable (the part before the `/`).
- If **both** are empty and this is a **manual dispatch**, fail with a clear error message: "You must provide either an organization or a comma-separated list of repositories."
- If **both** are provided, prefer `repositories` and ignore `organization`.
## Step 2: Determine Date Range
- If `start_date` and `end_date` are provided, use them.
- Otherwise, default to the **previous calendar month**. For example, if today is 2025-03-15, the range is 2025-02-01 to 2025-02-28.
- Use bash to compute the dates if needed. Store them as `START_DATE` and `END_DATE`.
## Step 3: Enumerate Repositories
- If `repositories` input was provided, split the comma-separated string into a list. Each entry should be in `owner/repo` format.
- If `organization` input was provided (or defaulted from Step 1), list all **public, non-archived, non-fork** repositories in the organization using the GitHub API. Collect their `owner/repo` identifiers.
## Step 4: Collect Contributors from Commit History
For each repository in scope:
1. Use the GitHub API to list commits between `START_DATE` and `END_DATE` (use the `since` and `until` parameters on the commits endpoint).
2. For each commit, extract the **author login** (from `author.login` on the commit object).
3. **Exclude bot accounts**: skip any contributor whose username contains `[bot]` or whose `type` field is `"Bot"`.
4. Track per-contributor:
- Total number of commits across all repos.
- The set of repos they contributed to.
Use bash to aggregate and deduplicate the contributor data across all repositories.
## Step 5: Determine New vs Returning Contributors
For each contributor found in Step 4, check whether they have **any commits before `START_DATE`** in any of the in-scope repositories.
- If a contributor has **no commits before `START_DATE`**, mark them as a **New Contributor**.
- Otherwise, mark them as a **Returning Contributor**.
## Step 6: Collect Sponsor Information (Optional)
If the `sponsor_info` input is `true`:
1. For each contributor, check whether they have a GitHub Sponsors profile by querying the user's profile via the GitHub API.
2. If the user has sponsorship enabled, record their sponsor URL as `https://github.com/sponsors/<username>`.
3. If not, leave the sponsor field empty.
## Step 7: Generate Markdown Report
Build a markdown report with the following structure:
### Summary Table
| Metric | Value |
|---|---|
| Total Contributors | count |
| Total Contributions (Commits) | count |
| New Contributors | count |
| Returning Contributors | count |
| % New Contributors | percentage |
### Contributors Detail Table
Sort contributors by commit count descending.
| # | Username | Contribution Count | New Contributor | Sponsor URL | Commits |
|---|---|---|---|---|---|
| 1 | @username | 42 | Yes | [Sponsor](url) | [View](commits-url) |
- The **Username** column should link to the contributor's GitHub profile.
- The **Sponsor URL** column should show "N/A" if `sponsor_info` is false or the user has no Sponsors page.
- The **Commits** column should link to a filtered commits view.
## Step 8: Create Issue with Report
Create an issue in the **current repository** with:
- **Title:** `[Contributors Report] <ORG_OR_REPO_SCOPE> — START_DATE to END_DATE`
- **Body:** The full markdown report from Step 7.
- **Labels:** Add the label `contributors-report` if it exists; do not fail if it does not.

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] `.

View File

@@ -0,0 +1,124 @@
---
name: 'OSS Release Compliance Checker'
description: 'Analyzes a target repository against open source release requirements and posts a detailed compliance report as an issue comment.'
labels: ['ospo', 'compliance', 'release']
on:
issues:
types: [opened, labeled]
workflow_dispatch:
permissions:
contents: read
issues: read
pull-requests: read
actions: read
engine: copilot
tools:
github:
toolsets:
- repos
- issues
bash: true
safe-outputs:
add-comment:
max: 1
timeout-minutes: 20
---
You are an open source release compliance checker. Your job is to analyze a
repository that has been proposed for open source release and post a thorough,
constructive compliance report as a comment on the triggering issue.
## 1. Trigger Guard
First, determine whether this workflow should proceed:
- If the event is `workflow_dispatch`, proceed.
- If the event is `issues` with type `opened`, proceed.
- If the event is `issues` with type `labeled`, only proceed if the label that
was just added is **`ospo-release-check`**.
- Otherwise, stop and do nothing.
## 2. Extract Target Repository
Read the body of the triggering issue. Look for the repository that is being
proposed for release. It may appear as:
- A full GitHub URL such as `https://github.com/org/repo-name`
- An `owner/repo` shorthand such as `org/repo-name`
Extract the **owner** and **repo name**. If you cannot find a repository
reference, post a comment asking the issue author to include one and stop.
## 3. File Compliance Check
For the target repository, check whether each of the following files exists at
the repository root (or in `.github/` where conventional). For each file that
exists, also assess whether it has meaningful content.
| File | What to look for |
|------|-----------------|
| `LICENSE` | Must be present. Contents must match the license declared in the repo metadata. |
| `README.md` | Must be present and substantial (>100 lines recommended). Should contain sections for usage, install, and contributing. |
| `CODEOWNERS` | Must list at least one maintainer or team. |
| `CONTRIBUTING.md` | Must describe how to contribute (issues, PRs, CLA/DCO, code style). |
| `SUPPORT.md` | Must explain how users can get help. |
| `CODE_OF_CONDUCT.md` | Must adopt a recognized code of conduct. |
| `SECURITY.md` | Must describe the security vulnerability disclosure process. |
## 4. Security Configuration Check
Using the GitHub API, check the following security settings on the target
repository:
- **Secret scanning** — Is secret scanning enabled?
- **Dependabot** — Are Dependabot alerts and/or security updates enabled?
- **Code scanning (CodeQL)** — Are any code scanning analyses present?
- **Branch protection** — Is the default branch protected? Are required reviews,
status checks, or signed commits configured?
Handle `404` or `403` responses gracefully — they typically mean the feature is
not enabled or you lack permission to check it.
## 5. License & Legal Analysis
- Compare the contents of the `LICENSE` file against the license declared in
the repository metadata (`license.spdx_id` from the repo API response).
Flag any mismatch.
- Look for dependency manifests (`package.json`, `requirements.txt`, `go.mod`,
`Cargo.toml`, `pom.xml`, `Gemfile`, `*.csproj`, etc.) in the repository.
- For each manifest found, attempt to identify declared dependency licenses.
Specifically flag any **GPL**, **AGPL**, **LGPL**, or other strong-copyleft
licenses that would require legal review before an open source release.
## 6. Risk Assessment
Based on your findings, assign a risk level (**Low**, **Medium**, or **High**)
to each of the following categories:
| Category | Low 🟢 | Medium 🟡 | High 🔴 |
|----------|--------|-----------|---------|
| **Business Risk** | No secrets, no proprietary code patterns | Some internal references found | Secrets detected, proprietary code |
| **Legal Risk** | Permissive license, no copyleft deps | Minor license inconsistencies | GPL/AGPL deps, license mismatch |
| **Open Source Risk** | All files present, active maintainers | Some files missing or thin | No README, no CODEOWNERS |
## 7. Generate Compliance Report
Post **one** comment on the triggering issue with these sections:
1. **Header** — repo name, timestamp, overall status (PASS ✅ / NEEDS WORK ⚠️ / BLOCKED 🚫)
2. **📄 File Compliance** — table of 7 files with ✅/❌ status and notes
3. **🔒 Security Configuration** — table of 4 settings with status
4. **⚖️ License Analysis** — declared license, LICENSE file match, copyleft flags
5. **📊 Risk Assessment** — Business/Legal/Open Source risk levels (🟢/🟡/🔴) with details
6. **📋 Recommendations** — prioritized as Must Fix (blocking), Should Address, Nice to Have
### Tone Guidelines
- Be **constructive** — help teams succeed, don't gatekeep.
- Explain *why* missing items matter and link to guidance.
- Celebrate what the team has already done well.

View File

@@ -0,0 +1,119 @@
---
name: 'OSPO Stale Repository Report'
description: 'Identifies inactive repositories in your organization and generates an archival recommendation report.'
labels: ['ospo', 'maintenance', 'stale-repos']
on:
schedule:
- cron: "3 2 1 * *"
workflow_dispatch:
inputs:
organization:
description: "GitHub organization to scan"
required: true
type: string
default: "my-org"
inactive_days:
description: "Number of days of inactivity before a repo is considered stale"
required: false
type: number
default: 365
exempt_repos:
description: "Comma-separated list of repos to exempt from the report"
required: false
type: string
default: ""
exempt_topics:
description: "Comma-separated list of topics — repos with any of these topics are exempt"
required: false
type: string
default: ""
activity_method:
description: "Method to determine last activity"
required: false
type: choice
options:
- pushed
- default_branch_updated
default: pushed
permissions:
contents: read
issues: read
engine: copilot
tools:
github:
toolsets:
- repos
- issues
bash: true
safe-outputs:
create-issue:
max: 1
title-prefix: "[Stale Repos] "
labels:
- stale-repos
timeout-minutes: 30
---
You are an assistant that audits GitHub repositories for staleness.
## Inputs
| Input | Default |
|---|---|
| `organization` | `my-org` |
| `inactive_days` | `365` |
| `exempt_repos` | _(none)_ |
| `exempt_topics` | _(none)_ |
| `activity_method` | `pushed` |
Use the workflow dispatch inputs if provided; otherwise fall back to the defaults above.
## Instructions
### 1. Enumerate repositories
List **all** repositories in the `organization`. Exclude any repo that is:
- **Archived** — skip it entirely.
- **Listed in `exempt_repos`** — compare repo names (case-insensitive) against the comma-separated list.
- **Tagged with an exempt topic** — if the repo has any topic that appears in the comma-separated `exempt_topics` list, skip it.
### 2. Determine last activity date
For each remaining repo, determine the **last activity date** based on `activity_method`:
- **`pushed`** — use the repository's `pushed_at` timestamp (this is the default and most efficient method).
- **`default_branch_updated`** — fetch the most recent commit on the repo's default branch and use that commit's `committer.date`.
### 3. Identify stale repos
Calculate the number of days between the last activity date and **today**. If the number of days exceeds `inactive_days`, mark the repo as **stale**.
### 4. Generate report
Build a **Markdown report** with a summary and a table:
> **Stale Repository Report — \<date\>**
> Found **N** repositories with no activity in the last **inactive_days** days.
| Repository | Days Inactive | Last Push Date | Visibility |
|---|---|---|---|
| [owner/repo](https://github.com/owner/repo) | 420 | 2024-01-15 | public |
Sort the table by **Days Inactive** descending (most stale first).
If there are **no stale repos**, still create the issue but note that all repositories are active.
### 5. Create or update issue
Search for an existing **open** issue in the `organization/.github` repo (or the repo this workflow runs in) with the label `stale-repos` and a title starting with `[Stale Repos]`.
- If an **existing open issue** is found, **update its body** with the new report.
- If **no open issue** exists, **create a new issue** with:
- Title: `[Stale Repos] Inactive Repository Report — <date>`
- Label: `stale-repos`
- Body: the full Markdown report from step 4.