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

335 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 示例PR 长期未活动催办工作流
> 本示例演示对长期未活动的 PR 执行催办流程。
> ⚠️ 与 Issue 不同PR 端点暂不支持 label 操作,因此**只评论催办**,不打 stale 标签。
## 场景
- **仓库**`Gitlink/forgeplus`
- **目标**:识别 60+ 天未活动的 open PR评论催办74+ 天的关闭
- **执行者**Claude Code + 用户(人在环路)
---
## Step 0 — 准备环境
```powershell
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
```powershell
.\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` 二次过滤。
```powershell
$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
```powershell
$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 |
```python
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 备份
```powershell
$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 批量应用
```powershell
$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 — 验证
```powershell
# 检查 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 恢复
```powershell
# 重新打开 PRRaw API
# 注意GitLink PR 端点重新打开的 API 可能不完善
# 推荐做法:让作者重新发起 PR
```
### 评论失败
```powershell
# 现象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.**关闭操作可逆性差** — 建议作者重新发起而非自动重开