forked from Gitlink/gitlink-cli
11 KiB
11 KiB
gitlink-stale — 白名单豁免规则
本文档详述"哪些 Issue/PR 永不被 stale skill 处理"的完整规则。 豁免规则在 Stage B 执行,先于 AI 判断(节省 API 调用)。
1. 豁免原则
核心思想:宁可放过,不可误关。
任何满足"长期重要"或"主动声明保留"信号的 Issue 都应被豁免。
| 原则 | 说明 |
|---|---|
| 保守 | 不确定时,豁免(不处理) |
| 多信号 | 任一豁免信号触发即可 |
| 可追溯 | 豁免原因必须记录在报告中 |
| 可配置 | 用户可自定义豁免规则 |
2. 豁免信号全集
2.1 标签豁免(最强信号)
| 标签名(中/英) | 豁免原因 | 默认权重 |
|---|---|---|
pinned / 置顶 |
显式标记永久保留 | 必豁免 |
security / 安全 |
安全相关,永不自动关闭 | 必豁免 |
roadmap / 路线图 |
长期规划 | 必豁免 |
epic / 里程碑 |
大型任务父节点 | 必豁免 |
keep-open / 保留 |
显式声明 | 必豁免 |
in-progress / 进行中 |
正在处理 | 必豁免 |
under-review / 审查中 |
等待审查 | 必豁免 |
help-wanted |
等社区认领 | 必豁免 |
good-first-issue |
等新手认领 | 必豁免 |
p0 / p1 |
高优先级 | 必豁免 |
2.2 Tracker 类型豁免
GitLink 的 tracker_id 映射(参考 issue-triage skill):
| tracker_id | 名称 | 默认豁免 |
|---|---|---|
| 1 | bug | ❌ 不豁免(仍可能 stale) |
| 2 | feature | ❌ 不豁免 |
| 3 | support | ❌ 不豁免(最易 stale) |
| 4 | doc | ❌ 不豁免 |
| 5 | test | ❌ 不豁免 |
| 6 | duplicate | ✅ 直接关闭(特殊处理) |
| 7 | question | ❌ 不豁免 |
| 自定义 | roadmap | ✅ 豁免 |
| 自定义 | epic | ✅ 豁免 |
2.3 优先级豁免
| priority_id | 等级 | 默认豁免 |
|---|---|---|
| 1 | low | ❌ 不豁免 |
| 2 | normal | ❌ 不豁免 |
| 3 | high | ⚠️ 仅在 confidence >= 0.9 时处理 |
| 4 | urgent | ✅ 豁免 |
2.4 标题模式豁免
匹配以下正则的标题豁免:
keep_open_title_patterns:
- "\\[WIP\\]"
- "\\[Pinned\\]"
- "\\[Keep.?Open\\]"
- "\\[RFC\\]"
- "^Roadmap:"
- "^路线图"
- "^讨论"
- "^提案"
- "长期"
- "permanent"
匹配以下模式的标题强制关闭(反向豁免):
force_close_title_patterns:
- "^测试$" # 仅"测试"两字
- "^test$" # 仅"test"
- "\\[占位\\]"
- "\\[已过期\\]"
- "^ignore$"
- "deprecated"
2.5 作者豁免
| 作者类型 | 豁免规则 |
|---|---|
| 仓库 owner | ✅ 豁免(信任 owner 会跟进) |
| 仓库 member | ✅ 豁免 |
| 资深贡献者(≥ 10 PR merged) | ⚠️ confidence >= 0.85 才处理 |
| 普通用户 | ❌ 不豁免 |
| 路过用户(仅 1 Issue) | ❌ 不豁免(更倾向清理) |
2.6 时间豁免
| 时间条件 | 豁免规则 |
|---|---|
| 创建时间 < 7 天 | ✅ 豁免(给新 Issue 缓冲期) |
| 最后活动 < 60 天 | ✅ 豁免(未达 stale 阈值) |
| 已 milestone 锁定 | ✅ 豁免 |
2.7 关联豁免
| 关联条件 | 豁免规则 |
|---|---|
| 有 linked PR(标题含"fixed in #N") | ✅ 豁免(等 PR 合并) |
| 有子任务(被 epic 引用) | ✅ 豁免 |
| duplicate of 已 closed | 直接关闭(特殊处理) |
3. 豁免执行算法
def check_exempt(issue, repo_meta, user_meta):
"""
返回 (is_exempt, reason) 或 (False, None)
顺序:从最强信号到弱信号,任一触发即返回
"""
# 1. 标签豁免(最强)
EXEMPT_LABELS = {
"pinned", "置顶",
"security", "安全",
"roadmap", "路线图",
"epic", "里程碑",
"keep-open", "保留",
"in-progress", "进行中",
"under-review", "审查中",
"help-wanted",
"good-first-issue",
"p0", "p1",
}
for label in get_labels(issue):
if label.lower() in EXEMPT_LABELS:
return True, f"含豁免标签: {label}"
# 2. 标题强信号
import re
for pattern in KEEP_OPEN_TITLE_PATTERNS:
if re.search(pattern, issue.subject, re.IGNORECASE):
return True, f"标题匹配保留模式: {pattern}"
# 3. 优先级豁免
if issue.priority_id == 4: # urgent
return True, "urgent 优先级"
# 4. tracker 豁免
if issue.tracker_id in [ROADMAP, EPIC]:
return True, "tracker 是 roadmap/epic"
# 5. 作者豁免
if issue.author.login == repo_meta.owner:
return True, "作者是仓库 owner"
if issue.author.login in repo_meta.members:
return True, "作者是仓库 member"
# 6. 时间豁免(创建 < 7 天)
days_since_created = (now() - parse(issue.created_at)).days
if days_since_created < 7:
return True, f"创建仅 {days_since_created} 天,在缓冲期内"
# 7. 高优先级的特殊处理
if issue.priority_id == 3: # high
# 不直接豁免,但需要 confidence >= 0.9
return False, None # 走正常流程
return False, None
def check_force_close(issue):
"""
反向豁免:强制关闭
"""
import re
for pattern in FORCE_CLOSE_TITLE_PATTERNS:
if re.search(pattern, issue.subject, re.IGNORECASE):
return True, f"标题匹配强制关闭模式: {pattern}"
return False, None
4. 豁免决策流程图
┌─────────────────────────────┐
│ Issue 候选(已通过时间过滤) │
└────────────┬────────────────┘
▼
┌─────────────────────────────┐
│ 1. 强制关闭模式匹配? │─── 是 ──→ force_close
└────────────┬────────────────┘
│ 否
▼
┌─────────────────────────────┐
│ 2. 含豁免标签? │─── 是 ──→ exempt
└────────────┬────────────────┘
│ 否
▼
┌─────────────────────────────┐
│ 3. 标题匹配保留模式? │─── 是 ──→ exempt
└────────────┬────────────────┘
│ 否
▼
┌─────────────────────────────┐
│ 4. priority == urgent? │─── 是 ──→ exempt
└────────────┬────────────────┘
│ 否
▼
┌─────────────────────────────┐
│ 5. tracker == roadmap/epic? │─── 是 ──→ exempt
└────────────┬────────────────┘
│ 否
▼
┌─────────────────────────────┐
│ 6. 作者是 owner/member? │─── 是 ──→ exempt
└────────────┬────────────────┘
│ 否
▼
┌─────────────────────────────┐
│ 7. 创建 < 7 天? │─── 是 ──→ exempt
└────────────┬────────────────┘
│ 否
▼
进入 AI 判断
5. 自定义豁免规则
用户可在仓库根目录创建 .gitlink-stale.yml 自定义:
# .gitlink-stale.yml
version: 1.0
# 阈值
thresholds:
stale_days: 60
close_days: 74
grace_days: 14
# 豁免标签(追加到默认列表)
exempt_labels:
- "客户合同"
- "VIP 用户反馈"
# 豁免标题模式(追加)
exempt_title_patterns:
- "^\\[长期讨论\\]"
# 强制关闭模式(追加)
force_close_title_patterns:
- "^spam"
# 豁免用户
exempt_authors:
- "trusted-contributor"
# 自定义 priority 豁免
exempt_priorities:
- 4 # urgent
- 3 # high(比默认更严格)
# 自定义 tracker 豁免
exempt_trackers:
- 8 # 自定义的"内部任务"
💡 Skill 在执行前自动加载此文件(如果存在),与默认规则合并。详见 SKILL.md §4.3。
6. 豁免审计
报告中必须列出所有被豁免的 Issue,便于人工复核:
{
"summary": {
"exempt": 15,
"exempt_breakdown": {
"label_pinned": 3,
"label_security": 2,
"label_roadmap": 5,
"priority_urgent": 2,
"author_owner": 2,
"title_pattern": 1
}
},
"exempt_items": [
{
"number": 88,
"title": "[Pinned] 项目长期路线图",
"exempt_reason": "含豁免标签: pinned",
"exempt_signal": "label_pinned"
},
{
"number": 92,
"title": "线上数据库故障",
"exempt_reason": "urgent 优先级",
"exempt_signal": "priority_urgent"
}
]
}
7. 边界情况
| 情况 | 处理 |
|---|---|
| 同一 Issue 含豁免标签和强制关闭模式 | 豁免优先(保守原则) |
标签名大小写不同(Pinned vs pinned) |
大小写不敏感 |
标签名含空格(keep open) |
标准化(去空格、转小写)后比较 |
| 标签是 emoji(📌) | 当前不支持,建议搭配文字标签 |
作者 ID 已注销(login == null) |
不豁免(可能就是僵尸) |
| 用户自定义规则与默认冲突 | 用户规则优先(追加而非覆盖) |
8. 推荐的标签配置
为了让 Skill 发挥最佳效果,强烈推荐仓库具备以下标签:
| 标签名 | 用途 |
|---|---|
pinned |
显式标记永久保留的 Issue |
security |
安全相关 |
roadmap |
路线图 |
stale |
已被本 Skill 标记 |
duplicate |
重复 Issue |
wontfix |
决定不修复(但保留记录) |
如果仓库缺少这些标签,Skill 在执行前会提示用户创建(不会自动创建,避免越权)。