---
name: gitlink-stale
version: 1.0.0
description: "Stale Issue/PR 自动处理：识别长期未活动的 Issue/PR，标记 stale 标签、通知相关者、过期自动关闭。当用户需要清理堆积 Issue/PR、定期巡检仓库、或想仿照 GitHub stale-bot 行为时触发。"
metadata:
  requires:
    bins: ["gitlink-cli"]
  cliHelp: "gitlink-cli issue --help"
---

# gitlink-stale（Stale Issue/PR 自动处理）

**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)，其中包含认证、权限处理和 API 注意事项。**
**CRITICAL — 所有"标记/评论/关闭"动作默认 dry-run；只有用户明确确认后才执行写入。**
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`（GitHub CLI）操作 GitLink 资源。**

> **前置依赖：** 先阅读 [`../gitlink-issue/SKILL.md`](../gitlink-issue/SKILL.md) 了解 Issue 基础操作和字段映射，[`../gitlink-pr/SKILL.md`](../gitlink-pr/SKILL.md) 了解 PR 基础操作。

---

## 1. 这个 Skill 做什么？

`gitlink-stale` 是一个 **AI Agent 驱动的长期未活动 Issue/PR 处理工作流**，解决开源/协作项目中常见的"Issue/PR 堆积无人理"问题：

- 🔍 **扫描识别**：找出 N 天未活动的 Issue/PR（默认 60 天）
- 🧠 **AI 智能判断**：区分"真僵尸"和"等维护者回复"（不是简单按时间一刀切）
- 🏷️ **自动打 stale 标签**：通知作者/相关者
- 💬 **友好催办评论**：避免简单粗暴的"过期警告"
- 🔒 **过期自动关闭**：超过宽限期（默认 14 天）仍未响应才关闭
- 🛡️ **白名单豁免**：`pinned`/`security`/`roadmap` 等关键 Issue 永不处理
- ✅ **Dry-run 优先**：所有写入操作默认预览，确认后才落地

适合**所有 Issue/PR 长期堆积**的开源项目或团队仓库，承担类似 GitHub `stale` bot 的角色，但通过 AI Agent 实现"人在环路"和"智能判断"。

---

## 2. 工作流总览

```
                 ┌─────────────────────────────────┐
                 │ Step 1: 扫描候选列表              │
                 │   issue +list --state open       │
                 │   pr +list --state open          │
                 └────────────┬────────────────────┘
                              ▼
       ┌──────────────────────────────────────────────┐
       │ Step 2: 时间过滤                              │
       │   now - updated_at > stale_days (默认 60)    │
       │   排除白名单（pinned/security/roadmap/...）  │
       └────────────┬─────────────────────────────────┘
                    ▼
       ┌──────────────────────────────────────────────┐
       │ Step 3: AI 智能分类（核心差异化）             │
       │   - 是否仍在等维护者回复？                    │
       │   - 是否是关键功能/路线图？                   │
       │   - 是否历史活跃度高？                       │
       │   - 输出 confidence + recommendation         │
       └────────────┬─────────────────────────────────┘
                    ▼
       ┌──────────────────────────────────────────────┐
       │ Step 4: 生成处理计划（dry-run）               │
       │   {                                          │
       │     issue_id: 142,                           │
       │     action: "mark_stale",                    │
       │     reason: "60 天未活动",                   │
       │     confidence: 0.85                         │
       │   }                                          │
       └────────────┬─────────────────────────────────┘
                    ▼
       ┌──────────────────────────────────────────────┐
       │ Step 5: 用户确认 → 执行                       │
       │   issue +label-add --label stale             │
       │   issue +comment --body "..."                │
       │   过宽限期: issue +close                     │
       └──────────────────────────────────────────────┘
```

---

## 3. Shortcuts 与 Raw API 速查

本 Skill 复用 gitlink-cli 已有命令，**不新增 shortcut**，确保单一可信源。

### 3.1 读操作（只读，可放心使用）

| 命令 | 用途 |
|------|------|
| `issue +list --state open --format json` | 获取 open Issue 列表 |
| `issue +view --number <N> --format json` | 获取 Issue 详情（含 journals 评论历史） |
| `issue +label-list --number <N> --format json` | 查看 Issue 当前标签 |
| `pr +list --state open --format json` | 获取 open PR 列表（注意需客户端按 `pull_request_status: 0` 二次过滤） |
| `pr +view --id <N> --format json` | 获取 PR 详情 |
| `api GET /v1/:owner/:repo/issue_tags.json` | 获取仓库标签（name→id 映射） |

### 3.2 写操作（默认 dry-run，确认后执行）

| 命令 | 用途 |
|------|------|
| `issue +label-add --number <N> --labels "stale"` | 给 Issue 打 stale 标签 |
| `issue +label-remove --number <N> --label "stale"` | 移除 stale 标签（用户回复后恢复） |
| `issue +comment --number <N> --body "<text>"` | 评论催办/关闭说明 |
| `issue +close --number <N>` | 关闭 Issue |
| `issue +batch-close --numbers <csv> --dry-run` | 批量关闭预览 |
| `pr +comment --id <N> --body "<text>"` | 给 PR 评论 |

> ⚠️ **PR 标签**：GitLink PR 端点暂不支持 PR 维度的 label 操作，对 PR 只评论、不打标签；若需要"PR stale 标签"，在评论正文中显式写"⚠️ Stale"。

---

## 4. 核心算法（AI 智能判断）

> 这是本 Skill 与"简单按时间一刀切"工具的核心差异。
> AI Agent 应**优先**遵循以下规则，对规则无法覆盖的情况使用语义判断。

### 4.1 三阶段判断流水线

```
Issue/PR JSON
    │
    ▼
┌─────────────────────────────────┐
│ Stage A: 时间扫描                │
│ - days_inactive = now - updated │
│ - 默认阈值: 60 天                │
└────────────┬────────────────────┘
             ▼
┌─────────────────────────────────┐
│ Stage B: 白名单豁免              │
│ - 含 pinned/security 等标签 → 跳过│
│ - tracker 是 roadmap/epic → 跳过 │
└────────────┬────────────────────┘
             ▼
┌─────────────────────────────────┐
│ Stage C: AI 真假僵尸判断         │
│ - 评论历史分析                   │
│ - 等维护者回复？等用户回复？      │
│ - 输出 truly_stale + confidence  │
└─────────────────────────────────┘
```

### 4.2 时间计算

**关键字段**：`updated_at`（Issue 最后活动时间，包括评论、状态变更、字段修改）

```python
days_inactive = (now_utc - parse(issue.updated_at)).days

# 阈值（可通过参数自定义）
if days_inactive >= close_threshold:    # 默认 74 天（60 + 14 宽限期）
    candidate_action = "auto_close"
elif days_inactive >= stale_threshold:  # 默认 60 天
    candidate_action = "mark_stale"
else:
    candidate_action = None  # 不处理
```

> ⚠️ **GitLink API 已知行为**：`updated_at` 字段在某些 Issue 上可能缺失或时区异常。降级策略：取 `journals` 数组最后一条的 `created_at` 作为最后活动时间。

### 4.3 白名单豁免规则

满足**任一**条件即跳过处理：

| 信号 | 说明 |
|------|------|
| 含 `pinned`/`置顶` 标签 | 重要 Issue |
| 含 `security`/`安全` 标签 | 安全相关 |
| 含 `roadmap`/`路线图` 标签 | 长期规划 |
| 含 `epic`/`里程碑` 标签 | 大任务 |
| tracker_id 是 `roadmap` 类型 | 路线图类 |
| priority_id == 4 (urgent) | 紧急任务 |
| 标题含 `[Keep Open]`/`[Pinned]` | 显式标记 |
| 作者仍是仓库活跃成员 | 信任作者会跟进 |

### 4.4 AI 真假僵尸判断（Stage C 核心）

**关键差异化**：不是简单的"时间到了就标记"，而是 AI 判断是否真的应该处理。

**判断信号**：

| 信号类型 | 僵尸（应处理） | 活跃（应跳过） |
|---------|---------------|---------------|
| 评论历史 | 维护者 0 回复，或仅"已知问题"占位 | 维护者最近 30 天内回复过 |
| Issue 类型 | bug/feature/小需求，需要明确动作 | question/讨论类，已自然结束 |
| 标签 | 无标签或仅 stale | `in-progress`/`under-review` |
| 评论数 | 0 评论，长期无人理 | ≥5 评论，有讨论 |
| 作者活跃度 | 0 issue 历史，可能是路过用户 | 资深贡献者，会跟进 |
| 关键词 | "测试"、"占位"、"无复现" | "正在处理"、"待 v2"、"等待上游" |

**AI 决策输出**：

```json
{
  "issue_number": 142,
  "title": "...",
  "last_activity": "2026-04-15T10:30:00Z",
  "days_inactive": 62,
  "ai_analysis": {
    "truly_stale": true,
    "confidence": 0.85,
    "reason": "用户最后回复 60 天前，维护者无回复，0 评论，作者仅此 1 个 Issue",
    "exempt": false
  },
  "recommended_action": "mark_stale",
  "next_review_date": "2026-06-30"
}
```

### 4.5 置信度阈值

| confidence | 建议动作 |
|-----------|---------|
| ≥ 0.8 | 直接列入"建议执行"清单 |
| 0.6 - 0.8 | 列入"建议执行"，但报告中标记"建议人工复核" |
| < 0.6 | **不自动处理**，仅列入"待人工判断"队列 |

---

## 5. 标准工作流（AI Agent 执行模板）

> **AI Agent 看这里**：以下是你被请求"清理 stale Issue/PR"时应遵循的标准流程。

### Step 1 — 确认范围与参数

```bash
# 默认参数（可被用户覆盖）
# - stale_threshold: 60 天
# - close_threshold: 74 天（60 + 14 宽限期）
# - batch_size: 20 个/批

# 确认 owner/repo
gitlink-cli issue +list --state open --limit 5 --format json
```

向用户确认："要扫描哪个仓库？阈值用默认（60 天标记/74 天关闭）还是自定义？想一批处理多少个？"

### Step 2 — 拉取候选列表

```bash
# Issue 候选
gitlink-cli issue +list \
    --owner <owner> --repo <repo> \
    --state open \
    --limit 100 \
    --format json > /tmp/issues-open.json

# PR 候选
gitlink-cli pr +list \
    --owner <owner> --repo <repo> \
    --state open \
    --format json > /tmp/prs-open.json
```

### Step 3 — 拉取仓库标签（用于白名单判断）

```bash
gitlink-cli api GET /v1/<owner>/<repo>/issue_tags.json --format json \
    > /tmp/repo-tags.json
```

### Step 4 — 逐个分析

对每个候选项：

```bash
# Issue 详情（含 journals）
gitlink-cli issue +view --number <N> --format json

# PR 详情
gitlink-cli pr +view --id <N> --format json
```

应用第 4 节判断规则，生成分析结果。

### Step 5 — 汇总报告

把所有候选项的分析结果合并：

```json
{
  "repository": "owner/repo",
  "scanned_at": "2026-06-23T10:00:00Z",
  "thresholds": {
    "stale_days": 60,
    "close_days": 74
  },
  "summary": {
    "total_open_issues": 85,
    "total_open_prs": 12,
    "stale_candidates": 23,
    "close_candidates": 8,
    "exempt": 15,
    "needs_review": 4
  },
  "items": [
    /* 每个候选项的详细分析 */
  ]
}
```

**用表格形式向用户展示摘要**（人类可读），等待用户确认。

### Step 6 — 应用动作（用户确认后）

按推荐动作执行：

```bash
# 动作 A：标记 stale
gitlink-cli issue +label-add --number 142 --labels "stale"
gitlink-cli issue +comment --number 142 --body "⏰ 本 Issue 已 60 天无活动..."

# 动作 B：自动关闭（超过宽限期）
gitlink-cli issue +label-add --number 142 --labels "stale"
gitlink-cli issue +comment --number 142 --body "🔒 本 Issue 已 74 天无活动，自动关闭..."
gitlink-cli issue +close --number 142

# 动作 C：PR 催办（不打标签，仅评论）
gitlink-cli pr +comment --id 8 --body "⏰ 本 PR 已 60 天无活动..."
```

---

## 6. 催办评论模板

### 6.1 标记 stale（友好版，非警告）

```markdown
⏰ **长期未活动提醒**

本 Issue 已 60 天未收到新回复，暂时标记为 `stale`。

- 如果**仍然相关**，请回复任意内容，会自动移除 stale 标签
- 如果**已经过时**，欢迎手动关闭
- 如果在 **14 天内**没有新活动，将自动关闭以保持 Issue 列表清爽

> 🤖 由 gitlink-stale skill 自动生成。如有疑问请联系 @维护者。
```

### 6.2 自动关闭（礼貌版）

```markdown
🔒 **自动关闭（长期未活动）**

本 Issue 自标记 stale 后 14 天内仍未收到新回复，自动关闭。

- 如问题仍然存在，请**重新打开**并补充最新信息
- 如需长期保留，可打上 `pinned` 标签豁免巡检

> 🤖 由 gitlink-stale skill 自动关闭。原始讨论保留在历史中。
```

### 6.3 PR 催办

```markdown
⏰ **PR 长期未活动**

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

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

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

---

## 7. 安全规则

| 规则 | 说明 |
|------|------|
| ✅ **Dry-run 优先** | 分析阶段只读，不调用任何写 API |
| ✅ **用户确认** | 应用变更前必须展示报告并征得同意 |
| ✅ **白名单豁免** | pinned/security/roadmap 永不动 |
| ✅ **AI 双重判断** | 不仅看时间，还要 AI 判断"真僵尸" |
| ✅ **小批量** | 一批不超过 20 个，超 50 强制分批 |
| ✅ **低置信度跳过** | confidence < 0.6 不自动处理 |
| ✅ **可回滚** | 记录原始标签和状态，便于撤销 |
| ❌ **禁止** | 批量关闭超过 50 个 Issue 而不分批确认 |
| ❌ **禁止** | 跳过 dry-run 直接执行 |

---

## 8. 回滚策略

### 8.1 备份原始状态

```bash
# 应用前导出当前 Issue 状态
gitlink-cli issue +list --state open --format json > /tmp/before-stale-$(date +%s).json
```

### 8.2 单 Issue 回滚

```bash
# 误标的 Issue：移除 stale 标签 + 道歉评论
gitlink-cli issue +label-remove --number 142 --label "stale"
gitlink-cli issue +comment --number 142 --body "抱歉，刚刚的 stale 标记是误判，已恢复。"
```

### 8.3 误关闭的 Issue 恢复

```bash
# 重新打开（用 Raw API，因 gitlink-cli +update 需要状态参数）
gitlink-cli api PATCH /v1/<owner>/<repo>/issues/142 \
    --body '{"subject":"<原>","description":"<原>","status_id":1}'  # 1=open
gitlink-cli issue +label-remove --number 142 --label "stale"
```

---

## 9. 与现有 Skills 的关系

| Skill | 关系 |
|-------|------|
| [`gitlink-shared`](../gitlink-shared/SKILL.md) | 前置必读：认证、错误处理、安全规则 |
| [`gitlink-issue`](../gitlink-issue/SKILL.md) | 基础命令来源：所有写操作通过这里的 shortcut |
| [`gitlink-pr`](../gitlink-pr/SKILL.md) | PR 操作来源 |
| [`gitlink-issue-triage`](../gitlink-issue-triage/SKILL.md) | 互补：triage 处理"未分类"，stale 处理"未活动" |

---

## 10. 参考文档

- [扫描算法详解](references/gitlink-stale-scan.md) — 时间计算、字段降级、批量策略
- [AI 判断规则详解](references/gitlink-stale-judge.md) — 真假僵尸判断的完整信号集
- [动作执行手册](references/gitlink-stale-actions.md) — 写操作命令清单、评论模板、回滚策略
- [白名单豁免规则](references/gitlink-stale-exempt.md) — 哪些 Issue 永不处理
- [每周清理工作流示例](examples/weekly-cleanup-workflow.md) — 端到端定期巡检
- [PR 催办示例](examples/pr-stale-workflow.md) — PR 维度的处理流程
- [AI 判断演示](examples/ai-judgment-demo.md) — 复杂 Issue 的判断示例

---

## 11. 常见问题

**Q: 为什么不用一个固定的 stale-bot 配置文件？**
A: 因为不同 Issue 的"重要程度"差异巨大。AI Agent 可以读取评论历史判断"是否还在等维护者"，比规则引擎更准。

**Q: 用户回复后会自动去掉 stale 标签吗？**
A: 默认不会自动响应。本 Skill 是按需触发（如每周巡检），下次扫描时会看到新活动并自动跳过；如果要立刻移除，可手动调用 `issue +label-remove`。详见 [references/gitlink-stale-actions.md](references/gitlink-stale-actions.md) §3。

**Q: 一次处理多少 Issue 合适？**
A: 建议 10-20 个/批。超过 50 个时强制分批，每批之间用户确认。

**Q: 误关了重要 Issue 怎么办？**
A: 见 §8.3 回滚策略。所有动作都保留原始字段快照，可重新打开。强烈建议 urgent/roadmap 类 Issue 打上对应标签加入白名单。

**Q: PR 没有 label 接口怎么办？**
A: GitLink PR 端点暂不支持 PR 维度的标签。对 PR 只做评论催办，不打 stale 标签；如需"PR 已 stale"的视觉提示，在评论标题中显式写"⏰"或"Stale"。

**Q: AI 判断和简单时间过滤冲突时怎么办？**
A: AI 判断优先。如果 AI 认为"仍在等维护者回复"，即使超过 60 天也不打 stale 标签。所有"非规则决策"会在报告中高亮，便于人工复核。
