OpenClaw 重复 PR/Issue 甄别工作流:gitcrawl 候选发现、prtags 分组与 GitHub 评论同步的完整实现
OpenClaw 仓库中维护了一套针对重复 PR/Issue 的分诊技能(skill),核心文件为 .agents/skills/tag-duplicate-prs-issues/SKILL.md。该技能定义了"本地历史检索(gitcrawl)→ 实时 GitHub 验证(gh)→ 维护者裁决落库(prtags)→ 评论自动同步"的完整链路。读完本文,你将掌握这套三工具分工的分诊流程、重复判定的证据规则与"单组原则",并能复现其中的全部命令、字段定义和裁决输出格式;同时了解仓库中配套的自动化关闭脚本 scripts/close-duplicate-prs-after-merge.mjs 如何用 hunk 级 diff 重叠来硬性佐证"重复"结论。
1. 技能定位:只判重复,不评质量
该 skill 的 frontmatter 明确了使用场景:
name: tag-duplicate-prs-issues
description: Use gitcrawl to search duplicate OpenClaw PRs/issues, group related work in prtags, and sync duplicate state to GitHub.
技能文档开宗明义:这是给维护者做分诊和归类用的,不是用来评审 PR 实现质量的("This skill is for maintainer triage and grouping. It is not for reviewing the implementation quality of a PR.")。配套元数据 agents/openai.yaml 中也给出了技能的一句话触发描述:"Find duplicate PRs and issues with gitcrawl, group them in prtags, and let prtags sync the GitHub comment"。
技能的最终目标(Goal)被归纳为五步:
- 收集重复证据(gather duplicate evidence)
- 判定是否为真实重复(decide whether it is a real duplicate)
- 为该重复簇创建或复用一个
prtags分组(create or reuse one prtags group) - 把维护者裁决保存到
prtags(save the maintainer judgment in prtags) - 依赖
prtags的正常分组写入来驱动 GitHub 评论同步(rely on normal prtags group writes to drive GitHub comment sync when that integration is configured)
2. 前置准备:安装 prtags、OAuth 登录与"缺件即停"规则
2.1 配套技能与 CLI 安装
文档要求"在 setup 完成前不要写入任何重复分组或标注",但只读的发现阶段(read-only discovery)可以只依赖 gitcrawl 和实时 gh 继续推进。配套技能有两处:
$gitcrawl:本地候选发现的第一入口。仓库中对应的技能定义在 .agents/skills/gitcrawl/SKILL.md,其 metadata 声明gitcrawl是一个 Go 二进制(github.com/openclaw/gitcrawl/cmd/gitcrawl@latest),职责是"GitHub archive: issue/PR search, sync freshness, duplicate clusters, gh-shim PR status"。prtags:来自独立仓库的 CLI,技能文档要求从该仓库最新的 GitHub Release 安装,不要依赖旧的本地构建,除非维护者明确想测试未发布行为:
curl -fsSL https://raw.githubusercontent.com/dutifuldev/prtags/main/scripts/install-prtags.sh | bash -s -- --bin-dir "$HOME/.local/bin"
2.2 认证:维护者本人 OAuth 设备流
prtags 必须以维护者本人账号通过 OAuth device flow 登录,明确禁止用共享维护者 token 做交互式分诊:
prtags auth login
prtags auth status
预期结果是 prtags 在本地保存登录的维护者身份,并以此身份执行所有认证写操作。
2.3 Missing-Setup Rule:不要前置预检,但要"缺件即停"
文档对 setup 的检查策略写得很细,值得单独提炼:
- 不要在工作流一开始做强制 preflight,正常步骤先走,直到真正需要某个工具或账号状态为止;
- 一旦发现
prtags缺失或未登录(发生在写步骤时),立即停止,不得在半写状态(partial write mode)下继续; - 缺失工具时引导用户执行上面的 install 脚本;
prtags auth status显示未登录时引导用户执行prtags auth login; - 只有在缺失的工具或登录状态被修复后才允许恢复工作流。
这条规则的意义在于:分诊是"读多写少"的工作流,把预检推迟到真正写之前,既不影响只读探索,又能保证所有写操作要么完整发生、要么完全不发生。
3. 读路径默认值(Read-Path Default):gitcrawl 优先,gh 兜底
技能对工具角色划定了清晰的边界,这是理解整套流程的骨架:
| 工具 | 角色 | 边界 |
|---|---|---|
gitcrawl |
候选生成 + 历史上下文 | 本地 title/body 搜索、neighbors、clusters、已关闭线程发现;所有候选在被实时 GitHub 确认前都只是线索 |
gh |
实时 GitHub 事实 | 目标状态、正文、评论、review、文件、关联 issue、当前 open/closed/merged 状态;仅当 gitcrawl 陈旧、缺数据或无法表达查询时才用 gh search |
prtags |
维护者策管层 | 创建/复用一个重复分组;保存重复状态、置信度、理由、组摘要;作为面向 GitHub 的分组评论的唯一事实来源 |
允许放弃 gitcrawl 改用实时 GitHub 搜索的情形被限定为三种具体理由:目标或候选尚未入库、本地数据对当前决策明显陈旧/不完整、gitcrawl 报错或超时或缺少所需数据。回退到实时搜索时必须注明回退及原因。
gitcrawl 技能文档(.agents/skills/gitcrawl/SKILL.md)还补充了两个关键实践:
- 检查同步新鲜度:
gitcrawl doctor --json; - 铁律:"Do not close/label from similarity alone; require matching intent plus live verification."——不得仅凭相似度就关闭/打标,必须意图匹配 + 实时验证。
还有一条针对写失败的兜底规则:如果后续的 prtags 目标级写入失败,原因是其自身 mirror 尚未同步到位,则应停止并报告"curation backend 缺少该目标对象",而不是强行走 fallback 写路径。
4. 判定规则:证据清单与单组原则
4.1 什么才算重复
工作规则(Working Rules)给出了三条否定式判据:
- 不能仅因为标题相似就判重;
- 不能仅因为改了相同的文件就判重;
- 重复簇必须基于:相同的用户可见问题 + 相同意图 + 实质重叠的实现或调查上下文。
4.2 证据清单(Evidence Checklist)
宣布重复前,证据必须来自至少两个类别。特别注意:gitcrawl 的 neighbors、搜索命中、cluster 成员身份只算候选生成,本身不构成足够证明。
针对 PR,可用的证据维度:
- 相同或几乎相同的问题陈述
- 相同或范围重叠的变更文件
- 相同的修复方向
- 相同子系统和相同失效模式
- 相同的关联 issue 或相同的用户可见症状
针对 Issue,可用的证据维度:
- 相同的用户可见问题
- 相同的复现路径或失效模式
- 相同的疑似修复区域
- 相同已关联/已讨论的 PR
- 相同维护者已在把两边引向同一个重复归类
"只有措辞相似"(only have wording similarity)被明确列为不合格证据。
4.3 单组原则(One-Group Rule)
重复分组被定义为互斥的:一个 PR/Issue 同一时间最多属于一个重复组。由此推出四条操作约束:
- 建新组之前,先搜索是否已有表达同一重复故事的组;
- 若目标似乎已属于另一个重复组,先停下解决冲突;
- 不能因为措辞略有不同就为同一目标建第二个组;
- 若两个候选组重叠且无法安全合并裁决,停下并询问维护者。
文档特意强调:"This rule matters more than speed."——宁可慢,也要保持"每个问题一个内聚的重复簇",而不是产生一堆近重复簇。
4.4 好分组 vs 坏分组的形状
一个合格的重复组应该描述底层问题和预期修复方向,不能只因共享某个关键词就归组:
- 好形状:相同的用户可见 bug 或维护者任务、相同子系统/代码面、相同的变更方向、相同的重复处置路径;
- 坏形状(反例):"所有碰过 Slack 的 PR"、"所有提到 retry 的 issue"、"所有 auth 相关条目";
- 组标题应命名真实问题,组描述应概括意图与代码面。
文档给出的三个正面示例:
gateway: startup regression from channel status bootstrapwhatsapp: QR preflight timeout handlingrelease: cross-OS validation handoff gaps
5. 八步工作流:从读取目标到评论同步
Step 1:读取目标(Read The Target)
一律用实时 GitHub 获取目标当前状态。PR 用:
gh pr view <number> --json number,title,state,mergedAt,body,closingIssuesReferences,files,comments,reviews,statusCheckRollup
Issue 用:
gh issue view <number> --json number,title,state,body,comments,closedAt
需要记录八项信息:目标类型与编号、标题、问题陈述、预期意图、子系统、open/closed/merged 状态、是否已有人文提及疑似重复线程。
Step 2:gitcrawl 广域检索
gitcrawl 是"本地 OpenClaw 历史与聚类源",只有当它缺数据、陈旧或故障时才切换到广域实时搜索。命令按"目标与邻近线程 → 关键短语/子系统检索 → 集群细看 → 候选实时验证"四段展开:
# 目标线程与邻居
gitcrawl threads openclaw/openclaw --numbers <issue-or-pr-number> --include-closed --json
gitcrawl neighbors openclaw/openclaw --number <issue-or-pr-number> --limit 20 --json
# 关键短语与子系统术语(混合检索)
gitcrawl search openclaw/openclaw --query "<key phrase from title or body>" --mode hybrid --limit 20 --json
gitcrawl search openclaw/openclaw --query "<subsystem or error phrase>" --mode hybrid --limit 20 --json
# 集群细节
gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --body-chars 280 --json
对候选 PR 用实时文件数据验证代码重叠:
gh pr view <candidate-pr> --json number,title,state,mergedAt,files,body,comments,reviews
对候选 Issue 验证实时状态与评论:
gh issue view <candidate-issue> --json number,title,state,body,comments,closedAt
Step 3:实时 GitHub 搜索补盲
在 gitcrawl 之后,仅当满足以下情形才做定向实时搜索:目标太新、本地库没有;评论/review 重要而本地库缺失;确切短语本地没有但 GitHub 理应有。
gh search prs --repo openclaw/openclaw --match title,body --limit 50 -- "<key phrase>"
gh search issues --repo openclaw/openclaw --match title,body --limit 50 -- "<key phrase>"
gh search issues --repo openclaw/openclaw --match comments --limit 50 -- "<error or maintainer phrase>"
Step 4:做出三选一裁决
每个目标必须落到以下三种结果之一:
not_duplicateduplicate_needs_judgmentduplicate_confirmed(仅当证据强到维护者可以安全关闭或重新打标该重复项时才使用)
使用 duplicate_needs_judgment 的明确情形:问题看似相同但实现目标不同;代码重叠弱;措辞含糊;可能存在两种合理的重复组解释;目标似乎同时交叠两个已存在的重复组。
Step 5:复用或创建一个 prtags 分组
先搜后建。文本搜索 + 相似搜索 + 全量列表三路排查:
prtags search text -R openclaw/openclaw "<problem phrase>" --types group --limit 10
prtags search similar -R openclaw/openclaw "<problem summary>" --types group --limit 10
prtags group list -R openclaw/openclaw
prtags group get <group-id>
prtags group get <group-id> --include-metadata
复用条件:代表同一问题、已含明显相关的成员、加入目标后组仍内聚。不要因为 gitcrawl 把若干 PR/Issue 排在一起就扩大既有分组——加入新成员前必须确认实际实现路径与维护者意图仍然一致。只有当没有任何现成组明显合适时才新建:
prtags group create -R openclaw/openclaw \
--kind mixed \
--title "<problem-centered title>" \
--description "<same intent, subsystem, and duplicate-resolution path>" \
--status open
随后挂入目标及已知重复成员:
prtags group add-pr <group-id> <pr-number>
prtags group add-issue <group-id> <issue-number>
若目标似乎已属于另一个组且无法安全复用,停止,不要创建第二个组。
Step 6:幂等地确保标注字段存在
用 field ensure 保证技能幂等。目标级(PR 与 Issue 各一套)字段:
prtags field ensure -R openclaw/openclaw --name duplicate_status --scope pull_request --type enum --enum-values not_duplicate,candidate,confirmed --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_status --scope issue --type enum --enum-values not_duplicate,candidate,confirmed --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_confidence --scope pull_request --type enum --enum-values low,medium,high --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_confidence --scope issue --type enum --enum-values low,medium,high --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_rationale --scope pull_request --type text --searchable
prtags field ensure -R openclaw/openclaw --name duplicate_rationale --scope issue --type text --searchable
组级字段:
prtags field ensure -R openclaw/openclaw --name duplicate_confidence --scope group --type enum --enum-values low,medium,high --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_rationale --scope group --type text --searchable
prtags field ensure -R openclaw/openclaw --name cluster_summary --scope group --type text --searchable
注意 duplicate_status 的三值 not_duplicate,candidate,confirmed 与 Step 4 的三态裁决形成映射:证据不全时写入 candidate 并调低置信度。
Step 7:保存维护者裁决
PR 与 Issue 各自写入目标级标注:
prtags annotation pr set -R openclaw/openclaw <pr-number> \
duplicate_status=confirmed \
duplicate_confidence=high \
duplicate_rationale="<same problem, same fix direction, overlapping files and comments>"
prtags annotation issue set -R openclaw/openclaw <issue-number> \
duplicate_status=confirmed \
duplicate_confidence=high \
duplicate_rationale="<same user-visible problem and same intended fix path>"
组级标注:
prtags annotation group set <group-id> \
duplicate_confidence=high \
cluster_summary="<one-sentence problem summary>" \
duplicate_rationale="<why these items belong in one duplicate cluster>"
两条失败处理规则:证据不全时写 duplicate_status=candidate 并降低置信度;若目标级标注写失败是因为 prtags 无法解析目标(mirror 未追上),则不要强制 fallback 写,保留已写成功的组状态,报告 curation backend 缺少该目标对象,推迟目标级标注直到 prtags 追上。
Step 8:让 prtags 同步组评论
这条设计是整个技能最有架构意味的一点:不要指示 Agent 直接创建 GitHub 评论。prtags 拥有对外的 GitHub 评论,它是组状态的派生投影(derived projection of group state)。配置了评论同步后,组写入本身就会自动入队派生评论,正常情况不需要手动触发同步。手动同步只作为修复/重试路径:
prtags group sync-comments <group-id>
# 查看哪些组仍需关注
prtags group list-comment-sync-targets -R openclaw/openclaw
技能应把 GitHub 评论视为"正确的 prtags 组状态的后果",既不应把手工写评论当作常规重复工作流的一部分,也不应把 sync-comments 当作每次裁决的必经步骤。
输出格式与停止条件
每次裁决后返回一段简短的维护者报告:
Decision: duplicate_confirmed | duplicate_needs_judgment | not_duplicate
Target: PR #<n> | Issue #<n>
Confidence: high | medium | low
Evidence:
- ...
- ...
- ...
prtags actions:
- reused group <group-id> | created group <group-id>
- added members: ...
- annotations written: ...
- comment sync: automatic if configured | manual repair triggered for <group-id>
停止条件(Stop Conditions)要求遇到以下情况时停下升级,而不是硬判:目标似乎属于两个不同重复组;重复归类不清晰;措辞匹配但实现目标不同;两个 PR 因不同原因触碰相同文件;两个 issue 症状相似但疑似不同根因。维护者得到的要么是一个干净的重复裁决,要么是一个明确的 "needs judgment" 结果——"Do not blur the line."
6. 仓库源码佐证:hunk 级证据的自动化落地
技能文档反复强调"代码重叠必须与意图匹配相互印证",仓库中与之形成闭环的自动化实现是 scripts/close-duplicate-prs-after-merge.mjs——一个在 PR 落地(merge)后关闭重叠候选 PR 的脚本。它的证据模型恰好是技能"证据清单"在代码面的精确化:
- 共享 issue 引用:
issueRefsFromPr()从closingIssuesReferences以及标题/正文中的close(s|d)/fix(e|s|d)/resolve(s|d)? #N正则中提取 issue 号,求交集; - hunk 级重叠:
parseUnifiedDiffRanges()解析 unified diff 的@@ -old +new,count @@行,得到每个文件的变更行区间;hasOverlappingHunks()做区间相交判断——这比技能文本中"相同或范围重叠的变更文件"更细一档,直接落到行号区间; - 硬性闸门:
buildDuplicateClosePlan()中,若既无共享 issue 又无重叠 hunk,直接抛错拒绝关闭("Refusing to close ... no shared issue and no overlapping changed hunks"),与技能"不能仅凭标题相似判重"的原则在代码上同构; - 默认 dry-run:
parseArgs()中--apply缺省为 false,未显式传--apply时只打印计划("dry-run only; pass --apply to label/comment/close duplicate PRs"),执行时按"打标 → 评论 → 关闭"三步操作,默认标签集为duplicate、close:duplicate、dedupe:child。
其用法为:
node scripts/close-duplicate-prs-after-merge.mjs --landed-pr <number> --duplicates <numbers> [--repo owner/repo] [--apply]
对应的测试 test/scripts/close-duplicate-prs-after-merge.test.ts 用 vitest 覆盖了关键语义:PR 列表解析(逗号/空白/# 前缀混排)、unified diff hunk 区间解析,以及两个典型的判重放行用例——"无显式 issue 引用但 hunk 重叠"和"issue 引用相同但 hunk 已漂移",验证了"两类证据任取其一即可、但必须至少有一类"的判定逻辑。这与技能文档 Step 4 的谨慎姿态一致:脚本只在机械可证的重叠下自动关闭,而需要维护者判断的部分(意图是否相同)仍留给 tag-duplicate-prs-issues 工作流与 prtags 裁决。
7. 小结:这套流程可迁移的设计原则
从 SKILL.md 与配套脚本看,OpenClaw 的重复甄别体系沉淀了几条可复用的工程原则:
- 分层事实源:本地缓存(gitcrawl)只做候选生成,实时 API(gh)负责事实核验,策管库(prtags)负责裁决与对外投影,三层各司其职、互不越权;
- 写操作的原子性纪律:缺件即停、mirror 未追上即停、不 fallback 强写,保证外部可见状态不会出现"半套标注";
- 评论即派生物:GitHub 评论不手工撰写,而是组状态的投影,手工同步仅作修复手段——这消除了"评论与标注不一致"这类长期漂移问题;
- 证据可计算化:从"至少两类证据"的自然语言规则,到脚本里共享 issue 交集 + hunk 区间相交的机械判定,重复判定在规则层与代码层保持了同一套证据模型。
适用前提提醒:prtags 为独立仓库的 CLI,需从 Release 安装并完成 OAuth 登录;gitcrawl 为 Go 二进制(见 .agents/skills/gitcrawl/SKILL.md 的 metadata);所有 -R openclaw/openclaw 命令假定在 OpenClaw 仓库上下文中运行,gh 需已登录且有仓库读写权限。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00