周一早上你打开项目的 Issue tracker(放 issue 的地方:GitHub Issues、Linear,
或者本仓库里 .scratch/ 下的一套 markdown 约定),里面堆着三十条没处理过的东西:
两条 bug 报告、一个外部贡献者发来的 PR、一堆「能不能加个 dark mode」式的请求。
你不可能一条条凭感觉看过去。triage 就是干这个的:
它把每条 issue 推过一台小型状态机——先分类、再验证、必要时把请求烤(grill)成形,
最后在 issue 上留下一份 AFK agent(无人值守、自己干活的 agent)能直接接手的简报。
它由你手动输入 /triage 启动,AI 不会自己去碰它。
这节课你会学到:七个角色标签组成的状态机、处理单条 issue 的五步流水线、
它往 tracker 和仓库里写的每一样东西,以及行为不对时该去改哪个文件的哪一段。
0001 的系统地图把 22 个 skill 分为主流程、入口(on-ramp)和独立件。triage
是入口之一:ask-matt 的原文是「Bugs and requests piling up → /triage」——
外部世界不断往你的 tracker 里塞原始报告,triage 把这堆东西加工成
agent 能接的活,然后汇进主流程。它和另一条入口 diagnosing-bugs 的区别在于节奏:
diagnosing-bugs 是「这个东西现在坏了,现在就修」;
triage 是「队列在堆积,我定期来分拣一遍」。docs 把它叫作 tracker 的
periodic maintenance(定期保养)——报告堆起来时就跑一遍,
让 ready-for-agent 这一列永远可信。
它在链条上的准确位置是「tracker 的最前端,build 链的上游」:
它产出的 agent brief(agent 简报,后面第 7 节详讲)会被 implement 拾取,
implement 内部再驱动 tdd 把活干完。
所以 triage 自己从不写实现代码——它写到 tracker 里的简报,就是下游一切的合同。
triage 只处理不是你创建的 issue——
bug 报告、外部来的功能请求,一切「 raw(未加工)地抵达」的东西。
to-tickets 从 spec 切出来的那些 issue 不要喂给
triage:它们诞生时就已经是 agent-ready 的,再走一遍分诊是纯浪费,
还会把已经定好的形状重新搅浑。
它还有一个方向上的近邻:to-spec。docs 的原话是,
to-spec 把一场刚刚发生的对话变成 tracker 里的新内容(对话 → tracker),
而 triage 处理的是已经落在 tracker 里的东西(tracker → 可执行的工作)。
两个 skill 读写同一个 tracker,方向正好相反。
路由原文:ask-matt/SKILL.md 的 On-ramps 一节 · 定位:docs/engineering/triage.md 的 Where it fits 一节 · 地图:0001
SKILL.md 的 frontmatter 里写着 disable-model-invocation: true,
同目录的 agents/openai.yaml 里写着 policy.allow_implicit_invocation: false。
两条加起来的意思是:这是一个 user-invoked(只能人启动)的 skill——
AI 在任何情况下都不应该自作主张去跑它。
想想也合理:分诊会往真实的 tracker 上写标签、发评论、关 issue,
这种对外副作用必须等你明确下令。
启动方式是输入 /triage 加一句自然语言的意图,skill 负责解释这句话并行动。
SKILL.md 给的例子:
triage 要读写你的 Issue tracker,所以它依赖
setup-matt-pocock-skills(0002 讲过)事先写好的两份配置文件:
| 配置文件(在被配置的项目仓库里) | 告诉 triage 什么 |
|---|---|
docs/agents/issue-tracker.md |
tracker 在哪、用什么 CLI 操作(GitHub 用 gh,GitLab 用 glab,本地就是读写 .scratch/ 下的 markdown 文件);外部 PR 算不算请求入口(request surface);一个光秃秃的 #42 该解析成 issue 还是 PR |
docs/agents/triage-labels.md |
五个状态角色的规范名到你的 tracker 真实标签字符串的映射。规范名(canonical name)是 skill 内部统一使用的名字;你的 tracker 可能管 needs-triage 叫 bug:triage,映射表让 skill 贴你已有的标签,而不是新建一套重复的 |
SKILL.md 原话:「这些映射应该已经提供给你了——如果没有,运行
/setup-matt-pocock-skills」。所以行为排查时,
「triage 贴的标签我 tracker 里根本没有」第一反应不是改 skill,而是去看那份映射表。
触发规则:skills/engineering/triage/SKILL.md frontmatter 与 Invocation 一节 · 隐式调用禁令:triage/agents/openai.yaml · 映射从哪来:setup-matt-pocock-skills/triage-labels.md · tracker 配置:setup-matt-pocock-skills/issue-tracker-github.md
CONTEXT.md 给过一个定义:Triage role(分诊角色)是分诊过程中贴到
issue 身上的规范状态机标签,每个角色通过 triage-labels.md 映射到 tracker
里的真实标签字符串。triage 一共用七个角色,分两族:
| 角色 | 属于哪族 | 含义(按 SKILL.md 原文) |
|---|---|---|
bug |
category(分类) | 有东西坏了——这是一个缺陷报告 |
enhancement |
category(分类) | 新功能或改进——这是一个请求 |
needs-triage |
state(状态) | 维护者需要评估——已经排进分诊队列,但还没看完 |
needs-info |
state(状态) | 等报告者补充信息——现有的细节不够下判断 |
ready-for-agent |
state(状态) | 规格完整,AFK agent 可以直接接手 |
ready-for-human |
state(状态) | 需要人来实现——涉及判断、外部权限或手工验证,不宜交给 agent |
wontfix |
state(状态) | 不会处理——要么已经实现,要么被拒绝 |
每条被分诊过的 issue 必须恰好携带一个 category role 和一个 state role——
不能没有,也不能同时挂两个状态。如果发现状态角色冲突(比如同时贴着
needs-info 和 ready-for-agent),skill 的指令是:
指出来,先问维护者,再做任何其它事。
它不替你猜哪个状态是真的。
报告者回复了
┌─────────────────────────────┐
▼ │
(无标签) ──► needs-triage ──┬──► needs-info
│ │
│ ├──► ready-for-agent ──► agent brief 已挂上,
│ │ implement 可拾取
│ ├──► ready-for-human ──► 等人来实现/合并
│ └──► wontfix ──► 关闭(理由写进评论,
│ 被拒绝的 enhancement 进 .out-of-scope/)
└── 维护者随时可以覆盖任何流转;
看起来不寻常的流转 → 先问再动
按 SKILL.md 的状态流转一节:一条没标签的 issue 通常先进
needs-triage;从那里流向四个终态或中间态之一;
needs-info 在报告者回复之后回到 needs-triage
重新评估(这就是第 5 节总览队列第三个桶存在的原因)。
维护者在任何时候都能覆盖流转——但 skill 被要求「把看起来不寻常的流转标出来,先问再继续」。
角色与流转:triage/SKILL.md 的 Roles 一节 · 词汇定义:CONTEXT.md 的 Triage role 词条
如果你的 tracker 配置把外部 PR 当作请求入口(request surface),triage
对 PR 走的是同一台状态机:同样的分类角色、同样的状态、同样的流转。
SKILL.md 的原话是「a PR is an issue with attached code」——
一个 PR 就是一条附了代码的 issue。差别只在状态的读法上:
对 PR 来说,状态是对着 diff(代码差异)读的——ready-for-agent
意思是「简报已挂上,agent 应该在这份 diff 的基础上继续推进」;
ready-for-human 意思是「人可以来合并了」。
| 规则 | 内容 |
|---|---|
| 开关在哪 | docs/agents/issue-tracker.md 里的「PRs as a request surface」标志。setup 生成模板时默认关闭,想开的人自己把文件里的标志翻成 yes——setup 故意不主动提这个选项 |
| 队列里出现哪些 PR | 只出现外部 PR——「外部」由 tracker 配置定义。GitHub 模板的做法:按 authorAssociation 过滤,保留 CONTRIBUTOR、FIRST_TIME_CONTRIBUTOR、NONE,丢掉 OWNER/MEMBER/COLLABORATOR。合作者自己进行中的 PR 不是分诊工作 |
| 过滤器只管发现 | 「只看外部」这条只作用于第 5 节的队列总览。你点名一条具体的 PR(「看一下 #57」)时,不管作者是谁都照常分诊 |
| #42 是 issue 还是 PR | 按 tracker 配置解析。GitHub 上 issue 和 PR 共享一个编号空间,模板的写法是先 gh pr view 42,失败再退回 gh issue view 42 |
PR 规则:triage/SKILL.md 开头第二段与 Show what needs attention 一节 · 外部定义与编号解析:issue-tracker-github.md 的 Pull requests as a triage surface 一节 · 默认关闭:setup-matt-pocock-skills/SKILL.md 的 Section A
你说「给我看看需要操心的东西」时,skill 查询 tracker,把结果分成三个桶, 每桶内部最老的排在最前:
needs-triage——评估进行到一半的;needs-info 且自上次分诊笔记以来报告者有新活动——你等的答案来了,需要重新评估。
PR 在范围内时,外部 PR 也进这三个桶,每行打 [PR] 或 [issue] 标记。
展示形式是「数量 + 每条一行摘要」,然后由你挑先看哪条——skill 不替你排序优先级。
注意第三个桶的设计意图:needs-info 不是死状态,报告者一回复,
这条 issue 就自动浮回你的注意力里(对应第 3 节「回复后回到 needs-triage」的流转)。
这是整个 skill 的主干。你点名一条 issue(或从队列里挑了一条)之后,
SKILL.md 规定五步按序走:
读完整的 issue 或 PR——正文、评论、标签、作者、日期;是 PR 就再读 diff。
解析所有历史分诊笔记,避免把已经回答过的问题再问一遍。
然后用项目的领域词汇表(CONTEXT.md)去探索代码库,尊重相关区域的 ADR
(架构决策记录,0004 讲过)。探索时跑两个检查:
.out-of-scope/*.md,
看这条请求像不像某个已经被拒绝过的概念(详见第 8 节)。
把你的分类建议、状态建议、推理过程,连同一段与请求相关的代码库摘要
(包括「这东西是不是已经实现了」)一起告诉维护者——然后停下来等指示。
docs 把这叫作「recommends and waits」:它给出带理由的判断,但只在你点头之后才动手。
这是 triage 和「凭感觉贴标签」最不一样的地方之一。
在任何面试(grilling)开始之前,先验证 issue 里的说法站不站得住:
结果分三种:confirmed(确认,附带指向具体代码路径)、failed
(没复现出来)、insufficient detail(细节不够验证——这本身就是强烈的
needs-info 信号)。SKILL.md 特别强调:
一次被确认的验证会让后面写出来的 agent brief 强得多——
「我按步骤复现了,断在 X 函数」和「报告者说坏了」是两种完全不同成色的简报。
triage 和临时起意贴标签区分开的就是验证这一步。
如果 AI 在分诊时跳过重现、直接凭描述写简报,这不是「快」,这是违反了
SKILL.md 第三步的明文规定——排查行为时先查这一段。
如果请求还需要充实形状,skill 把 /grilling 和
/domain-modeling 两个 skill 一起拉进来:
一次只问一个问题,把请求烤成形;过程中磨尖领域术语,决策一落地就就地更新
CONTEXT.md 和 ADR。这就是 0003 讲的面试纪律和 0004 讲的领域建模纪律
在 tracker 场景下的复用——triage 自己不重新发明面试,它调用现成的两个 skill。
烤出来的每一点共识都要接住:第五步发 needs-info 时,
它们全部进「已确定事项」,不丢。
五种出口,各写各的东西:
| 出口状态 | 往 tracker / 仓库写什么 |
|---|---|
ready-for-agent |
贴状态标签,并发一条 agent brief 评论(写法见第 7 节 AGENT-BRIEF.md) |
ready-for-human |
结构和 agent brief 相同,但要额外写清为什么不能委派给 agent——需要人工判断、需要外部系统的访问权限、涉及设计决策、或必须手工测试 |
needs-info |
发一条分诊笔记评论,用固定模板(见下面 6.7) |
wontfix |
关闭 issue;评论内容取决于为什么关:已实现 → 指出功能在代码里的位置,且不写 .out-of-scope/;被拒绝的 bug → 礼貌解释后关闭;被拒绝的 enhancement → 写 .out-of-scope/、在评论里链过去、再关闭(见第 8 节) |
needs-triage |
只贴标签;有部分进展时可以选发一条评论 |
你说「move #42 to ready-for-agent」时,skill 的指令是信任你:
直接应用角色,跳过收集、验证、面试整套流程。但有两条保留:
动手前先复述确认你将要做的事(改什么角色、发什么评论、关不关 issue);
如果是没经过面试就进 ready-for-agent,要补问一句「要不要写一份 agent brief」。
## Triage Notes
**What we've established so far:**
- point 1
- point 2
**What we still need from you (@reporter):**
- question 1
- question 2
两条硬要求:面试中敲定的一切放进「established so far」,让已经做掉的工作不丢;
问题必须具体、可行动——「please provide more info」这种空话被明文禁止。
这份笔记同时是下一次分诊的恢复点:SKILL.md 的 Resuming 一节规定,
只要 issue 上存在历史分诊笔记,重开时先读它、检查报告者有没有回答悬着的问题、
给出更新后的全貌,再继续——不许重复问已解决的问题。
分诊期间发到 tracker 上的每一条评论、每一个新 issue,都必须以这行 disclaimer(免责声明)开头:
> *This was generated by AI during triage.*
这是对外透明义务:报告者和贡献者有权知道这条回复是 AI 在分诊过程中生成的。
漏掉它不是格式小瑕疵,是 SKILL.md 开头用 must 写的硬性规定。
五步流水线、快速通道、模板、免责声明、恢复规则: triage/SKILL.md 的 Triage a specific issue or PR、Quick state override、Needs-info template、Resuming a previous session 各节
第一个 sibling 文件 AGENT-BRIEF.md 讲的是 ready-for-agent
那一刻发出去的那条评论怎么写。开篇第一句定调:agent brief 是 AFK agent 工作时依据的
权威规格——issue 的原始正文和讨论只是背景,brief 才是合同。
原始讨论可能又乱又长,合同必须干净。对 issue 来说,brief 是「从零把东西建出来」;
对 PR 来说,brief 是「在这份已有 diff 上还剩什么」——补完它、堵上缺口、回应评审意见。
| 原则 | 意思 | 具体要求 |
|---|---|---|
| Durability over precision(耐放优先于精确) | issue 可能在 ready-for-agent 里躺几天甚至几周,期间代码库会变;brief 要在文件被改名、移动、重构之后依然有用 |
描述接口、类型和行为契约;点名具体的类型名、函数签名、配置结构。不引用文件路径(会过期)、不引用行号、不假设当前的实现结构保持不变 |
| Behavioral, not procedural(写行为,不写步骤) | 写系统应该做什么,不写怎么实现——agent 会重新探索代码库,自己做实现决策 | 好:「SkillConfig 类型应接受一个 CronExpression 类型的可选 schedule 字段」。坏:「打开 src/types/skill.ts 在第 42 行加一个字段」 |
| Complete acceptance criteria(完整的验收标准) | agent 需要知道自己什么时候算干完;每条标准都要具体、可测、能独立验证 | 好:「运行 gh issue list --label needs-triage 能返回经过初步分类的 issue」。坏:「Triage should work correctly」 |
| Explicit scope boundaries(明确的范围边界) | 声明什么不做,防止 agent 镀金(gold-plating:顺手做了没人要的额外功能)或对相邻功能自作主张 | 模板里有独立的 Out of scope 一节,列出「不该动的东西」和「看着相关但属于另一条 issue 的东西」 |
## Agent Brief
**Category:** bug / enhancement
**Summary:** 一句话说清要发生什么
**Current behavior:** 现在是什么样(bug 就是坏掉的行为;enhancement 就是现状)
**Desired behavior:** 干完之后应该是什么样(含边界情况和出错条件)
**Key interfaces:** 要动的类型 / 函数签名 / 配置结构,以及为什么
**Acceptance criteria:** 可勾选的、逐条可独立验证的标准
**Out of scope:** 明确不改、不碰的东西
AGENT-BRIEF.md 还给了三份好示例(bug 版、enhancement 版、PR 版)和一份坏示例。
坏示例的罪名清单值得背下来:没分类、描述含糊(「the triage thing is broken」)、
引用了会过期的文件路径和行号、没有验收标准、没有范围边界、没写现状与期望的差别。
PR 版的差别只有一处:「Current behavior」描述的是 diff 的现状,
brief 请 agent 把这份 diff 做完、修好,而不是从零开工。
原则、模板、四份示例:triage/AGENT-BRIEF.md
第二个 sibling 文件 OUT-OF-SCOPE.md 讲的是被配置项目仓库根下
.out-of-scope/ 目录的玩法。它存的是被拒绝的功能请求的持久记录,
服务两个目的:
dark-mode.md 下。dark-mode.md、plugin-system.md——扫一眼目录名就知道拒绝过什么。Prior requests 列表收集所有请求过这个概念的历史 issue 链接。| 时机 | 规则 |
|---|---|
| 读:分诊第一步收集上下文时 | 读完所有 .out-of-scope/*.md。匹配按概念相似度,不是关键词——「night theme」要能撞上 dark-mode.md。撞上了就端给维护者:「这条像 .out-of-scope/dark-mode.md,我们当时因为 [理由] 拒了,你还这么想吗?」 |
| 维护者的三种回应 | Confirm(维持原判):新 issue 追加进该文件的 Prior requests 列表,然后关闭;Reconsider(重新考虑):删掉或更新该文件,issue 走正常分诊;Disagree(不认同匹配):两条事相关但不同,照常分诊 |
| 写:仅当 enhancement 被拒为 wontfix | 只有这一种情况写 .out-of-scope/。被拒绝的 bug 不写;已实现而关的更不写——那是建成的功能不是拒绝,写进去会用假拒绝毒化去重检查。enhancement PR 和 issue 一视同仁,被拒照样记录,免得同一个请求换件代码的外衣再回来 |
| 反悔 | 维护者改主意了:删掉对应文件;不重开历史 issue(它们是历史记录);触发反悔的那条新 issue 走正常分诊 |
全部规则:triage/OUT-OF-SCOPE.md · 与第五步的衔接:triage/SKILL.md 的 Apply the outcome 一节
| 写到哪里 | 写什么 | 什么时候发生 |
|---|---|---|
| Issue tracker(标签) | 应用/移除 Triage role 对应的真实标签 | 每次状态流转;保证一条 issue 恰好一个分类 + 一个状态 |
| Issue tracker(评论) | agent brief、分诊笔记(needs-info 模板)、wontfix 的说明评论——全部以 AI disclaimer 开头 | 第五步落地结果时 |
| Issue tracker(状态) | 关闭 issue | wontfix 出口;以及(流程上)验证不成立等维护者决定的情形 |
仓库 .out-of-scope/*.md |
新建、追加(Prior requests)、更新或删除概念文件 | 仅当 enhancement(含 PR)被拒;或维护者对旧决定反悔 |
仓库 CONTEXT.md / ADR |
面试中敲定的领域术语和架构决策 | 第四步 grill 时,由 domain-modeling 就地写入——不是 triage 自己的文件格式 |
| 不写什么 | 实现代码、测试、spec、临时 HTML 报告 | 实现是 implement/tdd 的事;triage 只把队列加工成可接的活 |
setup-matt-pocock-skills(前置:tracker 配置 + 标签映射)
│
▼
原始 issue/PR ──► /triage ──┬── 拉进 /grilling + /domain-modeling(第四步)
(别人创建的) │ └── 就地更新 CONTEXT.md / ADR
│
├── ready-for-agent(挂着 agent brief)
│ ▼
│ /implement(内部驱动 /tdd)拾取并干活
├── ready-for-human ──► 人来实现 / 合并
└── wontfix ──► .out-of-scope/(仅被拒的 enhancement)
不喂给它:/to-tickets 切出来的 issue(出生即 agent-ready)
方向近邻:/to-spec(对话 → tracker,和 triage 的 tracker → 工作 正好相反)
ready-for-agent 列,挑一条开 /implement(它会内部驱动 tdd);每条之间清上下文。/grill-with-docs 开始走 0001 的主线。/diagnosing-bugs 那条入口(0015),分诊只负责分拣,不负责修。.out-of-scope/ 里的理由说话;要翻案就走 8.2 的 Reconsider 分支,删文件、重走分诊。| 症状 | 先查 | 不要误改 |
|---|---|---|
| 贴出来的标签在你的 tracker 里不存在 / 贴成了重复的 | 项目仓库里 docs/agents/triage-labels.md 的右列映射(setup 生成的种子在 setup-matt-pocock-skills/triage-labels.md) |
triage/SKILL.md 里的七个角色规范名——那是跨项目统一的词,不该本地化 |
| AI 没重现 bug 就写了「已确认」的简报 | triage/SKILL.md 第三步 Verify the claim |
AGENT-BRIEF.md 的模板——问题在流程纪律,不在简报格式 |
| 简报里出现文件路径、行号,过两周就失效 | AGENT-BRIEF.md 的 Durability over precision 一节 |
implement 拾取简报的方式——它只负责读合同 |
| bug 被拒也写进了 .out-of-scope/,或「已实现」的也写了进去 | SKILL.md 第五步 wontfix 的三个分支 + OUT-OF-SCOPE.md 的 When to write 一节 |
.out-of-scope/ 里已有文件的文风——先改规则再改文体 |
| 队列总览里混进了合作者自己的 PR | 项目仓库 docs/agents/issue-tracker.md 里「外部」的定义(GitHub 模板是 authorAssociation 过滤清单) |
triage/SKILL.md 的发现规则——它故意把「谁算外部」下放给配置 |
| 外部 PR 完全没进分诊视野 | issue-tracker.md 的「PRs as a request surface」标志(setup 默认是 no) |
triage 的角色表——PR 和问题共用角色,不需要新角色 |
| 发到 tracker 的评论缺了 AI disclaimer | SKILL.md 开头第三段的 must 条款 |
评论的其它措辞——免责声明是固定的一行 |
| 重开一条 issue 时把已回答的问题又问了一遍 | SKILL.md 的 Resuming a previous session 一节 |
needs-info 模板本身——模板没错,是恢复流程没走 |
| AI 在你没发话时自己跑去分诊队列 | frontmatter 的 disable-model-invocation 与 agents/openai.yaml 的 allow_implicit_invocation: false——两者都在就是安装/加载问题 |
description 里的触发词——user-invoked 的 skill 不靠触发词被模型捡起 |
先别往回翻,凭记忆答。选项长度刻意对齐,不会从版式泄题。答案以 SKILL.md 和两个 sibling 文件的原文为准。
.out-of-scope/、哪一个明确不写。
最后回想你参与过的某个开源项目:它的 tracker 里现在哪几条该进第 5 节的哪个桶?
本课主一手材料(请打开原文读,不要只背本页摘要):
skills/engineering/triage/SKILL.md
—— 七个角色、状态流转、调用方式、三个桶、五步流水线、快速通道、分诊笔记模板、恢复规则、AI disclaimer。
…/AGENT-BRIEF.md
—— agent brief 的四条写作原则、模板、三份好示例与一份坏示例。
…/OUT-OF-SCOPE.md
—— .out-of-scope/ 知识库的文件格式、读写时机、维护者的三种回应。
docs/engineering/triage.md
—— 给人看的叙事版:「PR 是带代码的 issue / 先验证再写简报 / 它在链条上的位置」
(aihero.dev/skills-triage)。
速查页(本课同步): reference/triage.html
导航: 上一课 0013 code-review (implement 收尾时的两轴评审——分诊写出的简报最终会在那里被对照 Spec 轴检查)。 总览仍回 0001 系统地图; 前置配置见 0002 setup; 第四步拉进来的两个 skill 见 0003 grilling 与 0004 domain-modeling; 下游接力见 0011 implement 与 0012 tdd。
建议下一课(0015): 0015 diagnosing-bugs —— 另一条「有东西坏了」的入口。分诊课上你学会了把 bug 报告分拣、验证、写成简报; 下一课学的是当你决定现在就修时,怎么先要一条会变红的反馈回路、 再用回归测试把 bug 锁死。两课共用「先验证、别猜」的纪律,正好连着读。
SKILL.md / AGENT-BRIEF.md / OUT-OF-SCOPE.md 的原文,不会临场编造。
做完检索练习后,回复「练习结果 / 哪里卡住 / 开 0015 或先补 0013」,我们安排下一课。