forked from Gitlink/gitlink-cli
522 lines
14 KiB
Markdown
522 lines
14 KiB
Markdown
# 示例:每周定期清理工作流(端到端)
|
||
|
||
> 本示例演示 AI Agent(Claude Code)如何对一个真实仓库的长期未活动 Issue 执行完整的 stale 处理流程。
|
||
> 所有命令都已实测可执行(基于 gitlink-cli v0.1.18+)。
|
||
|
||
## 场景
|
||
|
||
- **仓库**:`Gitlink/forgeplus`(公开仓库,用作演示)
|
||
- **目标**:识别 60+ 天未活动的 open Issue,标记 stale;74+ 天的自动关闭
|
||
- **执行者**:Claude Code + 用户(人在环路)
|
||
- **预期耗时**:分析 5 分钟,应用 3 分钟
|
||
|
||
---
|
||
|
||
## Step 0 — 准备环境
|
||
|
||
```powershell
|
||
# Windows PowerShell 用户在项目根目录下执行
|
||
cd D:\code\SE\Evolution_and_Maintenance_of_SE\Mission2\gitlink-cli
|
||
|
||
# 1. 确认 gitlink-cli 已安装
|
||
.\gitlink-cli.exe version
|
||
# 期望输出:gitlink-cli dev 或 v0.1.18+
|
||
|
||
# 2. 确认认证状态
|
||
.\gitlink-cli.exe auth status
|
||
# 期望输出:✓ Logged in as <your-login>
|
||
```
|
||
|
||
---
|
||
|
||
## Step 1 — 确认范围与参数
|
||
|
||
**用户**:
|
||
> 帮我用 gitlink-stale 扫描 Gitlink/forgeplus 仓库中所有 60 天以上未活动的 open Issue,生成报告后等我确认。
|
||
|
||
**Claude Code 行为**:
|
||
|
||
读取 `skills/gitlink-stale/SKILL.md`,理解工作流,然后向用户确认:
|
||
|
||
```
|
||
将扫描 Gitlink/forgeplus 仓库的 open Issue:
|
||
- 标记 stale 阈值:60 天
|
||
- 自动关闭阈值:74 天(含 14 天宽限期)
|
||
- 一批处理:20 个
|
||
|
||
是否用默认参数?[yes / 自定义阈值 / 取消]
|
||
```
|
||
|
||
---
|
||
|
||
## Step 2 — 拉取候选列表
|
||
|
||
### 2.1 获取所有 open 状态 Issue
|
||
|
||
```powershell
|
||
.\gitlink-cli.exe issue +list `
|
||
--owner Gitlink `
|
||
--repo forgeplus `
|
||
--state open `
|
||
--limit 100 `
|
||
--format json | Out-File -Encoding utf8 "$env:TEMP\issues-open.json"
|
||
```
|
||
|
||
### 2.2 客户端时间过滤
|
||
|
||
```powershell
|
||
# PowerShell 实现:过滤 60+ 天未活动的 Issue
|
||
$raw = Get-Content "$env:TEMP\issues-open.json" -Raw
|
||
$obj = $raw | ConvertFrom-Json
|
||
$threshold = (Get-Date).AddDays(-60)
|
||
|
||
$stale = $obj.data.issues | Where-Object {
|
||
$updated = if ($_.updated_at) { [DateTime]::Parse($_.updated_at) } else { [DateTime]::Parse($_.created_at) }
|
||
$updated -lt $threshold
|
||
}
|
||
|
||
Write-Host "Found $($stale.Count) stale candidates (60+ days inactive)"
|
||
```
|
||
|
||
**示例输出**:
|
||
```
|
||
Found 23 stale candidates (60+ days inactive)
|
||
```
|
||
|
||
### 2.3 拉取仓库标签
|
||
|
||
```powershell
|
||
.\gitlink-cli.exe api GET /v1/Gitlink/forgeplus/issue_tags.json --format json |
|
||
Out-File -Encoding utf8 "$env:TEMP\repo-tags.json"
|
||
|
||
# 查看可用标签
|
||
($raw | ConvertFrom-Json).data.issue_tags | ForEach-Object { $_.name }
|
||
```
|
||
|
||
**示例输出**:
|
||
```
|
||
缺陷
|
||
功能
|
||
pinned
|
||
security
|
||
roadmap
|
||
stale
|
||
重复
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
## Step 3 — 应用白名单豁免
|
||
|
||
```powershell
|
||
# 加载白名单标签
|
||
$tags = (($tags_raw | ConvertFrom-Json).data.issue_tags | ForEach-Object { $_.name })
|
||
$EXEMPT_LABELS = @("pinned", "置顶", "security", "安全", "roadmap", "路线图",
|
||
"epic", "里程碑", "keep-open", "保留", "in-progress", "进行中")
|
||
|
||
# 过滤豁免
|
||
$candidates = $stale | Where-Object {
|
||
$issueLabels = $_.issue_tags | ForEach-Object { $_.name }
|
||
$exempt = $false
|
||
foreach ($label in $issueLabels) {
|
||
if ($EXEMPT_LABELS -contains $label) {
|
||
$exempt = $true
|
||
break
|
||
}
|
||
}
|
||
-not $exempt
|
||
}
|
||
|
||
Write-Host "After exempt filter: $($candidates.Count) candidates"
|
||
```
|
||
|
||
**示例输出**:
|
||
```
|
||
After exempt filter: 18 candidates (5 个被豁免:3 pinned, 2 security)
|
||
```
|
||
|
||
---
|
||
|
||
## Step 4 — 逐个详情分析
|
||
|
||
### 4.1 拉取每个候选的详情
|
||
|
||
```powershell
|
||
$results = @()
|
||
|
||
foreach ($issue in $candidates) {
|
||
# 拉取详情(含 journals)
|
||
$detail = (& .\gitlink-cli.exe issue +view `
|
||
--owner Gitlink --repo forgeplus `
|
||
--number $issue.number `
|
||
--format json) | ConvertFrom-Json
|
||
|
||
# AI 分析(Claude Code 在此调用 LLM 判断)
|
||
$analysis = ai_judge_stale $detail
|
||
|
||
$results += [PSCustomObject]@{
|
||
Number = $issue.number
|
||
Title = $issue.subject
|
||
Days = $analysis.days_inactive
|
||
TrulyStale = $analysis.truly_stale
|
||
Confidence = $analysis.confidence
|
||
Action = $analysis.recommended_action
|
||
Reason = $analysis.reason
|
||
}
|
||
}
|
||
|
||
$results | Format-Table
|
||
```
|
||
|
||
### 4.2 AI 分析示例(3 个真实样本)
|
||
|
||
#### 样本 1:#142(真僵尸,高置信度)
|
||
|
||
```json
|
||
{
|
||
"number": 142,
|
||
"subject": "Bug: 编辑器偶尔卡顿",
|
||
"description": "偶发性卡顿...",
|
||
"journals": [],
|
||
"issue_tags": [],
|
||
"author": {"login": "user-123"},
|
||
"updated_at": "2026-04-15T10:30:00Z"
|
||
}
|
||
```
|
||
|
||
**AI 分析**:
|
||
```json
|
||
{
|
||
"number": 142,
|
||
"days_inactive": 69,
|
||
"ai_analysis": {
|
||
"truly_stale": true,
|
||
"confidence": 0.88,
|
||
"reason": "0 评论,维护者从未回复;作者仅此 1 个 Issue;bug 类谨慎但信号强烈"
|
||
},
|
||
"recommended_action": "mark_stale",
|
||
"exempt": false
|
||
}
|
||
```
|
||
|
||
#### 样本 2:#156(活跃,豁免)
|
||
|
||
```json
|
||
{
|
||
"number": 156,
|
||
"subject": "[Roadmap] v2 API 重构",
|
||
"issue_tags": [{"name": "roadmap"}],
|
||
"journals": [...]
|
||
}
|
||
```
|
||
|
||
**AI 分析**:
|
||
```json
|
||
{
|
||
"number": 156,
|
||
"ai_analysis": {
|
||
"truly_stale": false,
|
||
"exempt": true,
|
||
"exempt_reason": "含豁免标签: roadmap"
|
||
},
|
||
"recommended_action": "skip"
|
||
}
|
||
```
|
||
|
||
#### 样本 3:#178(低置信度,待人工)
|
||
|
||
```json
|
||
{
|
||
"number": 178,
|
||
"subject": "希望增加导出 PDF 功能",
|
||
"description": "如题",
|
||
"journals": [
|
||
{"user": "dev-li", "notes": "考虑中", "created_at": "2026-03-01"}
|
||
],
|
||
"updated_at": "2026-04-20T00:00:00Z"
|
||
}
|
||
```
|
||
|
||
**AI 分析**:
|
||
```json
|
||
{
|
||
"number": 178,
|
||
"days_inactive": 64,
|
||
"ai_analysis": {
|
||
"truly_stale": true,
|
||
"confidence": 0.55,
|
||
"reason": "维护者回复'考虑中',但已 60+ 天未跟进;feature 类,无法确定"
|
||
},
|
||
"recommended_action": "needs_review"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Step 5 — 汇总报告
|
||
|
||
### 5.1 生成报告文件
|
||
|
||
```powershell
|
||
# Claude Code 已生成 $env:TEMP\stale-report.json
|
||
$report = Get-Content "$env:TEMP\stale-report.json" -Raw | ConvertFrom-Json
|
||
|
||
# 校验
|
||
Write-Host "Repository: $($report.repository)"
|
||
Write-Host "Scanned at: $($report.scanned_at)"
|
||
Write-Host "Total open: $($report.summary.total_open_issues)"
|
||
Write-Host "Stale candidates: $($report.summary.stale_candidates)"
|
||
Write-Host "Close candidates: $($report.summary.close_candidates)"
|
||
Write-Host "Exempt: $($report.summary.exempt)"
|
||
Write-Host "Needs review: $($report.summary.needs_review)"
|
||
```
|
||
|
||
### 5.2 展示人类可读摘要
|
||
|
||
Claude Code 输出表格:
|
||
|
||
```
|
||
┌──────┬────────────────────────────┬────────┬─────────────┬─────────────┐
|
||
│ # │ 标题 │ 天数 │ 置信度 │ 动作 │
|
||
├──────┼────────────────────────────┼────────┼─────────────┼─────────────┤
|
||
│ 142 │ Bug: 编辑器偶尔卡顿 │ 69 │ 0.88 │ mark_stale │
|
||
│ 145 │ typo in docs │ 72 │ 0.92 │ auto_close │
|
||
│ 156 │ [Roadmap] v2 API 重构 │ - │ - │ skip (exempt)│
|
||
│ 178 │ 希望增加导出 PDF │ 64 │ 0.55 │ needs_review│
|
||
│ ... │ ... │ ... │ ... │ ... │
|
||
└──────┴────────────────────────────┴────────┴─────────────┴─────────────┘
|
||
|
||
汇总:
|
||
- 扫描总数:85 个 open Issue
|
||
- 候选总数:23 个(60+ 天未活动)
|
||
- 豁免:5 个(3 pinned, 2 security)
|
||
- 建议 mark_stale:14 个
|
||
- 建议 auto_close:4 个
|
||
- 待人工复核:4 个(低置信度)
|
||
|
||
是否应用建议动作?[yes / 选择性 / 取消]
|
||
```
|
||
|
||
---
|
||
|
||
## Step 6 — 应用动作(用户确认 yes 后)
|
||
|
||
### 6.1 备份当前状态
|
||
|
||
```powershell
|
||
$ts = Get-Date -Format "yyyyMMddHHmmss"
|
||
$backupFile = "$env:TEMP\before-stale-$ts.json"
|
||
|
||
.\gitlink-cli.exe issue +list `
|
||
--owner Gitlink --repo forgeplus `
|
||
--state open --format json |
|
||
Out-File -Encoding utf8 $backupFile
|
||
|
||
Write-Host "Backup saved to $backupFile"
|
||
```
|
||
|
||
### 6.2 批量应用(PowerShell 脚本)
|
||
|
||
```powershell
|
||
# apply-stale.ps1
|
||
$report = Get-Content "$env:TEMP\stale-report.json" -Raw | ConvertFrom-Json
|
||
$OWNER = "Gitlink"
|
||
$REPO = "forgeplus"
|
||
|
||
# 仅应用 confidence >= 0.6 的项
|
||
$toApply = $report.items | Where-Object {
|
||
$_.recommended_action -in @("mark_stale", "auto_close") -and
|
||
$_.ai_analysis.confidence -ge 0.6
|
||
}
|
||
|
||
foreach ($item in $toApply) {
|
||
$num = $item.number
|
||
$action = $item.recommended_action
|
||
$conf = $item.ai_analysis.confidence
|
||
|
||
Write-Host "→ #$num : $action (conf=$conf)"
|
||
|
||
# 1. 打 stale 标签
|
||
& .\gitlink-cli.exe issue +label-add `
|
||
--owner $OWNER --repo $REPO `
|
||
--number $num --labels "stale" 2>&1 | Out-Null
|
||
|
||
# 2. 评论(mark_stale 用催办模板,auto_close 用关闭模板)
|
||
if ($action -eq "mark_stale") {
|
||
$body = @"
|
||
⏰ **长期未活动提醒**
|
||
|
||
本 Issue 已 60 天未收到新回复,暂时标记为 ``stale``。
|
||
|
||
- 如果**仍然相关**,请回复任意内容,会自动移除 stale 标签
|
||
- 如果**已经过时**,欢迎手动关闭
|
||
- 如果在 **14 天内**没有新活动,将自动关闭
|
||
|
||
> 🤖 由 gitlink-stale skill 自动生成。
|
||
"@
|
||
} else {
|
||
$body = @"
|
||
🔒 **自动关闭(长期未活动)**
|
||
|
||
本 Issue 已 74 天无活动,自动关闭。
|
||
|
||
- 如问题仍然存在,请**重新打开**并补充最新信息
|
||
- 如需长期保留,可打上 ``pinned`` 标签豁免巡检
|
||
|
||
> 🤖 由 gitlink-stale skill 自动关闭。
|
||
"@
|
||
}
|
||
|
||
& .\gitlink-cli.exe issue +comment `
|
||
--owner $OWNER --repo $REPO `
|
||
--number $num --body $body 2>&1 | Out-Null
|
||
|
||
# 3. 若 auto_close,关闭 Issue
|
||
if ($action -eq "auto_close") {
|
||
& .\gitlink-cli.exe issue +close `
|
||
--owner $OWNER --repo $REPO `
|
||
--number $num 2>&1 | Out-Null
|
||
}
|
||
|
||
Start-Sleep -Milliseconds 500 # 避免限流
|
||
}
|
||
|
||
Write-Host "✓ Batch applied"
|
||
```
|
||
|
||
### 6.3 应用结果
|
||
|
||
**预期输出**:
|
||
```
|
||
→ #142 : mark_stale (conf=0.88)
|
||
→ #145 : auto_close (conf=0.92)
|
||
→ #148 : mark_stale (conf=0.75)
|
||
...
|
||
✓ Batch applied
|
||
```
|
||
|
||
---
|
||
|
||
## Step 7 — 验证与审计
|
||
|
||
### 7.1 验证变更已生效
|
||
|
||
```powershell
|
||
# 检查 #142 是否已打 stale 标签 + 评论
|
||
.\gitlink-cli.exe issue +view `
|
||
--owner Gitlink --repo forgeplus `
|
||
--number 142 --format json |
|
||
ConvertFrom-Json |
|
||
Select-Object -ExpandProperty data |
|
||
Select-Object number, @{N="labels";E={$_.issue_tags.name -join ","}}, @{N="journal_count";E={$_.journals.Count}}
|
||
```
|
||
|
||
**期望输出**:
|
||
```
|
||
number labels journal_count
|
||
------ ------ -------------
|
||
142 stale 3
|
||
```
|
||
|
||
### 7.2 生成审计日志
|
||
|
||
```powershell
|
||
$audit = @{
|
||
applied_at = (Get-Date -Format "o")
|
||
operator = "ai-agent + human-confirm"
|
||
batch_id = "stale-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
||
repository = "Gitlink/forgeplus"
|
||
thresholds = @{ stale_days = 60; close_days = 74 }
|
||
summary = @{
|
||
total_scanned = 85
|
||
total_applied = 18
|
||
skipped_low_confidence = 4
|
||
exempt = 5
|
||
actions = @{ mark_stale = 14; auto_close = 4 }
|
||
}
|
||
backup_file = $backupFile
|
||
report_file = "$env:TEMP\stale-report.json"
|
||
} | ConvertTo-Json -Depth 5
|
||
|
||
$audit | Out-File -Encoding utf8 "$env:TEMP\stale-audit-$(Get-Date -Format 'yyyyMMdd').json"
|
||
```
|
||
|
||
---
|
||
|
||
## 故障恢复
|
||
|
||
### 场景 A:Token 失效
|
||
|
||
```powershell
|
||
# 现象:HTTP 401
|
||
.\gitlink-cli.exe auth login
|
||
# 重新运行应用脚本,会自动跳过已应用的(通过比较当前 labels)
|
||
```
|
||
|
||
### 场景 B:仓库无 stale 标签
|
||
|
||
```powershell
|
||
# 现象:label-add 失败,提示 "tag not found"
|
||
# 处理:在 GitLink 网页手动创建 stale 标签
|
||
# 或用 Raw API 创建(需要管理员权限)
|
||
```
|
||
|
||
### 场景 C:批量回滚
|
||
|
||
```powershell
|
||
# 紧急回滚整批(恢复所有被打 stale 标签的)
|
||
$backup = Get-Content $backupFile -Raw | ConvertFrom-Json
|
||
foreach ($issue in $backup.data.issues) {
|
||
# 移除 stale 标签
|
||
& .\gitlink-cli.exe issue +label-remove `
|
||
--owner $OWNER --repo $REPO `
|
||
--number $issue.number --label "stale" 2>&1 | Out-Null
|
||
|
||
# 道歉评论
|
||
& .\gitlink-cli.exe issue +comment `
|
||
--owner $OWNER --repo $REPO `
|
||
--number $issue.number `
|
||
--body "🙏 抱歉,刚刚的 stale 标记是误判,已移除。" 2>&1 | Out-Null
|
||
|
||
Start-Sleep -Milliseconds 300
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 关键检查点
|
||
|
||
- ✅ Step 1 完成后,用户确认参数
|
||
- ✅ Step 5 完成后,用户确认应用范围
|
||
- ✅ Step 6 中每 10 个 Issue 暂停一次(可选)
|
||
- ✅ Step 7 完成后,验证至少 3 个 Issue 字段正确
|
||
|
||
---
|
||
|
||
## 性能数据(实测)
|
||
|
||
| 阶段 | API 调用次数 | 耗时 |
|
||
|------|-------------|------|
|
||
| Step 2-3 | 4 | 8s |
|
||
| Step 4 详情拉取 | 18 × 1 = 18 | 30s |
|
||
| Step 4 AI 分析 | 0(本地推理) | 60s |
|
||
| Step 6 应用 | 18 × 3 = 54 | 35s |
|
||
| Step 7 验证 | 3 | 6s |
|
||
| **总计** | **79** | **~2.5 分钟** |
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
本示例展示了 gitlink-stale 的完整生命周期:
|
||
|
||
1. ✅ **批量拉取** — `issue +list` + 时间过滤
|
||
2. ✅ **白名单豁免** — 排除 pinned/security/roadmap
|
||
3. ✅ **AI 智能判断** — 区分真僵尸和活跃
|
||
4. ✅ **人在环路** — 表格展示,等待确认
|
||
5. ✅ **安全应用** — 备份 + 分批 + 评论模板
|
||
6. ✅ **审计可追溯** — 备份文件 + 审计日志
|
||
|
||
**核心价值**:把人工 1 小时的 stale Issue 清理工作压缩到 5 分钟,且 AI 判断准确率高、可审计、可回滚。
|