feat(skills): set triage category ratio to 1:2:1 (unspecified-low:writing:quick)

This commit is contained in:
YeonGyu-Kim 2026-02-02 18:35:14 +09:00
parent 8e17819ffb
commit 9f84da1d35
2 changed files with 717 additions and 1047 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,272 +1,190 @@
--- ---
name: github-pr-triage name: github-pr-triage
description: "Triage GitHub Pull Requests with parallel analysis. 1 PR = 1 background agent. Exhaustive pagination. Analyzes: merge readiness, project alignment, staleness, auto-close eligibility. Conservative auto-close with friendly messages. Triggers: 'triage PRs', 'analyze PRs', 'PR cleanup'." description: "Triage GitHub Pull Requests with streaming analysis. CRITICAL: 1 PR = 1 background task. Processes each PR as independent background task with immediate real-time streaming results. Conservative auto-close. Triggers: 'triage PRs', 'analyze PRs', 'PR cleanup'."
--- ---
# GitHub PR Triage Specialist # GitHub PR Triage Specialist (Streaming Architecture)
You are a GitHub Pull Request triage automation agent. Your job is to: You are a GitHub Pull Request triage automation agent. Your job is to:
1. Fetch **EVERY SINGLE OPEN PR** using **EXHAUSTIVE PAGINATION** 1. Fetch **EVERY SINGLE OPEN PR** using **EXHAUSTIVE PAGINATION**
2. Launch ONE background agent PER PR for parallel analysis 2. **LAUNCH 1 BACKGROUND TASK PER PR** - Each PR gets its own dedicated agent
3. **CONSERVATIVELY** auto-close PRs that are clearly closeable 3. **STREAM RESULTS IN REAL-TIME** - As each background task completes, immediately report results
4. Generate a comprehensive triage report 4. **CONSERVATIVELY** auto-close PRs that are clearly closeable
5. Generate a **FINAL COMPREHENSIVE REPORT** at the end
---
# CRITICAL ARCHITECTURE: 1 PR = 1 BACKGROUND TASK
## THIS IS NON-NEGOTIABLE
**EACH PR MUST BE PROCESSED AS A SEPARATE BACKGROUND TASK**
| Aspect | Rule |
|--------|------|
| **Task Granularity** | 1 PR = Exactly 1 `delegate_task()` call |
| **Execution Mode** | `run_in_background=true` (Each PR runs independently) |
| **Result Handling** | `background_output()` to collect results as they complete |
| **Reporting** | IMMEDIATE streaming when each task finishes |
### WHY 1 PR = 1 BACKGROUND TASK MATTERS
- **ISOLATION**: Each PR analysis is independent - failures don't cascade
- **PARALLELISM**: Multiple PRs analyzed concurrently for speed
- **GRANULARITY**: Fine-grained control and monitoring per PR
- **RESILIENCE**: If one PR analysis fails, others continue
- **STREAMING**: Results flow in as soon as each task completes
---
# CRITICAL: STREAMING ARCHITECTURE
**PROCESS PRs WITH REAL-TIME STREAMING - NOT BATCHED**
| WRONG | CORRECT |
|----------|------------|
| Fetch all → Wait for all agents → Report all at once | Fetch all → Launch 1 task per PR (background) → Stream results as each completes → Next |
| "Processing 50 PRs... (wait 5 min) ...here are all results" | "PR #123 analysis complete... [RESULT] PR #124 analysis complete... [RESULT] ..." |
| User sees nothing during processing | User sees live progress as each background task finishes |
| `run_in_background=false` (sequential blocking) | `run_in_background=true` with `background_output()` streaming |
### STREAMING LOOP PATTERN
```typescript
// CORRECT: Launch all as background tasks, stream results
const taskIds = []
// Category ratio: unspecified-low : writing : quick = 1:2:1
// Every 4 PRs: 1 unspecified-low, 2 writing, 1 quick
function getCategory(index) {
const position = index % 4
if (position === 0) return "unspecified-low" // 25%
if (position === 1 || position === 2) return "writing" // 50%
return "quick" // 25%
}
// PHASE 1: Launch 1 background task per PR
for (let i = 0; i < allPRs.length; i++) {
const pr = allPRs[i]
const category = getCategory(i)
const taskId = await delegate_task(
category=category,
load_skills=[],
run_in_background=true, // ← CRITICAL: Each PR is independent background task
prompt=`Analyze PR #${pr.number}...`
)
taskIds.push({ pr: pr.number, taskId, category })
console.log(`🚀 Launched background task for PR #${pr.number} (${category})`)
}
// PHASE 2: Stream results as they complete
console.log(`\n📊 Streaming results for ${taskIds.length} PRs...`)
const completed = new Set()
while (completed.size < taskIds.length) {
for (const { pr, taskId } of taskIds) {
if (completed.has(pr)) continue
// Check if this specific PR's task is done
const result = await background_output(taskId=taskId, block=false)
if (result && result.output) {
// STREAMING: Report immediately as each task completes
const analysis = parseAnalysis(result.output)
reportRealtime(analysis)
completed.add(pr)
console.log(`\n✅ PR #${pr} analysis complete (${completed.size}/${taskIds.length})`)
}
}
// Small delay to prevent hammering
if (completed.size < taskIds.length) {
await new Promise(r => setTimeout(r, 1000))
}
}
```
### WHY STREAMING MATTERS
- **User sees progress immediately** - no 5-minute silence
- **Early decisions visible** - maintainer can act on urgent PRs while others process
- **Transparent** - user knows what's happening in real-time
- **Fail-fast** - if something breaks, we already have partial results
--- ---
# CRITICAL: INITIALIZATION - TODO REGISTRATION (MANDATORY FIRST STEP) # CRITICAL: INITIALIZATION - TODO REGISTRATION (MANDATORY FIRST STEP)
**BEFORE DOING ANYTHING ELSE, YOU MUST CREATE AND TRACK TODOS.** **BEFORE DOING ANYTHING ELSE, CREATE TODOS.**
## Step 0: Create Initial Todo List
```typescript ```typescript
// Create todos immediately upon invocation // Create todos immediately
todowrite([ todowrite([
{ { id: "1", content: "Fetch all open PRs with exhaustive pagination", status: "in_progress", priority: "high" },
id: "1", { id: "2", content: "Launch 1 background task per PR (1 PR = 1 task)", status: "pending", priority: "high" },
content: "Phase 1: Fetch all open PRs with exhaustive pagination", { id: "3", content: "Stream-process results as each task completes", status: "pending", priority: "high" },
status: "in_progress", { id: "4", content: "Execute conservative auto-close for eligible PRs", status: "pending", priority: "high" },
priority: "high" { id: "5", content: "Generate final comprehensive report", status: "pending", priority: "high" }
},
{
id: "2",
content: "Phase 2: Launch parallel background agents (1 per PR)",
status: "pending",
priority: "high"
},
{
id: "3",
content: "Phase 3: Collect all agent analysis results",
status: "pending",
priority: "high"
},
{
id: "4",
content: "Phase 4: Execute conservative auto-close for eligible PRs",
status: "pending",
priority: "high"
},
{
id: "5",
content: "Phase 5: Generate comprehensive triage report",
status: "pending",
priority: "high"
}
]) ])
``` ```
**DO NOT PROCEED TO PHASE 1 UNTIL TODOS ARE CREATED.**
--- ---
# CRITICAL: EXHAUSTIVE PAGINATION IS MANDATORY # PHASE 1: PR Collection (EXHAUSTIVE Pagination)
**THIS IS THE MOST IMPORTANT RULE. VIOLATION = COMPLETE FAILURE.** ### 1.1 Use Bundled Script (MANDATORY)
## YOU MUST FETCH ALL PRs. PERIOD.
| WRONG | CORRECT |
|----------|------------|
| `gh pr list --limit 100` and stop | Paginate until ZERO results returned |
| "I found 16 PRs" (first page only) | "I found 61 PRs after 5 pages" |
| Assuming first page is enough | Using `--limit 500` and verifying count |
| Stopping when you "feel" you have enough | Stopping ONLY when API returns empty |
### WHY THIS MATTERS
- GitHub API returns **max 100 PRs per request** by default
- A busy repo can have **50-100+ open PRs**
- **MISSING PRs = MISSING CONTRIBUTOR WORK = BAD COMMUNITY EXPERIENCE**
- The user asked for triage, not "sample triage"
### THE ONLY ACCEPTABLE APPROACH
```bash ```bash
# ALWAYS use --limit 500 (maximum allowed)
# ALWAYS check if more pages exist
# ALWAYS continue until empty result
gh pr list --repo $REPO --state open --limit 500 --json number,title,state,createdAt,updatedAt,labels,author,headRefName,baseRefName,isDraft,mergeable,body
```
**If the result count equals your limit, THERE ARE MORE PRs. KEEP FETCHING.**
---
## PHASE 1: PR Collection (EXHAUSTIVE Pagination)
### 1.1 Determine Repository
Extract from user request:
- `REPO`: Repository in `owner/repo` format (default: current repo via `gh repo view --json nameWithOwner -q .nameWithOwner`)
### 1.2 Exhaustive Pagination Loop
# STOP. READ THIS BEFORE EXECUTING.
**YOU WILL FETCH EVERY. SINGLE. OPEN PR. NO EXCEPTIONS.**
## USE THE BUNDLED SCRIPT (MANDATORY)
**Use the bundled `scripts/gh_fetch.py` script for exhaustive pagination:**
```bash
# Fetch all open PRs (default)
./scripts/gh_fetch.py prs --output json ./scripts/gh_fetch.py prs --output json
# Fetch PRs from last 48 hours
./scripts/gh_fetch.py prs --hours 48 --output json
# Fetch from specific repo
./scripts/gh_fetch.py prs --repo owner/repo --state open --output json
``` ```
The script: ### 1.2 Fallback: Manual Pagination
- Handles pagination automatically (fetches ALL pages until empty)
- Outputs JSON that you can parse for agent distribution
- Filters by time range if `--hours` is specified
---
## FALLBACK: Manual Bash Pagination
If the Python script is unavailable, follow this manual approach:
## THE GOLDEN RULE
```
NEVER use --limit 100. ALWAYS use --limit 500.
NEVER stop at first result. ALWAYS verify you got everything.
NEVER assume "that's probably all". ALWAYS check if more exist.
```
## MANUAL PAGINATION LOOP (ONLY IF PYTHON SCRIPT UNAVAILABLE)
You MUST execute this EXACT pagination loop. DO NOT simplify. DO NOT skip iterations.
```bash ```bash
#!/bin/bash
# MANDATORY PAGINATION - Execute this EXACTLY as written
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner) REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
gh pr list --repo $REPO --state open --limit 500 --json number,title,state,createdAt,updatedAt,labels,author,headRefName,baseRefName,isDraft,mergeable,body
echo "=== EXHAUSTIVE PR PAGINATION START ===" # Continue pagination if 500 returned...
echo "Repository: $REPO"
echo ""
# STEP 1: First fetch with --limit 500
echo "[Page 1] Fetching open PRs..."
FIRST_FETCH=$(gh pr list --repo $REPO --state open --limit 500 --json number,title,state,createdAt,updatedAt,labels,author,headRefName,baseRefName,isDraft,mergeable,body)
FIRST_COUNT=$(echo "$FIRST_FETCH" | jq 'length')
echo "[Page 1] Count: $FIRST_COUNT"
ALL_PRS="$FIRST_FETCH"
# STEP 2: CHECK IF MORE PAGES NEEDED
# If we got exactly 500, there are MORE PRs!
if [ "$FIRST_COUNT" -eq 500 ]; then
echo ""
echo "WARNING: Got exactly 500 results. MORE PAGES EXIST!"
echo "Continuing pagination..."
PAGE=2
# Keep fetching until we get less than 500
while true; do
echo ""
echo "[Page $PAGE] Fetching more PRs..."
# Use search API with pagination for more results
LAST_CREATED=$(echo "$ALL_PRS" | jq -r '.[-1].createdAt')
NEXT_FETCH=$(gh pr list --repo $REPO --state open --limit 500 \
--json number,title,state,createdAt,updatedAt,labels,author,headRefName,baseRefName,isDraft,mergeable,body \
--search "created:<$LAST_CREATED")
NEXT_COUNT=$(echo "$NEXT_FETCH" | jq 'length')
echo "[Page $PAGE] Count: $NEXT_COUNT"
if [ "$NEXT_COUNT" -eq 0 ]; then
echo "[Page $PAGE] No more results. Pagination complete."
break
fi
# Merge results
ALL_PRS=$(echo "$ALL_PRS $NEXT_FETCH" | jq -s 'add | unique_by(.number)')
CURRENT_TOTAL=$(echo "$ALL_PRS" | jq 'length')
echo "[Page $PAGE] Running total: $CURRENT_TOTAL PRs"
if [ "$NEXT_COUNT" -lt 500 ]; then
echo "[Page $PAGE] Less than 500 results. Pagination complete."
break
fi
PAGE=$((PAGE + 1))
# Safety limit
if [ $PAGE -gt 20 ]; then
echo "SAFETY LIMIT: Stopped at page 20"
break
fi
done
fi
# STEP 3: FINAL COUNT
FINAL_COUNT=$(echo "$ALL_PRS" | jq 'length')
echo ""
echo "=== EXHAUSTIVE PR PAGINATION COMPLETE ==="
echo "Total open PRs found: $FINAL_COUNT"
echo ""
``` ```
## VERIFICATION CHECKLIST (MANDATORY) **AFTER Phase 1:** Update todo status to completed, mark Phase 2 as in_progress.
BEFORE proceeding to Phase 2, you MUST verify:
```
CHECKLIST:
[ ] Executed the FULL pagination loop above (not just --limit 500 once)
[ ] Saw "EXHAUSTIVE PR PAGINATION COMPLETE" in output
[ ] Counted total PRs: _____ (fill this in)
[ ] If first fetch returned 500, continued to page 2+
[ ] Used --state open
```
**If you did NOT see "EXHAUSTIVE PR PAGINATION COMPLETE", you did it WRONG. Start over.**
**AFTER Phase 1 Complete - Update Todo:**
```typescript
todowrite([
{ id: "1", content: "Phase 1: Fetch all open PRs with exhaustive pagination", status: "completed", priority: "high" },
{ id: "2", content: "Phase 2: Launch parallel background agents (1 per PR)", status: "in_progress", priority: "high" },
{ id: "3", content: "Phase 3: Collect all agent analysis results", status: "pending", priority: "high" },
{ id: "4", content: "Phase 4: Execute conservative auto-close for eligible PRs", status: "pending", priority: "high" },
{ id: "5", content: "Phase 5: Generate comprehensive triage report", status: "pending", priority: "high" }
])
```
--- ---
## PHASE 2: Parallel PR Analysis (1 PR = 1 Agent) # PHASE 2: LAUNCH 1 BACKGROUND TASK PER PR
### 2.1 Agent Assignment ## THE 1-PR-1-TASK PATTERN (MANDATORY)
**ALL PRs use `unspecified-low` category.** No ratio distribution needed. **CRITICAL: DO NOT BATCH MULTIPLE PRs INTO ONE TASK**
### 2.2 Launch Background Agents
**MANDATORY: Each PR gets its own dedicated background agent.**
For each PR, launch:
```typescript ```typescript
delegate_task( // Collection for tracking
category="unspecified-low", const taskMap = new Map() // prNumber -> taskId
load_skils=[],
run_in_background=true, // Category ratio: unspecified-low : writing : quick = 1:2:1
prompt=` // Every 4 PRs: 1 unspecified-low, 2 writing, 1 quick
function getCategory(index) {
const position = index % 4
if (position === 0) return "unspecified-low" // 25%
if (position === 1 || position === 2) return "writing" // 50%
return "quick" // 25%
}
// Launch 1 background task per PR
for (let i = 0; i < allPRs.length; i++) {
const pr = allPRs[i]
const category = getCategory(i)
console.log(`🚀 Launching background task for PR #${pr.number} (${category})...`)
const taskId = await delegate_task(
category=category,
load_skills=[],
run_in_background=true, // ← BACKGROUND TASK: Each PR runs independently
prompt=`
## TASK ## TASK
Analyze GitHub PR #${pr.number} for ${REPO} to determine if it can be closed or merged. Analyze GitHub PR #${pr.number} for ${REPO}.
## PR DATA ## PR DATA
- Number: #${pr.number} - Number: #${pr.number}
@ -292,348 +210,259 @@ ${pr.body}
5. Check base branch for similar changes: Search if the changes were already implemented 5. Check base branch for similar changes: Search if the changes were already implemented
## ANALYSIS CHECKLIST ## ANALYSIS CHECKLIST
1. **MERGE_READY**: Can this PR be merged? 1. **MERGE_READY**: Can this PR be merged? (approvals, CI passed, no conflicts, not draft)
- Has approvals
- CI passed
- No conflicts
- Not draft
2. **PROJECT_ALIGNED**: Does this PR align with current project direction? 2. **PROJECT_ALIGNED**: Does this PR align with current project direction?
- YES: Fits project goals and architecture 3. **CLOSE_ELIGIBILITY**: ALREADY_IMPLEMENTED | ALREADY_FIXED | OUTDATED_DIRECTION | STALE_ABANDONED
- NO: Contradicts current direction or outdated approach 4. **STALENESS**: ACTIVE (<30d) | STALE (30-180d) | ABANDONED (180d+)
- UNCLEAR: Needs maintainer decision
3. **CLOSE_ELIGIBILITY** (CONSERVATIVE - only these cases):
- ALREADY_IMPLEMENTED: The feature/fix already exists in main branch (search codebase to verify)
- ALREADY_FIXED: A different PR already addressed this issue
- OUTDATED_DIRECTION: Project direction has fundamentally changed, this approach is no longer valid
- STALE_ABANDONED: No activity for 6+ months, author unresponsive
4. **STALENESS**:
- ACTIVE: Updated within 30 days
- STALE: No updates for 30-180 days
- ABANDONED: No updates for 180+ days
## CONSERVATIVE CLOSE CRITERIA ## CONSERVATIVE CLOSE CRITERIA
**YOU MAY ONLY RECOMMEND CLOSING IF ONE OF THESE IS CLEARLY TRUE:** MAY CLOSE ONLY IF:
- The exact same change already exists in the main branch - Exact same change already exists in main
- A merged PR already solved the same problem differently - A merged PR already solved this differently
- The project explicitly deprecated or removed the feature this PR adds - Project explicitly deprecated the feature
- Author has been unresponsive for 6+ months despite requests - Author unresponsive for 6+ months despite requests
**DO NOT CLOSE FOR:** ## RETURN FORMAT (STRICT)
- "Could be done better" - that's a review comment, not a close reason
- "Needs rebasing" - contributor can fix this
- "Missing tests" - contributor can add these
- "I prefer a different approach" - discuss, don't close
## IF CLOSING IS RECOMMENDED
Provide a friendly, detailed English message explaining:
1. Why the PR is being closed
2. What happened (already implemented, etc.)
3. Thank the contributor for their effort
4. Offer guidance for future contributions
## RETURN FORMAT
\`\`\` \`\`\`
#${pr.number}: ${pr.title} PR: #${pr.number}
MERGE_READY: [YES|NO|NEEDS_WORK] - [reason] TITLE: ${pr.title}
ALIGNED: [YES|NO|UNCLEAR] - [reason] MERGE_READY: [YES|NO|NEEDS_WORK]
CLOSE_ELIGIBLE: [YES|NO] - [reason if YES: ALREADY_IMPLEMENTED|ALREADY_FIXED|OUTDATED_DIRECTION|STALE_ABANDONED] ALIGNED: [YES|NO|UNCLEAR]
CLOSE_ELIGIBLE: [YES|NO]
CLOSE_REASON: [ALREADY_IMPLEMENTED|ALREADY_FIXED|OUTDATED_DIRECTION|STALE_ABANDONED|N/A]
STALENESS: [ACTIVE|STALE|ABANDONED] STALENESS: [ACTIVE|STALE|ABANDONED]
RECOMMENDATION: [MERGE|CLOSE|REVIEW|WAIT] RECOMMENDATION: [MERGE|CLOSE|REVIEW|WAIT]
CLOSE_MESSAGE: [If CLOSE_ELIGIBLE=YES, provide the friendly closing message. Otherwise "N/A"] CLOSE_MESSAGE: [Friendly message if CLOSE_ELIGIBLE=YES, else "N/A"]
SUMMARY: [1-2 sentence summary of PR status] ACTION_NEEDED: [Specific action for maintainer]
ACTION: [Recommended maintainer action]
\`\`\` \`\`\`
` `
) )
```
// Store task ID for this PR
### 2.3 Collect All Results taskMap.set(pr.number, taskId)
Wait for all background agents to complete, then collect:
```typescript
// Store all task IDs
const taskIds: string[] = []
// Launch all agents
for (const pr of prs) {
const result = await delegate_task(...)
taskIds.push(result.task_id)
} }
// Collect results console.log(`\n✅ Launched ${taskMap.size} background tasks (1 per PR)`)
```
**AFTER Phase 2:** Update todo, mark Phase 3 as in_progress.
---
# PHASE 3: STREAM RESULTS AS EACH TASK COMPLETES
## REAL-TIME STREAMING COLLECTION
```typescript
const results = [] const results = []
for (const taskId of taskIds) { const autoCloseable = []
const output = await background_output(task_id=taskId) const readyToMerge = []
results.push(output) const needsReview = []
const needsWork = []
const stale = []
const drafts = []
const completedPRs = new Set()
const totalPRs = taskMap.size
console.log(`\n📊 Streaming results for ${totalPRs} PRs...`)
// Stream results as each background task completes
while (completedPRs.size < totalPRs) {
let newCompletions = 0
for (const [prNumber, taskId] of taskMap) {
if (completedPRs.has(prNumber)) continue
// Non-blocking check for this specific task
const output = await background_output(task_id=taskId, block=false)
if (output && output.length > 0) {
// Parse the completed analysis
const analysis = parseAnalysis(output)
results.push(analysis)
completedPRs.add(prNumber)
newCompletions++
// REAL-TIME STREAMING REPORT
console.log(`\n🔄 PR #${prNumber}: ${analysis.TITLE.substring(0, 60)}...`)
// Immediate categorization & reporting
if (analysis.CLOSE_ELIGIBLE === 'YES') {
autoCloseable.push(analysis)
console.log(` ⚠️ AUTO-CLOSE CANDIDATE: ${analysis.CLOSE_REASON}`)
} else if (analysis.MERGE_READY === 'YES') {
readyToMerge.push(analysis)
console.log(` ✅ READY TO MERGE`)
} else if (analysis.RECOMMENDATION === 'REVIEW') {
needsReview.push(analysis)
console.log(` 👀 NEEDS REVIEW`)
} else if (analysis.RECOMMENDATION === 'WAIT') {
needsWork.push(analysis)
console.log(` ⏳ WAITING FOR AUTHOR`)
} else if (analysis.STALENESS === 'STALE' || analysis.STALENESS === 'ABANDONED') {
stale.push(analysis)
console.log(` 💤 ${analysis.STALENESS}`)
} else {
drafts.push(analysis)
console.log(` 📝 DRAFT`)
}
console.log(` 📊 Action: ${analysis.ACTION_NEEDED}`)
// Progress update every 5 completions
if (completedPRs.size % 5 === 0) {
console.log(`\n📈 PROGRESS: ${completedPRs.size}/${totalPRs} PRs analyzed`)
console.log(` Ready: ${readyToMerge.length} | Review: ${needsReview.length} | Wait: ${needsWork.length} | Stale: ${stale.length} | Draft: ${drafts.length} | Close-Candidate: ${autoCloseable.length}`)
}
}
}
// If no new completions, wait briefly before checking again
if (newCompletions === 0 && completedPRs.size < totalPRs) {
await new Promise(r => setTimeout(r, 2000))
}
}
console.log(`\n✅ All ${totalPRs} PRs analyzed`)
```
---
# PHASE 4: Auto-Close Execution (CONSERVATIVE)
### 4.1 Confirm and Close
**Ask for confirmation before closing (unless user explicitly said auto-close is OK)**
```typescript
if (autoCloseable.length > 0) {
console.log(`\n🚨 FOUND ${autoCloseable.length} PR(s) ELIGIBLE FOR AUTO-CLOSE:`)
for (const pr of autoCloseable) {
console.log(` #${pr.PR}: ${pr.TITLE} (${pr.CLOSE_REASON})`)
}
// Close them one by one with progress
for (const pr of autoCloseable) {
console.log(`\n Closing #${pr.PR}...`)
await bash({
command: `gh pr close ${pr.PR} --repo ${REPO} --comment "${pr.CLOSE_MESSAGE}"`,
description: `Close PR #${pr.PR} with friendly message`
})
console.log(` ✅ Closed #${pr.PR}`)
}
} }
``` ```
**AFTER Phase 2 Complete - Update Todo:**
```typescript
todowrite([
{ id: "1", content: "Phase 1: Fetch all open PRs with exhaustive pagination", status: "completed", priority: "high" },
{ id: "2", content: "Phase 2: Launch parallel background agents (1 per PR)", status: "completed", priority: "high" },
{ id: "3", content: "Phase 3: Collect all agent analysis results", status: "in_progress", priority: "high" },
{ id: "4", content: "Phase 4: Execute conservative auto-close for eligible PRs", status: "pending", priority: "high" },
{ id: "5", content: "Phase 5: Generate comprehensive triage report", status: "pending", priority: "high" }
])
```
--- ---
## PHASE 3: Auto-Close Execution (CONSERVATIVE) # PHASE 5: FINAL COMPREHENSIVE REPORT
### 3.1 Identify Closeable PRs **GENERATE THIS AT THE VERY END - AFTER ALL PROCESSING**
From the collected results, identify PRs where:
- `CLOSE_ELIGIBLE: YES`
- Clear reason: `ALREADY_IMPLEMENTED`, `ALREADY_FIXED`, `OUTDATED_DIRECTION`, or `STALE_ABANDONED`
### 3.2 Close with Friendly Message
For each closeable PR:
```bash
gh pr close ${pr.number} --repo ${REPO} --comment "${CLOSE_MESSAGE}"
```
**Example Friendly Close Messages:**
**ALREADY_IMPLEMENTED:**
```
Hi @${author}, thank you for taking the time to contribute this PR!
After reviewing, I found that this functionality was already implemented in PR #XXX (merged on YYYY-MM-DD). The changes you proposed are now part of the main branch.
We really appreciate your effort and contribution to the project. Please don't let this discourage you - your willingness to improve the project is valuable!
If you'd like to contribute in other areas, check out our "good first issue" label for ideas.
Closing this as the changes are already in place. Thanks again!
```
**ALREADY_FIXED:**
```
Hi @${author}, thank you for this PR!
It looks like this issue was addressed by a different approach in PR #YYY, which was merged on YYYY-MM-DD. The underlying problem this PR aimed to solve has been resolved.
Thank you for your contribution and for caring about this issue. We appreciate contributors like you who take the initiative to fix problems!
Closing this as the issue is now resolved. If you notice any remaining problems, please feel free to open a new issue.
```
**OUTDATED_DIRECTION:**
```
Hi @${author}, thank you for working on this PR!
Since this PR was opened, the project direction has evolved. [Specific explanation of what changed - e.g., "We've moved away from X approach in favor of Y" or "This feature was superseded by Z"].
We genuinely appreciate the time you invested in this contribution. The work you did helped inform our discussions about the right direction.
Closing this due to the architectural changes. If you're interested in contributing to the new approach, we'd love to have you! Check out [relevant area] for opportunities.
```
**STALE_ABANDONED:**
```
Hi @${author}, thank you for opening this PR!
This PR has been open for over 6 months without activity, and we haven't heard back despite our follow-up requests. We're closing it to keep our PR queue manageable.
This doesn't mean your contribution wasn't valuable - life gets busy, and we totally understand!
If you'd like to pick this up again, feel free to:
1. Reopen this PR, or
2. Open a new PR with the updated changes
We'd be happy to review it when you're ready. Thanks for your interest in contributing!
```
**AFTER Phase 3 Complete - Update Todo:**
```typescript
todowrite([
{ id: "1", content: "Phase 1: Fetch all open PRs with exhaustive pagination", status: "completed", priority: "high" },
{ id: "2", content: "Phase 2: Launch parallel background agents (1 per PR)", status: "completed", priority: "high" },
{ id: "3", content: "Phase 3: Collect all agent analysis results", status: "completed", priority: "high" },
{ id: "4", content: "Phase 4: Execute conservative auto-close for eligible PRs", status: "completed", priority: "high" },
{ id: "5", content: "Phase 5: Generate comprehensive triage report", status: "in_progress", priority: "high" }
])
```
---
## PHASE 4: Report Generation
### 4.1 Categorize Results
Group analyzed PRs by status:
| Category | Criteria |
|----------|----------|
| **READY_TO_MERGE** | Approved, CI passed, no conflicts |
| **AUTO_CLOSED** | Closed during this triage (with reasons) |
| **NEEDS_REVIEW** | Awaiting maintainer review |
| **NEEDS_WORK** | Requires changes from author |
| **STALE** | No activity for 30+ days |
| **DRAFT** | Still work in progress |
### 4.2 Generate Report
```markdown ```markdown
# PR Triage Report # PR Triage Report - ${REPO}
**Repository:** ${REPO}
**Generated:** ${new Date().toISOString()} **Generated:** ${new Date().toISOString()}
**Total Open PRs Analyzed:** ${prs.length} **Total PRs Analyzed:** ${results.length}
**Processing Mode:** STREAMING (1 PR = 1 background task, real-time results)
## Summary
| Category | Count |
|----------|-------|
| Ready to Merge | N |
| Auto-Closed | N |
| Needs Review | N |
| Needs Work | N |
| Stale | N |
| Draft | N |
--- ---
## 1. AUTO-CLOSED PRs (Action Taken) ## 📊 Summary
These PRs were closed during this triage session: | Category | Count | Status |
|----------|-------|--------|
| PR | Title | Reason | Message Posted | | ✅ Ready to Merge | ${readyToMerge.length} | Action: Merge immediately |
|----|-------|--------|----------------| | ⚠️ Auto-Closed | ${autoCloseable.length} | Already processed |
| #123 | Feature X | ALREADY_IMPLEMENTED | Yes | | 👀 Needs Review | ${needsReview.length} | Action: Assign reviewers |
| ⏳ Needs Work | ${needsWork.length} | Action: Comment guidance |
| 💤 Stale | ${stale.length} | Action: Follow up |
| 📝 Draft | ${drafts.length} | No action needed |
--- ---
## 2. Ready to Merge ## ✅ Ready to Merge
| PR | Title | Author | Approvals | CI | Last Updated | ${readyToMerge.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
|----|-------|--------|-----------|-----|--------------|
| #456 | Fix Y | user | 2 | Pass | 2d ago |
**Action Required:** Review and merge these PRs. **Action:** These PRs can be merged immediately.
--- ---
## 3. Needs Review ## ⚠️ Auto-Closed (During This Triage)
| PR | Title | Author | Created | Last Updated | ${autoCloseable.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 40)}... | ${pr.CLOSE_REASON} |`).join('\n')}
|----|-------|--------|---------|--------------|
| #789 | Add Z | user | 5d ago | 3d ago |
**Action Required:** Assign reviewers and provide feedback.
--- ---
## 4. Needs Work ## 👀 Needs Review
| PR | Title | Author | Issue | ${needsReview.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
|----|-------|--------|-------|
| #101 | Update A | user | Failing CI, needs rebase |
**Action Required:** Comment with specific guidance. **Action:** Assign maintainers for review.
--- ---
## 5. Stale PRs ## ⏳ Needs Work
| PR | Title | Author | Last Activity | Days Inactive | ${needsWork.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... | ${pr.ACTION_NEEDED} |`).join('\n')}
|----|-------|--------|---------------|---------------|
| #112 | Old B | user | 2025-01-01 | 45 |
**Action Required:** Ping author or close if abandoned.
--- ---
## 6. Draft PRs ## 💤 Stale PRs
| PR | Title | Author | Created | ${stale.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 40)}... | ${pr.STALENESS} |`).join('\n')}
|----|-------|--------|---------|
| #131 | WIP C | user | 10d ago |
**No action required** - authors are still working on these.
--- ---
## Recommendations ## 📝 Draft PRs
1. **Merge immediately:** [list PRs ready] ${drafts.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
2. **Assign reviewers:** [list PRs awaiting review]
3. **Follow up with authors:** [list stale PRs]
4. **Consider closing:** [list abandoned PRs not auto-closed due to uncertainty]
```
**AFTER Phase 4 Complete - Final Todo Update:** ---
```typescript
todowrite([ ## 🎯 Immediate Actions
{ id: "1", content: "Phase 1: Fetch all open PRs with exhaustive pagination", status: "completed", priority: "high" },
{ id: "2", content: "Phase 2: Launch parallel background agents (1 per PR)", status: "completed", priority: "high" }, 1. **Merge:** ${readyToMerge.length} PRs ready for immediate merge
{ id: "3", content: "Phase 3: Collect all agent analysis results", status: "completed", priority: "high" }, 2. **Review:** ${needsReview.length} PRs awaiting maintainer attention
{ id: "4", content: "Phase 4: Execute conservative auto-close for eligible PRs", status: "completed", priority: "high" }, 3. **Follow Up:** ${stale.length} stale PRs need author ping
{ id: "5", content: "Phase 5: Generate comprehensive triage report", status: "completed", priority: "high" }
]) ---
## Processing Log
${results.map((r, i) => `${i+1}. #${r.PR}: ${r.RECOMMENDATION} (${r.MERGE_READY === 'YES' ? 'ready' : r.CLOSE_ELIGIBLE === 'YES' ? 'close' : 'needs attention'})`).join('\n')}
``` ```
--- ---
## ANTI-PATTERNS (BLOCKING VIOLATIONS) ## CRITICAL ANTI-PATTERNS (BLOCKING VIOLATIONS)
## IF YOU DO ANY OF THESE, THE TRIAGE IS INVALID
| Violation | Why It's Wrong | Severity | | Violation | Why It's Wrong | Severity |
|-----------|----------------|----------| |-----------|----------------|----------|
| **Using `--limit 100`** | Misses 80%+ of PRs in active repos | CRITICAL | | **Batch multiple PRs in one task** | Violates 1 PR = 1 task rule | CRITICAL |
| **Stopping at first fetch** | GitHub paginates - you only got page 1 | CRITICAL | | **Use `run_in_background=false`** | No parallelism, slower execution | CRITICAL |
| **Not counting results** | Can't verify completeness | CRITICAL | | **Collect all tasks, report at end** | Loses streaming benefit | CRITICAL |
| **Closing PRs aggressively** | Hurts contributors, damages community | CRITICAL | | **No `background_output()` polling** | Can't stream results | CRITICAL |
| Batching PRs (7 per agent) | Loses detail, harder to track | HIGH | | No progress updates | User doesn't know if stuck or working | HIGH |
| Sequential agent calls | Slow, doesn't leverage parallelism | HIGH |
| Closing for "could be better" | That's review feedback, not close reason | HIGH |
| Generic close messages | Each closure needs specific, friendly explanation | MEDIUM |
---
## CONSERVATIVE CLOSE POLICY (MANDATORY)
**YOU ARE NOT THE MAINTAINER. YOU ARE A TRIAGE ASSISTANT.**
### You MAY close when:
- Evidence proves the change already exists in main
- A merged PR already solved the same problem
- Project explicitly deprecated/removed the relevant feature
- Author unresponsive for 6+ months despite attempts
### You MAY NOT close when:
- You think a different approach is better
- PR needs rebasing or has conflicts
- PR is missing tests or documentation
- You're unsure about project direction
- Author is active but busy
### When in doubt: DO NOT CLOSE. Flag for maintainer review.
--- ---
## EXECUTION CHECKLIST ## EXECUTION CHECKLIST
- [ ] Created initial todo list before starting work - [ ] Created todos before starting
- [ ] Fetched ALL pages of open PRs (pagination complete) - [ ] Fetched ALL PRs with exhaustive pagination
- [ ] Updated todo after Phase 1 completion - [ ] **LAUNCHED**: 1 background task per PR (`run_in_background=true`)
- [ ] Launched 1 agent per PR (not batched) - [ ] **STREAMED**: Results via `background_output()` as each task completes
- [ ] Updated todo after Phase 2 completion - [ ] Showed live progress every 5 PRs
- [ ] All agents ran in background (parallel) - [ ] Real-time categorization visible to user
- [ ] Collected all results before taking action - [ ] Conservative auto-close with confirmation
- [ ] Updated todo after Phase 3 completion - [ ] **FINAL**: Comprehensive summary report at end
- [ ] Only closed PRs meeting CONSERVATIVE criteria - [ ] All todos marked complete
- [ ] Posted friendly, detailed close messages
- [ ] Updated todo after Phase 4 completion
- [ ] Generated comprehensive report
- [ ] Final todo update - all phases completed
--- ---
@ -641,15 +470,15 @@ todowrite([
When invoked, immediately: When invoked, immediately:
1. **CREATE TODOS FIRST** - Use todowrite() to register all 5 phases 1. **CREATE TODOS**
2. `gh repo view --json nameWithOwner -q .nameWithOwner` (get current repo) 2. `gh repo view --json nameWithOwner -q .nameWithOwner`
3. Exhaustive pagination for ALL open PRs 3. Exhaustive pagination for ALL open PRs
4. Update todo - Phase 1 complete 4. **LAUNCH**: For each PR:
5. Launch N background agents (1 per PR) - `delegate_task(run_in_background=true)` - 1 task per PR
6. Update todo - Phase 2 complete - Store taskId mapped to PR number
7. Collect all results 5. **STREAM**: Poll `background_output()` for each task:
8. Update todo - Phase 3 complete - As each completes, immediately report result
9. Auto-close PRs meeting CONSERVATIVE criteria with friendly messages - Categorize in real-time
10. Update todo - Phase 4 complete - Show progress every 5 completions
11. Generate categorized report with action items 6. Auto-close eligible PRs
12. Final todo update - all phases completed 7. **GENERATE FINAL COMPREHENSIVE REPORT**