triage 精读:只碰别人建的 issue,把标签当状态机,每个决定先交回给人
triage 是一条平行车道:只处理不是你创建的 issue(外来的 PR 也归它管——『a PR is an issue with attached code』),把标签当状态机用(两个类别 + 五个状态,每个 item 恰好一个类别一个状态,冲突就停下来先问人),每一步都先给建议再等人点头,产物是一份能放几周的 agent brief。本文走一遍真实路径(三个桶挑一条 → 查重与查历史拒绝 → 给建议 → 验证 → 必要时 grilling → 落状态),讲清它为什么故意把验证做浅、为什么每条评论都必须带 AI 免责声明,以及它和 implement 的接续关系。
本篇属于系列 读懂最优秀的开发技能是怎么写的(Matt Pocock skills 逐技能精读) · 第 21 篇
(本文读的是 mattpocock/skills 快照 5beb35b,2026-10-19。引用原文都能按文中路径回仓库核对。这是「逐个技能精读」系列的第二十一篇,聚焦 triage 一个技能。)
它怎么工作:两个类别、五个状态、五步、一份 brief
skills/engineering/triage/SKILL.md 的 frontmatter 第一句就声明了自己是「只有人能打」的:disable-model-invocation: true(agents/openai.yaml 里对应 allow_implicit_invocation: false)。正文 112 行,是这一路读下来结构最像工程文档的一份;另一半内容挂在两份子文件上:AGENT-BRIEF.md(207 行,怎么写 agent brief)和 OUT-OF-SCOPE.md(105 行,被拒请求的知识库)。
角色只有七个:两个类别——bug、enhancement;五个状态——needs-triage、needs-info、ready-for-agent、ready-for-human、wontfix。状态转移写得很短:未标注的 issue 先落到 needs-triage,从那里走向另外四个;needs-info 在报告人回复后回到 needs-triage;maintainer 可以随时覆盖。这些是规范名,与 tracker 里真实标签的映射由 setup-matt-pocock-skills 提供,没有就让你先去跑它。
如果这个仓库把外部 PR 也当成请求面,triage 一并管:a PR is an issue with attached code——同一套角色、同一套状态、同一台机器,只有几处标了「for a PR」的差异;#42 到底是 issue 还是 PR,按 tracker 配置解析。
入口有两种。一种是让它批量报「什么在等我」:查 tracker,按最旧优先给三个桶——从未 triage 过的、正卡在 needs-triage 的、needs-info 但报告人有新回复的——每条一行摘要加计数,让你挑。另一种是你点名一个编号。
点名之后是五步:
- Gather context。 读全文(body、评论、标签、作者、日期;PR 还要读 diff),解析历史 triage 记录以免重复问已经问过的问题;用项目的领域词表读代码、尊重该区域的 ADR;然后跑两个检查:查重(按领域概念搜这个行为是不是已经有实现,而不是照抄请求里的措辞),和查历史拒绝(读
.out-of-scope/*.md)。 - Recommend。 给出类别与状态的建议加理由,再附一段和这个请求相关的代码库摘要(包括它是不是已经实现了),然后等人给方向。
- Verify the claim。 在任何 grilling 之前先验证请求站不站得住:bug 按报告人的步骤复现;PR 就 checkout 下来,跑相关测试或命令。报告三种结果之一——confirmed(带上代码路径)、failed、或细节不足(这本身就是很强的
needs-info信号)。 - Grill(如果需要)。 请求还欠细节时,同一个回合调
grilling和domain-modeling,一轮问题一轮问题地把它问成形,决定落地时顺手更新GLOSSARY.md和 ADR。 - Apply the outcome。
ready-for-agent贴一份 agent brief;ready-for-human同结构,但说明为什么不能委派;needs-info贴三份说明模板;wontfix分三种情况处理(下面说)。
还有一条逃生通道叫 Quick state override:maintainer 说「把 #42 移到 ready-for-agent」,就信他,先确认你要做什么再直接做,跳过 grilling。
产物很特别:它落下的东西全是评论,不是一个文件。 agent brief、triage notes、关闭说明,全都贴在 issue 上,跟着 issue 走。
它和「一般技能」的差别:标签是状态机,不是分类目录
一般的做法是给一组标签随便贴。这里要求每个 item 恰好一个类别加一个状态,冲突时停下:
Every triaged issue should carry exactly one category role and one state role. If state roles conflict, flag it and ask the maintainer before doing anything else.
第二个差别是先建议再等。原文写得很平静:Tell the maintainer your category and state recommendation with reasoning ... Wait for direction. 这不是礼貌,而是把「分类」这个动作留在人手里——技能只负责准备判断材料。
第三个差别是发起边界:它只碰不是你创建的东西。to-tickets 产出的工单已经是 agent-ready 的,再 triage 一遍就是把之前的工作浪费掉。文档把这条规则说得比技能更直白:/triage 只对进来的东西(incoming issues)有效,不对你自己建的东西。
第四个差别是它和 implement 的接续关系——两者在同一条流水线的两端。triage 是一条 on-ramp,平行于「有想法 → 追问 → spec → 工单 → 实现 → 评审」的主道;两条道的终点是同一个东西:一个带 brief 的 ready-for-agent issue。implement 捡它的方式,和捡 to-tickets 出的工单完全一样。
第五个差别是正文薄、产物规范厚。状态机和五步在 SKILL.md 里,怎么写一份能在仓库里放几周不烂的 brief 在 AGENT-BRIEF.md 里,被拒的概念怎么沉淀在 OUT-OF-SCOPE.md 里——技能本身不膨胀,膨胀的是它要产出的东西的规范。
它有意识摒弃了什么
一、摒弃了「agent 先动手」。 先建议,等指示,再落状态。这是整个技能最重要的一个克制。
二、摒弃了深挖根因。 验证是故意做浅的:它只回答「这是真的吗、大概在哪」,以及「确认过的验证会让 brief 强得多」(A confirmed verification makes a much stronger agent brief.)。要根因就换 diagnosing-bugs,那是另一件工作。
三、摒弃了空问题。 needs-info 的模板要求先把「目前已经确立的」列出来,再列「还需要你回答的」,并且问题必须具体可执行,不是「请提供更多信息」。
四、摒弃了 brief 里的路径和行号。 AGENT-BRIEF.md 的第一条原则叫 durability over precision:描述接口、类型、行为契约;不要引用文件路径(they go stale)、不要引用行号、不要假设当前的实现结构不会变。brief 可能要在 ready-for-agent 上躺几天甚至几周。
五、摒弃了「记录拒绝 = 记一条数据库行」。 .out-of-scope/ 一个概念一个文件(不是一条 issue 一个),写成一段简短的设计文档;理由要 durable,明确反对写「我们现在太忙」这种临时状态——those aren't real rejections, they're deferrals。
六、摒弃了把「已经实现」记成「被拒绝」。 wontfix 分三种情况:已经实现 → 指出现有实现在哪、不写 .out-of-scope/(那会污染查重);被拒的 bug → 解释后关闭;被拒的 enhancement → 写 .out-of-scope/、评论里给链接、再关闭。
七、摒弃了不声明的 AI 写作。 SKILL.md 里有一条硬规则:triage 期间发的每一条评论或 issue,开头必须是 > *This was generated by AI during triage.*
八、摒弃了「一次扫完整个 backlog」。 批量列表是给挑选用的廉价视图,它只有正文、没有评论。文档举了一个真实的翻车例子:三个 issue 的评论里早就写着「已修复,建议关闭」,结果它们各自又拿到一份新的 agent brief。要跑批量,你必须明说让它逐条读评论。
原文证据
硬规则和状态机原文:
Every comment or issue posted to the issue tracker during triage must start with this disclaimer:
> *This was generated by AI during triage.*Every triaged issue should carry exactly one category role and one state role. If state roles conflict, flag it and ask the maintainer before doing anything else.
PR 的等价性(第 11 行):
If this repo treats external pull requests as a request surface (see the issue-tracker config), triage covers them too: a PR is an issue with attached code, using the same roles, same states, and same machine, with a few deltas marked “for a PR” below.
「报告你在哪找过」和「等指示」(第 70、72 行):
Run two checks against the codebase: (a) redundancy: search for an existing implementation of the requested behavior by domain concept (not just the request’s wording), and report where you looked. Tell the maintainer your category and state recommendation with reasoning, plus a brief codebase summary relevant to the request (including whether it’s already implemented). Wait for direction.
brief 的第一原则(skills/engineering/triage/AGENT-BRIEF.md:15):
- Don’t reference file paths: they go stale
可迁移结论
- 把标签当状态机而不是分类目录。 每个 item 恰好一个状态、冲突即停,让「哪些在等我」这种查询变成一次简单过滤。
- 给状态机补一条「异常转移要问」的规则。
flag it and ask the maintainer before doing anything else一句话就把状态机的自由度收住了。 - 让 agent 先建议后执行。 咨询型参与比代理型参与便宜得多——错一次分类的代价,远大于多问一句。
- 让搜索可审计。 「按领域概念搜,而不是按请求的措辞搜」加「报告你在哪找过」,把一次搜索变成一份可以被人复核的证据。
- 产出物的规范值得单独写一份文档。 brief 的四条原则(durability over precision / behavioral not procedural / 完整可勾的验收标准 / 明确的边界)几乎可以照搬到任何「写给未来 agent 的任务说明」上。
- 写清触发边界。 「只处理不是你建的东西」这一句,比任何一条关于质量的说明都更能防止技能被滥用。
- 强制声明 AI 参与。 一条模板化的免责声明,成本近乎为零。
triage 是这套技能里少见的「纯入口」:它不写代码、不产出文件、不提供设计方法,只做三件事——把人递进来的东西查清楚、给出一个可被推翻的归类、然后把决定权交回去。它和 implement 之间只有一份 brief 的距离:车道不同,终点相同。
讨论
用 GitHub 账号留言;评论保存在公开仓库chengshu-blog-discussions的 Discussions 里。也可通过 RSS 订阅后续文章。