# gitlink-stale — 动作执行手册

> 本文档说明如何把分析报告中的推荐动作**安全地**应用到 GitLink Issue/PR。
> 所有命令默认 dry-run，确认后再去掉 `--dry-run` 实际执行。

## 1. 应用前置检查

### 1.1 备份当前状态

```bash
# 导出当前所有目标 Issue/PR 的原始字段（用于回滚）
gitlink-cli issue +list --owner <owner> --repo <repo> --state open --format json \
    > /tmp/before-stale-$(date +%s).json
```

### 1.2 确认权限

```bash
# 检查当前用户对该仓库的写权限
gitlink-cli user +me --format json
gitlink-cli api GET /:owner/:repo --format json | jq '.data.permissions'
```

若 `permissions.push !== true`，所有写操作会失败，应停止并提示用户。

### 1.3 确认仓库有 stale 标签

```bash
# 检查仓库标签是否存在 stale
gitlink-cli api GET /v1/<owner>/<repo>/issue_tags.json --format json \
    | jq '.data.issue_tags[] | select(.name == "stale")'

# 如果不存在，提示用户手动创建（或通过 Raw API 创建）
# 强烈建议由人工创建，避免 Skill 越权
```

---

## 2. 三种推荐动作

### 2.1 动作 A：mark_stale（标记 stale）

**触发条件**：
- `days_inactive >= 60`（stale 阈值）
- `ai_analysis.truly_stale == true`
- `confidence >= 0.6`

**执行命令**：

```bash
# 1. 打 stale 标签
gitlink-cli issue +label-add \
    --owner <owner> --repo <repo> \
    --number <N> \
    --labels "stale"

# 2. 评论催办（友好版，非警告）
gitlink-cli issue +comment \
    --owner <owner> --repo <repo> \
    --number <N> \
    --body "⏰ **长期未活动提醒**

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

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

> 🤖 由 gitlink-stale skill 自动生成。"
```

### 2.2 动作 B：auto_close（自动关闭）

**触发条件**：
- `days_inactive >= 74`（close 阈值）
- `ai_analysis.truly_stale == true`
- `confidence >= 0.8`

**执行命令**：

```bash
# 1. 确保 stale 标签存在（如果之前没打，先打）
gitlink-cli issue +label-add \
    --owner <owner> --repo <repo> \
    --number <N> \
    --labels "stale"

# 2. 评论关闭说明
gitlink-cli issue +comment \
    --owner <owner> --repo <repo> \
    --number <N> \
    --body "🔒 **自动关闭（长期未活动）**

本 Issue 已 74 天无活动，自动关闭。

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

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

# 3. 关闭 Issue
gitlink-cli issue +close \
    --owner <owner> --repo <repo> \
    --number <N>
```

### 2.3 动作 C：PR 催办（PR stale）

> ⚠️ PR 端点暂不支持 label 操作，仅评论催办。

**执行命令**：

```bash
gitlink-cli pr +comment \
    --owner <owner> --repo <repo> \
    --id <N> \
    --body "⏰ **PR 长期未活动**

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

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

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

> 🤖 由 gitlink-stale skill 自动生成。"
```

如果 PR 也超过 close 阈值（74 天）：

```bash
gitlink-cli pr +comment \
    --owner <owner> --repo <repo> \
    --id <N> \
    --body "🔒 **PR 自动关闭（长期未活动）**

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

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

> 🤖 由 gitlink-stale skill 自动关闭。"

gitlink-cli pr +close \
    --owner <owner> --repo <repo> \
    --id <N>
```

---

## 3. 用户回复后移除 stale 标签

默认情况下，本 Skill 是按需触发（如每周巡检），**不会**自动监听回复事件。

但如果用户希望"用户回复后立刻移除 stale 标签"，可以单独触发：

```bash
# 检查某 Issue 是否有新回复
CURRENT=$(gitlink-cli issue +view --owner <owner> --repo <repo> \
    --number <N> --format json)

HAS_STALE=$(echo "$CURRENT" | jq '[.data.issue_tags[] | select(.name == "stale")] | length > 0')
LAST_JOURNAL_USER=$(echo "$CURRENT" | jq -r '.data.journals[-1].user.login')
AUTHOR=$(echo "$CURRENT" | jq -r '.data.author.login')

if [ "$HAS_STALE" = "true" ] && [ "$LAST_JOURNAL_USER" = "$AUTHOR" ]; then
    # 作者回复了 → 移除 stale
    gitlink-cli issue +label-remove \
        --owner <owner> --repo <repo> \
        --number <N> \
        --label "stale"
    
    gitlink-cli issue +comment \
        --owner <owner> --repo <repo> \
        --number <N> \
        --body "✅ 检测到作者回复，已移除 stale 标签。"
fi
```

> 💡 **推荐做法**：用 webhook 监听 Issue 评论事件，触发本段脚本，实现"自动响应回复"。详见 [`../gitlink-webhook/SKILL.md`](../../gitlink-webhook/SKILL.md)。

---

## 4. 批量应用模板

### 4.1 Shell 脚本（推荐）

```bash
#!/usr/bin/env bash
# apply-stale.sh — 从 report.json 应用 stale 动作
set -euo pipefail

OWNER="${1:?usage: apply-stale.sh <owner> <repo> <report.json>}"
REPO="${2:?missing repo}"
REPORT="${3:?missing report.json}"

# 读取报告
TOTAL=$(jq '.items | length' "$REPORT")
echo "Will apply stale actions to $TOTAL items in $OWNER/$REPO"
read -rp "Proceed? (yes/no) " CONFIRM
[ "$CONFIRM" = "yes" ] || { echo "aborted"; exit 1; }

# 备份
gitlink-cli issue +list --owner "$OWNER" --repo "$REPO" --state open --format json \
    > "/tmp/before-stale-$(date +%s).json"

# 逐条应用
jq -c '.items[]' "$REPORT" | while read -r item; do
    TYPE=$(echo "$item" | jq -r '.type')
    NUM=$(echo "$item" | jq '.number')
    ACTION=$(echo "$item" | jq -r '.recommended_action')
    CONF=$(echo "$item" | jq '.ai_analysis.confidence')
    
    echo "→ #$NUM ($TYPE): $ACTION (conf=$CONF)"
    
    # 跳过低置信度
    if (( $(echo "$CONF < 0.6" | bc -l) )); then
        echo "  skipped (low confidence)"
        continue
    fi
    
    # 按动作执行
    case "$ACTION" in
        mark_stale)
            apply_mark_stale "$OWNER" "$REPO" "$TYPE" "$NUM"
            ;;
        auto_close)
            apply_auto_close "$OWNER" "$REPO" "$TYPE" "$NUM"
            ;;
        *)
            echo "  skipped (action=$ACTION)"
            ;;
    esac
    
    sleep 0.5  # 避免限流
done

echo "✓ Batch applied"
```

### 4.2 AI Agent 执行模板

向 Claude Code 发送：

```
请按以下步骤应用 /tmp/stale-report.json 中的动作：

1. 读取报告，过滤 confidence < 0.6 的项
2. 对每个剩余项：
   a. 若 action == mark_stale：
      - issue +label-add --labels stale
      - issue +comment --body <模板>
   b. 若 action == auto_close：
      - 上述步骤 + issue +close
   c. 若 type == pr：
      - 仅 pr +comment（不打标签）
3. 每应用 10 个后暂停，问我是否继续
4. 完成后输出统计：成功数、失败数、跳过数

任何步骤失败都不要继续，停下来问我。
```

---

## 5. 评论模板库

### 5.1 友好催办（mark_stale）

**通用版**（推荐）：

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

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

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

> 🤖 由 gitlink-stale skill 自动生成。
```

**bug 类专用**：

```markdown
⏰ **这个 bug 还能复现吗？**

本 Issue 已 60 天未活动，可能：

- 问题已经在最新版本中修复？欢迎确认
- 问题不再复现？欢迎手动关闭
- 仍然存在？请回复最新版本号和复现步骤

如果 **14 天内**没有新活动，将默认视为已解决，自动关闭。
```

**feature 类专用**：

```markdown
⏰ **这个需求还在期待吗？**

本 feature 请求已 60 天未活动。可能：

- 不再需要？欢迎手动关闭
- 仍然想要？欢迎回复说明用例
- 想自己实现？欢迎提交 PR

如果 **14 天内**没有新活动，将默认视为不再需要，自动关闭。
```

### 5.2 自动关闭（auto_close）

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

本 Issue 已 74 天无活动，自动关闭。

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

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

### 5.3 PR 催办

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

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

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

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

### 5.4 误标道歉（回滚用）

```markdown
🙏 **抱歉，刚刚的 stale 标记是误判**

经过人工复核，本 Issue 不应被标记为 stale，已移除标签。

如带来困扰，敬请谅解。

> 🤖 由 gitlink-stale skill 回滚。
```

---

## 6. 回滚策略

### 6.1 误标的 Issue（仅打了 stale 标签）

```bash
# 移除 stale 标签
gitlink-cli issue +label-remove \
    --owner <owner> --repo <repo> \
    --number <N> \
    --label "stale"

# 道歉评论
gitlink-cli issue +comment \
    --owner <owner> --repo <repo> \
    --number <N> \
    --body "🙏 **抱歉，刚刚的 stale 标记是误判**..."
```

### 6.2 误关闭的 Issue（被自动关闭）

```bash
# 重新打开（用 Raw API，因 +update 需要状态参数）
CURRENT=$(gitlink-cli issue +view \
    --owner <owner> --repo <repo> \
    --number <N> --format json)
SUBJECT=$(echo "$CURRENT" | jq -r '.data.subject')
DESC=$(echo "$CURRENT" | jq -r '.data.description // ""')

PAYLOAD=$(jq -n \
    --arg s "$SUBJECT" \
    --arg d "$DESC" \
    '{subject:$s, description:$d, status_id:1}')

gitlink-cli api PATCH "/v1/<owner>/<repo>/issues/<N>" --body "$PAYLOAD"

# 移除 stale 标签
gitlink-cli issue +label-remove \
    --owner <owner> --repo <repo> \
    --number <N> \
    --label "stale"

# 道歉评论
gitlink-cli issue +comment \
    --owner <owner> --repo <repo> \
    --number <N> \
    --body "🙏 **已重新打开**，刚才的自动关闭是误判，抱歉。原始讨论继续。"
```

### 6.3 批量回滚

```bash
#!/usr/bin/env bash
# rollback-stale.sh — 从备份文件批量恢复
BACKUP="${1:?usage: rollback-stale.sh <backup.json>}"

jq -c '.data.issues[]' "$BACKUP" | while read -r issue; do
    NUM=$(echo "$issue" | jq '.number')
    SUBJECT=$(echo "$issue" | jq -r '.subject')
    DESC=$(echo "$issue" | jq -r '.description // ""')
    
    # 重新打开
    PAYLOAD=$(jq -n --arg s "$SUBJECT" --arg d "$DESC" \
        '{subject:$s, description:$d, status_id:1}')
    gitlink-cli api PATCH "/v1/<owner>/<repo>/issues/$NUM" --body "$PAYLOAD" > /dev/null
    
    # 移除 stale 标签
    gitlink-cli issue +label-remove --number "$NUM" --label "stale" 2>/dev/null || true
    
    sleep 0.3
done

echo "✓ Rollback complete"
```

---

## 7. 错误处理

| 错误 | 原因 | 处理 |
|------|------|------|
| `HTTP 401` | Token 失效 | `gitlink-cli auth login` |
| `HTTP 403` | 无写权限 | 联系仓库 owner |
| `HTTP 404` | Issue 已被删除 | 跳过，记录到 errors |
| `HTTP 422` | subject/description 被清空 | 必须先 GET 再 PATCH |
| `label not found` | 仓库无 `stale` 标签 | 提示用户先创建标签 |
| `state: -1` | 参数错 | 检查 issue +close 的 number |

应用失败时**不要重试**，记录到错误日志，整体应用结束后人工排查。

---

## 8. 审计日志

每次应用后记录：

```json
{
  "applied_at": "2026-06-23T10:30:00Z",
  "operator": "ai-agent + human-confirm",
  "batch_id": "stale-20260623-1",
  "repository": "owner/repo",
  "thresholds": {
    "stale_days": 60,
    "close_days": 74
  },
  "summary": {
    "total_scanned": 85,
    "total_applied": 18,
    "skipped_low_confidence": 4,
    "exempt": 15,
    "actions": {
      "mark_stale": 12,
      "auto_close": 6
    }
  },
  "items_applied": [
    {
      "type": "issue",
      "number": 142,
      "action": "mark_stale",
      "confidence": 0.85,
      "success": true,
      "changes": {
        "labels_added": ["stale"],
        "comment_added": true
      }
    }
  ],
  "backup_file": "/tmp/before-stale-1719139200.json"
}
```

保存到 `/tmp/stale-audit-<timestamp>.json`，便于追溯。

---

## 9. 最佳实践

- ✅ **小批量试水**：先对 3-5 个候选执行 mark_stale，观察结果再扩大
- ✅ **urgent 谨慎**：对 confidence < 0.85 的 urgent/feature 类 Issue 额外人工复核
- ✅ **避开高峰**：大批量执行安排在用户活跃低谷时段
- ✅ **通知 owner**：执行前在仓库管理员沟通渠道同步"本次将清理 N 个 Issue"
- ✅ **保留备份**：所有备份文件至少保留 30 天
- ❌ **禁止**：跳过 dry-run 直接批量执行
- ❌ **禁止**：对 archived 或 read-only 仓库执行
- ❌ **禁止**：批量关闭超过 50 个 Issue 而不分批
