gitlink-cli/skills/gitlink-stale/examples/pr-stale-workflow.md

9.3 KiB
Raw Blame History

示例PR 长期未活动催办工作流

本示例演示对长期未活动的 PR 执行催办流程。 ⚠️ 与 Issue 不同PR 端点暂不支持 label 操作,因此只评论催办,不打 stale 标签。

场景

  • 仓库Gitlink/forgeplus
  • 目标:识别 60+ 天未活动的 open PR评论催办74+ 天的关闭
  • 执行者Claude Code + 用户(人在环路)

Step 0 — 准备环境

cd D:\code\SE\Evolution_and_Maintenance_of_SE\Mission2\gitlink-cli

.\gitlink-cli.exe version
.\gitlink-cli.exe auth status

Step 1 — 拉取 PR 列表

1.1 获取所有 open PR

.\gitlink-cli.exe pr +list `
    --owner Gitlink `
    --repo forgeplus `
    --state open `
    --format json | Out-File -Encoding utf8 "$env:TEMP\prs-open.json"

1.2 客户端二次过滤

⚠️ 关键GitLink 的 pr +list --state open--state 参数仅影响统计计数,返回列表可能包含所有状态。必须pull_request_status == 0 二次过滤。

$raw = Get-Content "$env:TEMP\prs-open.json" -Raw | ConvertFrom-Json

# 二次过滤:仅保留真正 open 的 PR
$openPRs = $raw.data.pull_requests | Where-Object { $_.pull_request_status -eq 0 }

# 时间过滤60+ 天未活动
$threshold = (Get-Date).AddDays(-60)
$stalePRs = $openPRs | Where-Object {
    $updated = if ($_.updated_at) { [DateTime]::Parse($_.updated_at) } else { [DateTime]::Parse($_.created_at) }
    $updated -lt $threshold
}

Write-Host "Total open PRs: $($openPRs.Count)"
Write-Host "Stale candidates (60+ days): $($stalePRs.Count)"

示例输出

Total open PRs: 12
Stale candidates (60+ days): 4

Step 2 — 逐个详情分析

对每个候选 PR

$results = @()

foreach ($pr in $stalePRs) {
    # 拉取 PR 详情
    $detail = (& .\gitlink-cli.exe pr +view `
        --owner Gitlink --repo forgeplus `
        --id $pr.pull_request_number `
        --format json) | ConvertFrom-Json
    
    # 计算天数
    $lastActivity = if ($detail.data.updated_at) {
        [DateTime]::Parse($detail.data.updated_at)
    } else {
        [DateTime]::Parse($detail.data.created_at)
    }
    $days = [int]((Get-Date) - $lastActivity).TotalDays
    
    # AI 判断PR 特化规则)
    $analysis = ai_judge_pr_stale $detail
    
    $results += [PSCustomObject]@{
        Id = $pr.pull_request_number
        Title = $pr.title
        Author = $pr.user.login
        Days = $days
        Confidence = $analysis.confidence
        Action = if ($days -ge 74) { "auto_close" } else { "mark_stale" }
        Reason = $analysis.reason
    }
}

$results | Format-Table

AI 判断 PR 的特殊规则

PR 与 Issue 的差异:

维度 Issue PR
标签 支持豁免标签 暂不支持
评论催办 mark_stale + 评论 仅评论
自动关闭 issue +close pr +close
合并状态 N/A 已 merged 的不算 stale
def ai_judge_pr_stale(pr_detail):
    """
    PR 特化判断
    """
    # 已 merged 或已 closed 的不算(理论上已被过滤)
    if pr_detail.pull_request_status != 0:
        return {"truly_stale": False, "exempt": True, "reason": "已 merged/closed"}
    
    # 是否有冲突?
    if pr_detail.conflict:
        return {
            "truly_stale": True,
            "confidence": 0.9,
            "reason": "存在冲突,可能需要 rebase"
        }
    
    # 是否等待 review
    if pr_detail.reviewers and not pr_detail.approved:
        return {
            "truly_stale": True,
            "confidence": 0.75,
            "reason": "等待 reviewer 回应"
        }
    
    # 作者活跃度
    if pr_detail.user.login in repo_contributors:
        return {
            "truly_stale": True,
            "confidence": 0.65,
            "reason": "贡献者提交后未跟进"
        }
    
    return {
        "truly_stale": True,
        "confidence": 0.8,
        "reason": "默认判断"
    }

Step 3 — 展示报告

Claude Code 输出:

发现 4 个 60+ 天未活动的 PR

┌──────┬────────────────────────────┬──────────┬────────┬─────────────┬────────────┐
│ #    │ 标题                        │ 作者      │ 天数   │ 置信度      │ 动作       │
├──────┼────────────────────────────┼──────────┼────────┼─────────────┼────────────┤
│ 8    │ feat: 新增搜索功能          │ contrib-a│ 68     │ 0.85        │ mark_stale │
│ 12   │ fix: 修复登录 bug           │ newbie   │ 92     │ 0.92        │ auto_close │
│ 15   │ docs: 更新 README          │ user-1   │ 65     │ 0.70        │ mark_stale │
│ 21   │ refactor: 重构 API         │ contrib-b│ 78     │ 0.88        │ auto_close │
└──────┴────────────────────────────┴──────────┴────────┴─────────────┴────────────┘

⚠️ 注意PR 暂不支持 label 操作,将仅评论催办。

是否应用?[yes / 选择性 / 取消]

Step 4 — 应用动作(用户确认后)

4.1 备份

$ts = Get-Date -Format "yyyyMMddHHmmss"
.\gitlink-cli.exe pr +list `
    --owner Gitlink --repo forgeplus `
    --state open --format json |
    Out-File -Encoding utf8 "$env:TEMP\before-pr-stale-$ts.json"

4.2 批量应用

$OWNER = "Gitlink"
$REPO = "forgeplus"
$report = Get-Content "$env:TEMP\pr-stale-report.json" -Raw | ConvertFrom-Json

$toApply = $report.items | Where-Object {
    $_.recommended_action -in @("mark_stale", "auto_close") -and
    $_.ai_analysis.confidence -ge 0.6
}

foreach ($item in $toApply) {
    $id = $item.number
    $action = $item.recommended_action
    
    Write-Host "→ PR #$id : $action"
    
    # 选择评论模板
    if ($action -eq "mark_stale") {
        $body = @"
⏰ **PR 长期未活动**

本 PR 已 60 天未更新,可能存在以下情况:

- 合并遇到冲突?请 rebase 后重新推送
- 等待 review可 @mention 相关维护者
- 不再需要?欢迎手动关闭

如果 **14 天内**没有新活动,将默认关闭。

> 🤖 由 gitlink-stale skill 自动生成。
"@
    } else {
        $body = @"
🔒 **PR 自动关闭(长期未活动)**

本 PR 已 74 天无活动,自动关闭。

- 如仍需合并,请 rebase 后重新打开
- 如有冲突,可重新发起 PR

> 🤖 由 gitlink-stale skill 自动关闭。
"@
    }
    
    # 1. 评论催办
    & .\gitlink-cli.exe pr +comment `
        --owner $OWNER --repo $REPO `
        --id $id --body $body 2>&1 | Out-Null
    
    # 2. 若 auto_close关闭 PR
    if ($action -eq "auto_close") {
        & .\gitlink-cli.exe pr +close `
            --owner $OWNER --repo $REPO `
            --id $id 2>&1 | Out-Null
    }
    
    Start-Sleep -Milliseconds 500
}

Write-Host "✓ Batch applied"

Step 5 — 验证

# 检查 PR #8 是否已评论
.\gitlink-cli.exe pr +view `
    --owner Gitlink --repo forgeplus `
    --id 8 --format json |
    ConvertFrom-Json |
    Select-Object -ExpandProperty data |
    Select-Object title, @{N="status";E={$_.pull_request_status}}, @{N="journals_count";E={$_.journals.Count}}

故障恢复

误关闭的 PR 恢复

# 重新打开 PRRaw API
# 注意GitLink PR 端点重新打开的 API 可能不完善
# 推荐做法:让作者重新发起 PR

评论失败

# 现象pr +comment 返回 404
# 原因:--id 用了内部 id 而非 pull_request_number
# 处理:确认 id 是网页 URL 中的 pull_request_number

与 Issue 处理的差异

维度 Issue PR
标签 支持 stale/pinned 等 不支持
评论催办
自动关闭 issue +close --number N pr +close --id N
客户端过滤 直接看 status_id 必须看 pull_request_statusstate 参数不可靠)
重开 PATCH status_id=1 API 可能不完善

💡 核心差异PR 没有 label 维度,所有"stale 状态"必须通过评论标题或正文中的 /🔒 emoji 表达。


关键检查点

  • Step 1 完成后,按 pull_request_status == 0 二次过滤
  • Step 2 PR 详情中检查是否有冲突
  • Step 3 展示时明确告知用户"PR 不打标签,仅评论"
  • Step 4 应用前备份 PR 列表

性能数据

阶段 API 调用次数 耗时
Step 1 列表 1 3s
Step 2 详情 N × 1 8s
Step 4 评论+关闭 N × 2 6s
总计4 个 PR 13 ~20s

总结

PR stale 处理的核心要点:

  1. 必过滤 pull_request_status--state 参数不可靠
  2. 仅评论催办 — 不打标签
  3. AI 判断考虑 PR 特性 — 冲突、reviewer、合并状态
  4. 关闭操作可逆性差 — 建议作者重新发起而非自动重开