github-pr-triage by code-yeongyu/oh-my-opencode
npx skills add https://github.com/code-yeongyu/oh-my-opencode --skill github-pr-triage你是一个 GitHub Pull Request 分类自动化代理。你的工作是:
每个 PR 必须作为单独的后台任务处理
| 方面 | 规则 |
|---|---|
| 任务粒度 | 1 PR = 恰好 1 个 task() 调用 |
| 执行模式 | run_in_background=true (每个 PR 独立运行) |
| 结果处理 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
background_output() 用于在任务完成时收集结果 |
| 报告 | 每个任务完成时立即进行流式传输 |
使用实时流式传输处理 PR - 而不是批处理
| 错误方式 | 正确方式 |
|---|---|
| 获取所有 → 等待所有代理 → 一次性报告所有结果 | 获取所有 → 为每个 PR 启动 1 个任务 (后台) → 每个任务完成时流式传输结果 → 继续下一个 |
| "正在处理 50 个 PR... (等待 5 分钟) ...这里是所有结果" | "PR #123 分析完成... [结果] PR #124 分析完成... [结果] ..." |
| 处理过程中用户看不到任何信息 | 每个后台任务完成时用户可以看到实时进度 |
run_in_background=false (顺序阻塞) | run_in_background=true 配合 background_output() 流式传输 |
// 正确:将所有 PR 作为后台任务启动,流式传输结果
const taskIds = []
// 类别比例:unspecified-low : writing : quick = 1:2:1
// 每 4 个 PR: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%
}
// 阶段 1:为每个 PR 启动 1 个后台任务
for (let i = 0; i < allPRs.length; i++) {
const pr = allPRs[i]
const category = getCategory(i)
const taskId = await task(
category=category,
load_skills=[],
run_in_background=true, // ← 关键:每个 PR 是独立的后台任务
prompt=`Analyze PR #${pr.number}...`
)
taskIds.push({ pr: pr.number, taskId, category })
console.log(`🚀 Launched background task for PR #${pr.number} (${category})`)
}
// 阶段 2:在任务完成时流式传输结果
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
// 检查这个特定 PR 的任务是否完成
const result = await background_output(taskId=taskId, block=false)
if (result && result.output) {
// 流式传输:每个任务完成时立即报告
const analysis = parseAnalysis(result.output)
reportRealtime(analysis)
completed.add(pr)
console.log(`\n✅ PR #${pr} analysis complete (${completed.size}/${taskIds.length})`)
}
}
// 小延迟以防止过度请求
if (completed.size < taskIds.length) {
await new Promise(r => setTimeout(r, 1000))
}
}
在执行任何其他操作之前,先创建 TODOs。
// 立即创建 todos
todowrite([
{ id: "1", content: "使用详尽分页获取所有打开的 PR", status: "in_progress", priority: "high" },
{ id: "2", content: "为每个 PR 启动 1 个后台任务 (1 PR = 1 个任务)", status: "pending", priority: "high" },
{ id: "3", content: "在每个任务完成时流式处理结果", status: "pending", priority: "high" },
{ id: "4", content: "对符合条件的 PR 执行保守的自动关闭", status: "pending", priority: "high" },
{ id: "5", content: "生成最终综合报告", status: "pending", priority: "high" }
])
./scripts/gh_fetch.py prs --output json
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
# 如果返回 500 条,继续分页...
阶段 1 之后: 将 todo 状态更新为已完成,将阶段 2 标记为进行中。
关键:不要将多个 PR 批量处理到一个任务中
// 用于跟踪的集合
const taskMap = new Map() // prNumber -> taskId
// 类别比例:unspecified-low : writing : quick = 1:2:1
// 每 4 个 PR: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%
}
// 为每个 PR 启动 1 个后台任务
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 task(
category=category,
load_skills=[],
run_in_background=true, // ← 后台任务:每个 PR 独立运行
prompt=`
## 任务
分析 ${REPO} 的 GitHub PR #${pr.number}。
## PR 数据
- 编号: #${pr.number}
- 标题: ${pr.title}
- 状态: ${pr.state}
- 作者: ${pr.author.login}
- 创建时间: ${pr.createdAt}
- 更新时间: ${pr.updatedAt}
- 标签: ${pr.labels.map(l => l.name).join(', ')}
- 源分支: ${pr.headRefName}
- 目标分支: ${pr.baseRefName}
- 是否为草稿: ${pr.isDraft}
- 可合并: ${pr.mergeable}
## PR 正文
${pr.body}
## 获取额外上下文
1. 获取 PR 评论: gh pr view ${pr.number} --repo ${REPO} --json comments
2. 获取 PR 审查: gh pr view ${pr.number} --repo ${REPO} --json reviews
3. 获取 PR 更改的文件: gh pr view ${pr.number} --repo ${REPO} --json files
4. 检查分支是否存在: git ls-remote --heads origin ${pr.headRefName}
5. 检查目标分支是否有类似更改: 搜索是否已实现这些更改
## 分析清单
1. **可合并**: 这个 PR 可以合并吗? (已批准,CI 通过,无冲突,不是草稿)
2. **项目一致性**: 这个 PR 是否符合当前项目方向?
3. **关闭资格**: 已实现 | 已修复 | 方向过时 | 陈旧废弃
4. **陈旧度**: 活跃 (<30天) | 陈旧 (30-180天) | 废弃 (180天+)
## 保守关闭标准
仅在以下情况下可以关闭:
- 完全相同的更改已存在于主分支中
- 已合并的 PR 已用不同方式解决了此问题
- 项目已明确弃用该功能
- 作者超过 6 个月无响应,尽管有请求
## 返回格式 (严格)
\`\`\`
PR: #${pr.number}
TITLE: ${pr.title}
MERGE_READY: [YES|NO|NEEDS_WORK]
ALIGNED: [YES|NO|UNCLEAR]
CLOSE_ELIGIBLE: [YES|NO]
CLOSE_REASON: [ALREADY_IMPLEMENTED|ALREADY_FIXED|OUTDATED_DIRECTION|STALE_ABANDONED|N/A]
STALENESS: [ACTIVE|STALE|ABANDONED]
RECOMMENDATION: [MERGE|CLOSE|REVIEW|WAIT]
CLOSE_MESSAGE: [如果 CLOSE_ELIGIBLE=YES 则提供友好消息,否则为 "N/A"]
ACTION_NEEDED: [维护者需要采取的具体操作]
\`\`\`
`
)
// 存储此 PR 的任务 ID
taskMap.set(pr.number, taskId)
}
console.log(`\n✅ Launched ${taskMap.size} background tasks (1 per PR)`)
阶段 2 之后: 更新 todo,将阶段 3 标记为进行中。
const results = []
const autoCloseable = []
const readyToMerge = []
const needsReview = []
const needsWork = []
const stale = []
const drafts = []
const completedPRs = new Set()
const totalPRs = taskMap.size
console.log(`\n📊 Streaming results for ${totalPRs} PRs...`)
// 在每个后台任务完成时流式传输结果
while (completedPRs.size < totalPRs) {
let newCompletions = 0
for (const [prNumber, taskId] of taskMap) {
if (completedPRs.has(prNumber)) continue
// 对此特定任务进行非阻塞检查
const output = await background_output(task_id=taskId, block=false)
if (output && output.length > 0) {
// 解析已完成的分析
const analysis = parseAnalysis(output)
results.push(analysis)
completedPRs.add(prNumber)
newCompletions++
// 实时流式传输报告
console.log(`\n🔄 PR #${prNumber}: ${analysis.TITLE.substring(0, 60)}...`)
// 立即分类和报告
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}`)
// 每完成 5 个任务更新一次进度
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 (newCompletions === 0 && completedPRs.size < totalPRs) {
await new Promise(r => setTimeout(r, 2000))
}
}
console.log(`\n✅ All ${totalPRs} PRs analyzed`)
在关闭前请求确认(除非用户明确表示自动关闭是可以的)
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})`)
}
// 逐个关闭并显示进度
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}`)
}
}
在所有处理完成后生成此报告
# PR 分类报告 - ${REPO}
**生成时间:** ${new Date().toISOString()}
**分析的 PR 总数:** ${results.length}
**处理模式:** 流式传输 (1 PR = 1 个后台任务,实时结果)
---
## 📊 摘要
| 类别 | 数量 | 状态 |
|----------|-------|--------|
| ✅ 准备合并 | ${readyToMerge.length} | 操作:立即合并 |
| ⚠️ 已自动关闭 | ${autoCloseable.length} | 已处理 |
| 👀 需要审查 | ${needsReview.length} | 操作:分配审查者 |
| ⏳ 需要工作 | ${needsWork.length} | 操作:评论指导 |
| 💤 陈旧 | ${stale.length} | 操作:跟进 |
| 📝 草稿 | ${drafts.length} | 无需操作 |
---
## ✅ 准备合并
${readyToMerge.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
**操作:** 这些 PR 可以立即合并。
---
## ⚠️ 已自动关闭(在此分类期间)
${autoCloseable.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 40)}... | ${pr.CLOSE_REASON} |`).join('\n')}
---
## 👀 需要审查
${needsReview.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
**操作:** 分配维护者进行审查。
---
## ⏳ 需要工作
${needsWork.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... | ${pr.ACTION_NEEDED} |`).join('\n')}
---
## 💤 陈旧 PR
${stale.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 40)}... | ${pr.STALENESS} |`).join('\n')}
---
## 📝 草稿 PR
${drafts.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
---
## 🎯 立即行动
1. **合并:** ${readyToMerge.length} 个 PR 已准备好立即合并
2. **审查:** ${needsReview.length} 个 PR 等待维护者关注
3. **跟进:** ${stale.length} 个陈旧 PR 需要联系作者
---
## 处理日志
${results.map((r, i) => `${i+1}. #${r.PR}: ${r.RECOMMENDATION} (${r.MERGE_READY === 'YES' ? 'ready' : r.CLOSE_ELIGIBLE === 'YES' ? 'close' : 'needs attention'})`).join('\n')}
| 违规行为 | 为什么是错误的 | 严重性 |
|---|---|---|
| 将多个 PR 批量处理到一个任务中 | 违反 1 PR = 1 任务规则 | 关键 |
使用run_in_background=false | 没有并行性,执行速度慢 | 关键 |
| 收集所有任务,最后报告 | 失去流式传输优势 | 关键 |
没有background_output()轮询 | 无法流式传输结果 | 关键 |
| 没有进度更新 | 用户不知道是否卡住或正在工作 | 高 |
run_in_background=true)background_output() 在每个任务完成时传输结果调用时立即:
gh repo view --json nameWithOwner -q .nameWithOwnertask(run_in_background=true) - 每个 PR 1 个任务background_output():
每周安装数
96
仓库
GitHub 星标数
38.5K
首次出现
2026年2月2日
安全审计
安装在
opencode75
claude-code62
gemini-cli56
codex53
github-copilot45
cursor44
You are a GitHub Pull Request triage automation agent. Your job is to:
EACH PR MUST BE PROCESSED AS A SEPARATE BACKGROUND TASK
| Aspect | Rule |
|---|---|
| Task Granularity | 1 PR = Exactly 1 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 |
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 |
// 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 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))
}
}
BEFORE DOING ANYTHING ELSE, CREATE TODOS.
// Create todos immediately
todowrite([
{ id: "1", content: "Fetch all open PRs with exhaustive pagination", status: "in_progress", priority: "high" },
{ id: "2", content: "Launch 1 background task per PR (1 PR = 1 task)", status: "pending", priority: "high" },
{ id: "3", content: "Stream-process results as each task completes", status: "pending", priority: "high" },
{ id: "4", content: "Execute conservative auto-close for eligible PRs", status: "pending", priority: "high" },
{ id: "5", content: "Generate final comprehensive report", status: "pending", priority: "high" }
])
./scripts/gh_fetch.py prs --output json
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
# Continue pagination if 500 returned...
AFTER Phase 1: Update todo status to completed, mark Phase 2 as in_progress.
CRITICAL: DO NOT BATCH MULTIPLE PRs INTO ONE TASK
// Collection for tracking
const taskMap = new Map() // prNumber -> taskId
// 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%
}
// 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 task(
category=category,
load_skills=[],
run_in_background=true, // ← BACKGROUND TASK: Each PR runs independently
prompt=`
## TASK
Analyze GitHub PR #${pr.number} for ${REPO}.
## PR DATA
- Number: #${pr.number}
- Title: ${pr.title}
- State: ${pr.state}
- Author: ${pr.author.login}
- Created: ${pr.createdAt}
- Updated: ${pr.updatedAt}
- Labels: ${pr.labels.map(l => l.name).join(', ')}
- Head Branch: ${pr.headRefName}
- Base Branch: ${pr.baseRefName}
- Is Draft: ${pr.isDraft}
- Mergeable: ${pr.mergeable}
## PR BODY
${pr.body}
## FETCH ADDITIONAL CONTEXT
1. Fetch PR comments: gh pr view ${pr.number} --repo ${REPO} --json comments
2. Fetch PR reviews: gh pr view ${pr.number} --repo ${REPO} --json reviews
3. Fetch PR files changed: gh pr view ${pr.number} --repo ${REPO} --json files
4. Check if branch exists: git ls-remote --heads origin ${pr.headRefName}
5. Check base branch for similar changes: Search if the changes were already implemented
## ANALYSIS CHECKLIST
1. **MERGE_READY**: Can this PR be merged? (approvals, CI passed, no conflicts, not draft)
2. **PROJECT_ALIGNED**: Does this PR align with current project direction?
3. **CLOSE_ELIGIBILITY**: ALREADY_IMPLEMENTED | ALREADY_FIXED | OUTDATED_DIRECTION | STALE_ABANDONED
4. **STALENESS**: ACTIVE (<30d) | STALE (30-180d) | ABANDONED (180d+)
## CONSERVATIVE CLOSE CRITERIA
MAY CLOSE ONLY IF:
- Exact same change already exists in main
- A merged PR already solved this differently
- Project explicitly deprecated the feature
- Author unresponsive for 6+ months despite requests
## RETURN FORMAT (STRICT)
\`\`\`
PR: #${pr.number}
TITLE: ${pr.title}
MERGE_READY: [YES|NO|NEEDS_WORK]
ALIGNED: [YES|NO|UNCLEAR]
CLOSE_ELIGIBLE: [YES|NO]
CLOSE_REASON: [ALREADY_IMPLEMENTED|ALREADY_FIXED|OUTDATED_DIRECTION|STALE_ABANDONED|N/A]
STALENESS: [ACTIVE|STALE|ABANDONED]
RECOMMENDATION: [MERGE|CLOSE|REVIEW|WAIT]
CLOSE_MESSAGE: [Friendly message if CLOSE_ELIGIBLE=YES, else "N/A"]
ACTION_NEEDED: [Specific action for maintainer]
\`\`\`
`
)
// Store task ID for this PR
taskMap.set(pr.number, taskId)
}
console.log(`\n✅ Launched ${taskMap.size} background tasks (1 per PR)`)
AFTER Phase 2: Update todo, mark Phase 3 as in_progress.
const results = []
const autoCloseable = []
const readyToMerge = []
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`)
Ask for confirmation before closing (unless user explicitly said auto-close is OK)
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}`)
}
}
GENERATE THIS AT THE VERY END - AFTER ALL PROCESSING
# PR Triage Report - ${REPO}
**Generated:** ${new Date().toISOString()}
**Total PRs Analyzed:** ${results.length}
**Processing Mode:** STREAMING (1 PR = 1 background task, real-time results)
---
## 📊 Summary
| Category | Count | Status |
|----------|-------|--------|
| ✅ Ready to Merge | ${readyToMerge.length} | Action: Merge immediately |
| ⚠️ Auto-Closed | ${autoCloseable.length} | Already processed |
| 👀 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 |
---
## ✅ Ready to Merge
${readyToMerge.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
**Action:** These PRs can be merged immediately.
---
## ⚠️ Auto-Closed (During This Triage)
${autoCloseable.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 40)}... | ${pr.CLOSE_REASON} |`).join('\n')}
---
## 👀 Needs Review
${needsReview.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
**Action:** Assign maintainers for review.
---
## ⏳ Needs Work
${needsWork.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... | ${pr.ACTION_NEEDED} |`).join('\n')}
---
## 💤 Stale PRs
${stale.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 40)}... | ${pr.STALENESS} |`).join('\n')}
---
## 📝 Draft PRs
${drafts.map(pr => `| #${pr.PR} | ${pr.TITLE.substring(0, 50)}... |`).join('\n')}
---
## 🎯 Immediate Actions
1. **Merge:** ${readyToMerge.length} PRs ready for immediate merge
2. **Review:** ${needsReview.length} PRs awaiting maintainer attention
3. **Follow Up:** ${stale.length} stale PRs need author ping
---
## 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')}
| Violation | Why It's Wrong | Severity |
|---|---|---|
| Batch multiple PRs in one task | Violates 1 PR = 1 task rule | CRITICAL |
Userun_in_background=false | No parallelism, slower execution | CRITICAL |
| Collect all tasks, report at end | Loses streaming benefit | CRITICAL |
Nobackground_output() polling | Can't stream results | CRITICAL |
| No progress updates | User doesn't know if stuck or working | HIGH |
run_in_background=true)background_output() as each task completesWhen invoked, immediately:
gh repo view --json nameWithOwner -q .nameWithOwnertask(run_in_background=true) - 1 task per PRbackground_output() for each task:
Weekly Installs
96
Repository
GitHub Stars
38.5K
First Seen
Feb 2, 2026
Security Audits
Gen Agent Trust HubPassSocketWarnSnykWarn
Installed on
opencode75
claude-code62
gemini-cli56
codex53
github-copilot45
cursor44
Azure Data Explorer (Kusto) 查询技能:KQL数据分析、日志遥测与时间序列处理
130,600 周安装