Skip to content
Open
146 changes: 144 additions & 2 deletions .github/skills/find-reviewable-pr/scripts/query-reviewable-prs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ param(
[int]$DocsLimit = 5,

[Parameter(Mandatory = $false)]
[ValidateSet("table", "json", "review")]
[ValidateSet("table", "json", "review", "markdown")]
[string]$OutputFormat = "review",

[Parameter(Mandatory = $false)]
Expand Down Expand Up @@ -1024,14 +1024,156 @@ function Format-Table-Output {
) -AutoSize
}

function Format-Markdown-Output {
$date = (Get-Date).ToString("yyyy-MM-dd")
$md = [System.Text.StringBuilder]::new()

[void]$md.AppendLine("# 📋 PR Review Queue — $date")
[void]$md.AppendLine("")

# Helper to render a PR table
$renderTable = {
param([array]$prs, [bool]$showMilestone = $false)
if ($showMilestone) {
[void]$md.AppendLine("| PR | Title | Author | Milestone | Platform | Age | Updated |")
[void]$md.AppendLine("|----|-------|--------|-----------|----------|-----|---------|")
} else {
[void]$md.AppendLine("| PR | Title | Author | Platform | Age | Updated |")
[void]$md.AppendLine("|----|-------|--------|----------|-----|---------|")
}
foreach ($pr in $prs) {
$title = if ($pr.Title.Length -gt 60) { $pr.Title.Substring(0, 57) + "..." } else { $pr.Title }
$link = "[#$($pr.Number)]($($pr.URL))"
if ($showMilestone) {
[void]$md.AppendLine("| $link | $title | @$($pr.Author) | $($pr.Milestone) | $($pr.Platform) | $($pr.Age)d | $($pr.Updated)d ago |")
} else {
[void]$md.AppendLine("| $link | $title | @$($pr.Author) | $($pr.Platform) | $($pr.Age)d | $($pr.Updated)d ago |")
}
}
[void]$md.AppendLine("")
}

# Helper: filter for display (same logic as Format-Review-Output)
$showCategory = {
param([string]$cat)
if ($Category -eq $cat) { return $true }
if ($Category -eq "all") { return $true }
if ($Category -eq "default" -and ($cat -eq "priority" -or $cat -eq "milestoned")) { return $true }
return $false
}
$defaultFilter = {
param($prList)
if ($Category -eq "default") {
$prList | Where-Object { $_.ReviewDecision -ne "CHANGES_REQUESTED" }
} else {
$prList
}
}

# Filter out stale and do-not-merge PRs
$excludeStale = {
param($prList)
$prList | Where-Object {
$labels = $_.Labels -split ', '
-not ($labels -contains 'stale' -or $labels -contains 'do-not-merge')
}
}

# 1. Priority (P/0)
if ($priorityPRs.Count -gt 0 -and (& $showCategory "priority")) {
$list = @(& $excludeStale (& $defaultFilter $priorityPRs))
if ($list.Count -gt 0) {
[void]$md.AppendLine("## 🔴 Immediate Action Required")
[void]$md.AppendLine("")
[void]$md.AppendLine("### P/0 Priority")
& $renderTable ($list | Select-Object -First $Limit)
}
}

# 2. Approved (not merged)
if ($approvedPRs.Count -gt 0 -and (& $showCategory "approved")) {
$list = if ($Category -eq "approved") { $approvedPRs } else { @($approvedPRs | Where-Object { -not $_.IsPriority }) }
$list = @(& $excludeStale $list)
if ($list.Count -gt 0) {
[void]$md.AppendLine("### ✅ Approved — Ready to Merge")
& $renderTable ($list | Select-Object -First $Limit)
}
}

# 3. Milestoned
if ($milestonedPRs.Count -gt 0 -and (& $showCategory "milestoned")) {
$list = if ($Category -eq "milestoned") { $milestonedPRs } else { @($milestonedPRs | Where-Object { -not $_.IsPriority -and -not $_.IsApproved -and -not $_.IsReadyToReview }) }
$list = @(& $excludeStale (& $defaultFilter $list))
if ($list.Count -gt 0) {
[void]$md.AppendLine("## 📅 Milestoned — Deadline-Driven")
& $renderTable ($list | Select-Object -First $Limit) $true
}
}

# 4. Partner PRs
if ($partnerPRs.Count -gt 0 -and (& $showCategory "partner")) {
$list = if ($Category -eq "partner") { $partnerPRs } else { @($partnerPRs | Where-Object { -not $_.IsPriority -and -not $_.IsApproved -and -not $_.IsReadyToReview -and $_.Milestone -eq "" }) }
$list = @(& $excludeStale $list)
if ($list.Count -gt 0) {
[void]$md.AppendLine("## 🤝 Partner PRs")
& $renderTable ($list | Select-Object -First 10)
}
}

# 5. Community PRs
if ($communityPRs.Count -gt 0 -and (& $showCategory "community")) {
$list = if ($Category -eq "community") { $communityPRs } else { @($communityPRs | Where-Object { -not $_.IsPriority -and -not $_.IsApproved -and -not $_.IsReadyToReview -and $_.Milestone -eq "" }) }
$list = @(& $excludeStale $list)
if ($list.Count -gt 0) {
[void]$md.AppendLine("## ✨ Community PRs")
& $renderTable ($list | Select-Object -First 10)
}
}

# 6. docs-maui PRs
if ((& $showCategory "docs-maui")) {
$hasDocs = $false
if ($docsMauiPriorityPRs.Count -gt 0) { $hasDocs = $true }
if ($docsMauiRecentPRs.Count -gt 0) { $hasDocs = $true }
if ($hasDocs) {
[void]$md.AppendLine("## 📖 docs-maui PRs")
if ($docsMauiPriorityPRs.Count -gt 0) {
& $renderTable ($docsMauiPriorityPRs | Select-Object -First $DocsLimit)
}
if ($docsMauiRecentPRs.Count -gt 0) {
& $renderTable ($docsMauiRecentPRs | Select-Object -First $DocsLimit)
}
}
}

# Queue Health
$totalP0 = @(& $excludeStale (& $defaultFilter $priorityPRs)).Count
$totalApproved = @(& $excludeStale $approvedPRs).Count
$oldest = $processedPRs | Sort-Object Age -Descending | Select-Object -First 1
$over30 = @($processedPRs | Where-Object { $_.Age -gt 30 }).Count

[void]$md.AppendLine("## 📊 Queue Health")
[void]$md.AppendLine("- **Total PRs needing review**: $($processedPRs.Count)")
[void]$md.AppendLine("- **P/0 PRs**: $totalP0 (target: 0)")
[void]$md.AppendLine("- **Approved but not merged**: $totalApproved")
if ($oldest) {
[void]$md.AppendLine("- **Oldest unreviewed PR**: [#$($oldest.Number)]($($oldest.URL)) ($($oldest.Age) days)")
}
[void]$md.AppendLine("- **PRs > 30 days old**: $over30")

# Output to stdout (not Write-Host) so it can be redirected to a file
$md.ToString()
}

# Generate output
switch ($OutputFormat) {
"review" { Format-Review-Output }
"json" { Format-Json-Output }
"table" { Format-Table-Output }
"markdown" { Format-Markdown-Output }
}

# Return processed PRs for pipeline usage (only when not in review mode)
if ($OutputFormat -ne "review") {
if ($OutputFormat -ne "review" -and $OutputFormat -ne "markdown") {
return $processedPRs
}
109 changes: 109 additions & 0 deletions .github/workflows/pr-review-queue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: PR Review Queue

on:
schedule:
- cron: "0 8 * * 1-5" # Weekdays at 8:00 UTC
workflow_dispatch:
pull_request:
types: [opened, synchronize]

permissions:
contents: read
issues: write
pull-requests: read

concurrency:
group: pr-review-queue
cancel-in-progress: true

jobs:
generate-report:
runs-on: ubuntu-latest
# On pull_request, only validate the script runs — don't create issues
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4

- name: Generate PR review queue
id: generate
env:
GH_TOKEN: ${{ github.token }}
run: |
pwsh .github/skills/find-reviewable-pr/scripts/query-reviewable-prs.ps1 \
-Category all \
-OutputFormat markdown \
> pr-review-queue-raw.md 2>pr-query-errors.txt

if [ $? -ne 0 ]; then
echo "::error::Query script failed"
cat pr-query-errors.txt
exit 1
fi

# Strip Write-Host progress lines — keep only markdown (starts at "# ")
sed -n '/^# 📋/,$p' pr-review-queue-raw.md > pr-review-queue-body.md

if [ ! -s pr-review-queue-body.md ]; then
echo "::error::Query produced empty output"
cat pr-query-errors.txt
exit 1
fi

echo "Generated report:"
head -20 pr-review-queue-body.md

- name: Close previous queue issues
env:
GH_TOKEN: ${{ github.token }}
run: |
gh issue list \
--repo ${{ github.repository }} \
--search '"[PR Review Queue]" in:title' \
--state open \
--json number \
--jq '.[].number' | \
xargs -I{} gh issue close {} --repo ${{ github.repository }} --reason "not planned" 2>/dev/null || true

- name: Create queue issue
env:
GH_TOKEN: ${{ github.token }}
run: |
gh issue create \
--repo ${{ github.repository }} \
--title "[PR Review Queue] $(date -u '+%Y-%m-%d')" \
--body-file pr-review-queue-body.md \
--label "report" \
--label "s/triaged"

# Dry-run on PRs: validate the script works without creating issues
validate:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4

- name: Validate PR review queue script
env:
GH_TOKEN: ${{ github.token }}
run: |
pwsh .github/skills/find-reviewable-pr/scripts/query-reviewable-prs.ps1 \
-Category all \
-OutputFormat markdown \
> pr-review-queue-raw.md 2>pr-query-errors.txt

if [ $? -ne 0 ]; then
echo "::error::Query script failed"
cat pr-query-errors.txt
exit 1
fi

sed -n '/^# 📋/,$p' pr-review-queue-raw.md > pr-review-queue-body.md

if [ ! -s pr-review-queue-body.md ]; then
echo "::error::Query produced empty output"
cat pr-query-errors.txt
exit 1
fi

echo "✅ Validation passed — report preview:"
cat pr-review-queue-body.md
Loading