gitlink-cli/skills/gitlink-stale/references/gitlink-stale-judge.md

11 KiB
Raw Blame History

gitlink-stale — AI 判断规则详解

本文档说明 Stage C "AI 真假僵尸判断" 的完整信号集与决策算法。 这是本 Skill 与简单时间过滤工具的核心差异。

1. 为什么需要 AI 判断?

简单按"60 天未活动"一刀切会有大量误判:

误判场景 简单规则的错误 AI 判断的纠正
路线图 Issue 标记 stale → 关闭 识别为 roadmap跳过
等维护者 busy 标记 stale作者无感 看评论历史,知道在等
已知 issue 占位 标记 stale 看到维护者说"已知问题,待 v2"
高质量 bug等修复 标记 stale作者失望 看到讨论活跃,跳过
路过用户的占位 一直占着 看到作者 0 历史,应清理

核心思想updated_at 时间 + AI 判断 = 准确识别"真僵尸"。


2. 判断信号全集

2.1 评论历史信号(最重要)

通过 issue +view --number N 拿到的 journals 数组:

信号 真僵尸(应处理) 活跃(应跳过)
最后评论者 用户 / 无人 维护者
维护者最后回复时间 60+ 天前 30 天内
评论数 0-1 条 ≥ 5 条
评论内容关键词 "已知问题"、"占位"、"无复现" "正在处理"、"待 v2"、"等待上游"
用户最后追问 60 天前追问无回复 最近有讨论

判断伪代码

def analyze_journals(journals, maintainers):
    if not journals:
        return {"truly_stale": True, "score": 0.9, "reason": "0 评论,长期无人理"}
    
    last_journal = journals[-1]
    last_user = last_journal["user"]["login"]
    last_time = parse(last_journal["created_at"])
    
    # 维护者最近回复过 → 跳过
    if last_user in maintainers:
        days_since = (now() - last_time).days
        if days_since < 30:
            return {"truly_stale": False, "score": 0.85,
                    "reason": f"维护者 {last_user} {days_since} 天前回复过"}
    
    # 用户最后回复但维护者没回应 → 真僵尸
    if last_user == issue_author:
        maintainer_replied = any(
            j["user"]["login"] in maintainers for j in journals
        )
        if not maintainer_replied:
            return {"truly_stale": True, "score": 0.9,
                    "reason": "用户提问后维护者从未回复"}
    
    # 评论内容关键词
    last_text = last_journal["notes"]
    if any(kw in last_text for kw in ["正在处理", "待 v2", "等待上游", "WIP"]):
        return {"truly_stale": False, "score": 0.8,
                "reason": "评论含'进行中'类关键词"}
    
    if any(kw in last_text for kw in ["已知问题", "占位", "暂不处理"]):
        return {"truly_stale": True, "score": 0.75,
                "reason": "评论含'已知/占位'类关键词"}
    
    return {"truly_stale": True, "score": 0.65, "reason": "默认判定为僵尸"}

2.2 Issue 类型信号

tracker 默认判断 例外
bug 谨慎处理(可能仍有效) 若含"已修复,待 release"则跳过
feature 看评论活跃度 若是热门需求(≥ 5 👍)则跳过
question 大胆清理(多半已自然结束) 若维护者问"还遇到吗?"而用户没回,必清理
duplicate 直接关闭 -
support 大胆清理 -
doc 看是否是 README 修正 -

特殊情况

tracker/标签 判断
roadmap 永不处理(白名单)
epic 永不处理(白名单)
security 永不处理(白名单)
pinned 永不处理(白名单)
in-progress 永不处理(白名单)
under-review 永不处理(白名单)

2.3 标签信号

def check_labels(labels, action):
    """
    返回 (exempt, reason) 或 (False, None)
    """
    STALE_EXEMPT = {
        "pinned", "置顶",
        "security", "安全",
        "roadmap", "路线图",
        "epic", "里程碑",
        "in-progress", "进行中",
        "under-review", "审查中",
        "keep-open", "保留",
        "help-wanted",  # 等社区认领
        "good-first-issue",  # 等新手认领
    }
    
    for label in labels:
        if label.lower() in STALE_EXEMPT:
            return True, f"含豁免标签: {label}"
    
    return False, None

2.4 作者活跃度信号

def analyze_author(author_login, repo_activity):
    """
    评估 Issue 作者的活跃度
    """
    author_issues = repo_activity["by_author"].get(author_login, [])
    
    if len(author_issues) == 1:
        # 路过用户:只此一个 Issue可能是占位
        return {"stale_tendency": 0.7, "reason": "作者仅此 1 个 Issue"}
    
    if author_login in repo_activity["contributors"]:
        # 资深贡献者,信任会跟进
        return {"stale_tendency": 0.3, "reason": "作者是仓库贡献者"}
    
    if len(author_issues) >= 5:
        # 多 issue 用户,可能批量提交后不再跟进
        return {"stale_tendency": 0.6, "reason": f"作者历史 {len(author_issues)} 个 Issue"}
    
    return {"stale_tendency": 0.5, "reason": "中性"}

2.5 标题关键词信号

keep_open_patterns:
  - "[WIP]"
  - "[Pinned]"
  - "[Keep Open]"
  - "路线图"
  - "长期"
  - "讨论"
  - "RFC"
  - "提案"

force_close_patterns:
  - "[已过期]"
  - "[占位]"
  - "测试"        # 仅 2 字符的"测试"
  - "测试用"
  - "ignore"
  - "deprecated"

3. 综合决策算法

3.1 信号汇总

def ai_judge_stale(issue, journals, repo_meta):
    # 1. 时间过滤(前置)
    days = compute_days_inactive(issue)
    if days < stale_threshold:
        return {"truly_stale": False, "exempt": True,
                "reason": f"仅 {days} 天未活动,未达阈值"}
    
    # 2. 白名单豁免
    exempt, exempt_reason = check_labels(get_labels(issue), ...)
    if exempt:
        return {"truly_stale": False, "exempt": True, "reason": exempt_reason}
    
    # 3. 标题强信号
    if matches_force_close(issue.subject):
        return {"truly_stale": True, "confidence": 0.95,
                "reason": "标题含强制关闭关键词"}
    if matches_keep_open(issue.subject):
        return {"truly_stale": False, "confidence": 0.9,
                "reason": "标题含保留关键词"}
    
    # 4. 综合多信号
    signals = []
    
    # 4a. 评论历史信号(权重 0.4
    j_signal = analyze_journals(journals, repo_meta.maintainers)
    signals.append(("journals", j_signal["score"], j_signal["reason"], 0.4))
    
    # 4b. 类型信号(权重 0.25
    t_signal = analyze_tracker(issue.tracker_id)
    signals.append(("tracker", t_signal["score"], t_signal["reason"], 0.25))
    
    # 4c. 作者活跃度(权重 0.15
    a_signal = analyze_author(issue.author, repo_meta)
    signals.append(("author", a_signal["stale_tendency"], a_signal["reason"], 0.15))
    
    # 4d. 时间长度(权重 0.2
    time_score = min(1.0, (days - stale_threshold) / stale_threshold)
    signals.append(("time", time_score, f"{days} 天未活动", 0.2))
    
    # 5. 加权平均
    final_score = sum(score * weight for _, score, _, weight in signals)
    final_reason = "; ".join(f"{name}: {reason}" for name, _, reason, _ in signals)
    
    return {
        "truly_stale": final_score >= 0.6,
        "confidence": final_score,
        "reason": final_reason,
        "exempt": False
    }

3.2 置信度阈值

confidence 含义 建议动作
≥ 0.85 极有把握 直接列入"建议执行"清单
0.7 - 0.85 较有把握 列入"建议执行",报告中标记
0.6 - 0.7 一般 列入"建议复核"
< 0.6 把握不足 不自动处理,仅列入"待人工"队列

4. 边界情况

情况 处理
journals 数组很大 仅取最后 5 条用于 AI 判断
评论内容是图片/表情 跳过,仅看时间
评论是用户自己反复回("up"、"催" 维护者从未回应 → 真僵尸
维护者评论是 "duplicate of #N" 视为 duplicate自动关闭
跨语言评论(中英混合) 都能识别
评论含代码块 去除代码块后再分析

5. 示例分析

5.1 示例 A真僵尸高置信度

{
  "number": 142,
  "subject": "Bug: 登录页偶尔卡顿",
  "description": "有时候会卡...",
  "journals": [],
  "issue_tags": [],
  "author": {"login": "user-123"},
  "days_inactive": 68
}

分析

  • journals: 空 → 0.9
  • tracker: bug → 0.5(中性)
  • author: 仅此 1 个 Issue → 0.7
  • time: 68 天 → 0.13

加权0.9*0.4 + 0.5*0.25 + 0.7*0.15 + 0.13*0.2 = 0.556

输出

{
  "truly_stale": false,  // 略低于阈值
  "confidence": 0.556,
  "reason": "journals: 0 评论tracker: bug 谨慎author: 仅 1 Issuetime: 68 天",
  "recommended_action": "needs_review"
}

5.2 示例 B误判避免活跃

{
  "number": 156,
  "subject": "[Roadmap] v2 API 设计",
  "description": "长期讨论 v2 接口规范...",
  "journals": [
    {"user": "dev-li", "notes": "正在按这个方向重构", "created_at": "2026-06-15"},
    {"user": "dev-wang", "notes": "+1", "created_at": "2026-06-18"}
  ],
  "issue_tags": ["roadmap"],
  "days_inactive": 65
}

分析

  • 白名单:含 roadmapexempt: true

输出

{
  "truly_stale": false,
  "exempt": true,
  "exempt_reason": "含豁免标签: roadmap",
  "recommended_action": "skip"
}

5.3 示例 C明显僵尸高置信度

{
  "number": 178,
  "subject": "测试",
  "description": "测试",
  "journals": [
    {"user": "user-1", "notes": "测试", "created_at": "2026-02-01"}
  ],
  "author": {"login": "user-1"},
  "days_inactive": 142
}

分析

  • 标题:含"测试"force_close 模式)→ confidence 0.95
  • author = last journal user → 用户自言自语
  • time: 142 天

输出

{
  "truly_stale": true,
  "confidence": 0.95,
  "reason": "标题含强制关闭关键词",
  "recommended_action": "auto_close"
}

6. 信号权重调优

权重默认值(可在 Skill 配置中自定义):

signal_weights:
  journals: 0.4       # 评论历史最重要
  tracker: 0.25       # Issue 类型
  time: 0.2           # 时间长度
  author: 0.15        # 作者活跃度

confidence_thresholds:
  strong: 0.85        # 直接执行
  medium: 0.7         # 执行但标记
  weak: 0.6           # 待人工

调优建议

  • 团队项目:维护者评论信号最重要(提高 journals 权重)
  • 开源项目:作者活跃度更关键(提高 author 权重)
  • 紧急项目:时间长度更严格(提高 time 权重,降低阈值)

7. 与规则引擎的对比

维度 规则引擎(如 GitHub stale-bot AI 判断(本 Skill
准确率 ~70%(按时间一刀切) ~90%(多信号综合)
误关率 5-10% < 2%
配置复杂度 YAML 写规则 AI 自动理解上下文
可解释性 高(规则明确) reasoning 字段说明)
性能 极快(无 AI 推理) 中(需要 LLM 调用)

结论:本 Skill 适合"宁可慢一点,也要少误关"的高质量项目。对于"堆积严重、宁可错杀"的清理任务,可在 SKILL.md 中临时调整 stale_days 和置信度阈值。