/triage:一个让 Issue 自己走进流水线的 AI 协议,一个案例让你彻底搞懂

/triage:一个让 Issue 自己走进流水线的 AI 协议,一个案例让你彻底搞懂 每个开源项目最终都会面对同一件事issue 比代码多而能看 issue 的人比 issue 还少。一、问题一个中型开源项目的 issue tracker 大概是这样的42 个未分类的 issue最早的一个贴了 8 个月其中 13 个是重复请求支持 dark mode第 5 次7 个只有一句话描述这个功能坏了没有复现步骤没有环境信息3 个已经被实现但 issue 还开着维护者每天最多能认真看 3 个——剩下的要么快速扫过要么永远不看Matt Pocock 写/triage就是对着这堆问题去的。他是 TypeScript 圈的内容创作者Total TypeScript 的创始人同时在维护多个开源项目。mattpocock/skills 是他为 Claude Code、Cursor、Windsurf 等 agent 编写的技能集合/triage是其中最核心的一个。一句话概括它的设计把 issue 的完整生命周期——从这是个什么东西到agent 拿走去执行——变成一套机器可读的状态机协议。二、状态机/triage的核心模型很小全部角色加起来只有 7 个。两个类别角色categorybug— 东西坏了enhancement— 新功能或改进五个状态角色stateneeds-triage— 维护者还没评估needs-info— 等待提问者补充信息ready-for-agent— 规格化完毕AFK agent 可以直接接手ready-for-human— 需要人来实现涉及判断、外部访问、设计决策wontfix— 不做每个合格的 triaged issue 恰好挂一个类别角色和一个状态角色。状态之间的流转是一个简单的有向图无标签 → needs-triage ├── needs-info ⇄ needs-triage提问者回复后回到评估队列 ├── ready-for-agent ├── ready-for-human └── wontfix维护者说 Move #42 to ready-for-agentskill 直接照办——跳过验证、跳过追问、跳过所有步骤。Matt 对这个设计的前提很清楚维护者的判断是最高指令状态机是工具不是裁判。但/triage做的事远比改标签多。它的工程价值在五步工作流里。五步工作流当维护者说 Lets look at #42/triage不会直接选标签。它会第一步收集上下文。读完整 issue body、所有评论、标签、作者、日期。针对代码库做两件事查冗余——按领域概念搜索已有实现不是关键词匹配查历史拒绝记录——扫描.out-of-scope/目录找出之前被拒绝的类似请求。如果发现dark-mode已经在代码库里实现了issue 直接标记为wontfix并关闭。Bug 是修好了但提问者不知道不是不修。第二步推荐。给维护者一个类别 状态的推荐附推理和代码库摘要。然后等着。第三步验证声明。对 bug按照提问者的步骤复现。对 PRcheckout 出来跑一遍。这条是整个工作流里最花功夫但最重要的一步——复现成功的 bug 和听起来像是真的的 bug后续的处理成本差了一个数量级。第四步追问。需求不够具体时调起/grilling和/domain-modeling两个 skill。/grilling一次一个问逼提问者把模糊需求钉死/domain-modeling同步把领域术语沉淀为结构化的CONTEXT.md和 ADR。问出来的东西写进项目知识库以后直接复用。第五步执行结果。四种走向ready-for-agent→ 贴一份 Agent Briefready-for-human→ 同样的结构但注明为什么不能交给 agentneeds-info→ 贴一份结构化的 triage notes已确认什么 还需要什么wontfix→ 关闭 issue。如果是增强功能被拒写入.out-of-scope/留档每一步 AI 写的评论都以这句话开头 *This was generated by AI during triage.*不打自招的标签没有假装是人类。三、Agent Brief给未来写的合约/triage里最有教益的部分是 Agent Brief。传统 issue 驱动的开发开发者依赖 issue body 里的描述来理解需求。但 issue body 是对话记录——它有情绪、有废话、有中途推翻的结论。它是上下文不是规格。如果 issue 在ready-for-agent队列里躺了两周代码库已经变了issue 里提到的文件可能挪了位置、改了签名、甚至被删了。Agent Brief 的设计哲学概括成一句话耐久性优先于精确性。描述接口和类型不要描述文件路径。描述行为和合约不要描述实现步骤。一份合格的 Agent Brief 包含六个部分字段做什么例子Categorybug 还是 enhancementbugSummary一句话说清描述截断会在单词中间切断产生损坏的输出Current behavior现在什么状况超过 1024 字符的描述在 1024 字符处硬截断Desired behavior完成后应该是怎样在 1024 字符前的最后一个单词边界截断追加 ...Key interfaces涉及的接口/类型签名SkillMetadata类型的description字段Acceptance criteria独立可验证的完成条件[] 超 1024 字符描述在最后单词边界截断Out of scope明确不做的事不改变 1024 字符限制本身注意什么不在里面文件路径。行号。当前实现结构的任何假设。// 好描述类型合约 The SkillConfig type should accept an optional schedule field of type CronExpression // 坏描述怎么改代码 Open src/types/skill.ts and add a schedule field on line 42这个区别就是给 agent 下命令和给 agent 写合约的区别。Agent 拿到合约后自己探索代码库、自己决定怎么实现。维护者不需要预测 agent 的路径只需要定义它的边界。对于 PR 上的 triageAgent Brief 有另一个用途。PR 是附了代码的 issue——一样的角色一样的状态但ready-for-agent的含义变成agent 应该在这个 diff 上继续工作。Brief 里写的是 diff 还缺什么而不是从零开始建什么。Matt 在源码里埋了一个对比好 Brief 和坏 Brief。坏 Brief 长这样## Agent Brief **Summary:** Fix the triage bug **What to do:** The triage thing is broken. Look at the main file and fix it. The function around line 150 has the issue. **Files to change:** - src/triage/handler.ts (line 150) - src/types.ts (line 42)没有 category、没有 acceptance criteria、没有 scope boundary、只有精确但速朽的文件路径和行号。这种 Brief 的有效期可能只有一次 push。四、.out-of-scope/项目的不做清单项目需要记录自己做了什么。但项目也需要记录自己决定不做什么以及为什么。.out-of-scope/目录是/triage的第二个设计发明。它是一套结构化拒绝记录一个概念一个文件Kebab-case 命名。每个文件写清楚概念名、决策、原因、以及所有提过这个请求的 issue 链接。目录结构简朴到近乎简陋.out-of-scope/ ├── dark-mode.md ├── plugin-system.md └── graphql-api.md格式是一篇短小的设计笔记不是数据库条目。# Dark Mode This project does not support dark mode or user-facing theming. ## Why this is out of scope The rendering pipeline assumes a single color palette defined in ThemeConfig. Supporting multiple themes would require: - A theme context provider wrapping the entire component tree - Per-component theme-aware style resolution - A persistence layer for user theme preferences This is a significant architectural change that doesnt align with the projects focus on content authoring. ## Prior requests - #42 — Add dark mode support - #87 — Night theme for accessibility - #134 — Dark theme optionMatt 对这个文件质量有一个硬性要求——原因必须有分量不能用我们太忙或暂时不考虑——那不是真正的拒绝那是推迟。文件写的是决策读它的人需要知道为什么。同样的设计自洽性出现在它的使用边界上只有被拒绝的增强功能才写入.out-of-scope/。因为已经实现了而关闭的 issue 不写入——那是已构建的功能不是拒绝。拒绝被记录已实现被链接二者永不混淆。当新 issue 进入 triage/triage会扫描.out-of-scope/做概念匹配。night theme 会被匹配到dark-mode.md。然后把匹配结果交给维护者这跟.out-of-scope/dark-mode.md很像——上次拒绝的理由是渲染管线假设单一调色板。还是一样的看法吗维护者可以确认关掉新 issue加到 Prior requests 里、重新考虑删掉.out-of-scope/文件这次接了、或者分清楚两个 issue 有关联但不重复正常走 triage。这套机制的价值在于让不变得有证据、可检索、可推翻。省时间只是附带的。五、从工具到工程文化/triage的技术实现不需要训练模型不调用外部 API不依赖 GPU。它就是一段指令告诉 agent 照这个协议干活。它改变的是维护者的行为成本没有 triage看一个 issue 的隐性成本是我得把它处理好。大多数人选择不看。有 triage成本降到底说一句 Lets look at #42阅读、查重、追问、规格化全由 agent 执行。维护者只做判断。自动化只是表象。实际效果是大幅降低判断前的准备成本。还有一个容易被忽略但极其重要的设计决策所有 AI 生成的评论都必须打上标记。 *This was generated by AI during triage.*不涉及技术涉及的是信任。issue tracker 是维护者和社区之间的公共空间。AI 写的评论读起来像人写的而没人知道——这个空间就被污染了。Matt 把它定成硬性规则放在 SKILL.md 顶部全大写。工程文化层面/triage在尝试一件事把这个项目怎么管理 issue从默认的混乱变成默认的秩序而代价只是安装一个 skill。它不要求维护者改变工作习惯。不需要学新工具不需要迁移 issue tracker不需要装 CI pipeline。装好后在 Claude Code 里敲/triage和 Show me what needs my attention 就可以开始。六、实战从零到第一个 Agent Brief讲到这里所有概念都还是纸上的。拿一个真实项目跑一遍看看这些概念在实际中怎么落地。我拿自己的 MCPVideo 项目做实验。先装好/triage然在 GitHub 上建 5 个不同类型的 issue——故意不分类、不打标签模拟真实的 issue tracker 初始状态。6.1 配置一分钟在项目根目录跑/setup-matt-pocock-skills。跟着提示走它问三个问题Issue 托管在哪里——选 GitHub IssuesLabel 映射——直接使用默认映射bug→bugenhancement→enhancement等等PR 要不要纳入 triage——选否配置完后项目的.claude/目录下多了一个 issue-tracker 配置文件。整个流程不超过一分钟。6.2 创建 5 个 Issue在 GitHub Issues 页面像平时一样新建#1 — 视频渲染到第 300 帧时卡住不动 #2 — 视频导出后画面全黑有声音但没画面 #3 — 希望能导出竖屏 9:16 格式 #4 — Scene3 过渡动画闪烁 #5 — 渲染速度太慢了能不能优化这些 issue 写得有好有坏。#2 带了复现步骤和环境信息#3 的需求很清晰#1 和 #5 只有一句话#4 介于两者之间。这就是真实项目中 issue 质量的常态分布。建完之后所有 issue 都是unlabeled。零标签零分类。6.3 第一轮查看未分类列表在 Claude Code 里敲/triage Show me what needs my attention输出Unlabeled (5): #1 — 视频渲染到第 300 帧时卡住不动 (1天前) #2 — 视频导出后画面全黑有声音但没画面 (刚创建) #3 — 希望能导出竖屏 9:16 格式 (刚创建) #4 — Scene3 过渡动画闪烁 (刚创建) #5 — 渲染速度太慢了能不能优化 (刚创建) needs-triage: (empty) needs-info: (empty)5 个 issue一条线。从这里开始每处理一个就少一个悬而未决。6.4 处理 #2Bug 里的诊断/triage Lets look at #2/triage开始干活。第一步收集上下文。它读了 #2 的完整 body——标题说画面全黑但 body 里贴了完整的 Remotion 渲染日志。它扫描了.out-of-scope/目录空的这是新项目然后在src/里搜索渲染管线相关的代码。日志里藏了根因。渲染日志末尾清清楚楚写着Error: Could not find composition with ID SelfAttentionVideo. Available compositions: llm-context-video, ai-scan-video, ai-agent-video, ... self-attention-video, ...提问者敲的命令是npx remotion render SelfAttentionVideo out.mp4但正确的 composition ID 是self-attention-video全小写 连字符不是 PascalCase。渲染根本没开始更谈不上画面黑不黑。/triage给出了推荐推荐needs-info渲染日志显示渲染从未开始——composition ID 不匹配。提问者用了SelfAttentionVideoPascalCase但正确的 ID 是self-attention-video小写连字符。这不像是画面渲染问题更像是指令错误。要问提问者的问题是否正确指定了 composition ID是故意选择SelfAttentionVideo还是从另一个命令复制过来的AI 不是魔法。它没看到画面全黑——它看到的是错误日志而日志已经告诉了答案。维护者不需要亲自读 200 行日志/triage读完了给了三行结论。在我确认后/triage在 issue 下贴了一条评论This was generated by AI during triage.## Triage NotesWhat weve established so far:- Issue body 中的渲染日志显示了具体错误composition IDSelfAttentionVideo不存在- 可用的 composition ID 包括self-attention-video、token-video、prompt-video等What we still need from you (Lincats888):- 你使用的是哪个 composition ID日志里列出的self-attention-video全小写 连字符是否就是你要渲染的- 确认后如果还有黑屏问题我们需要一个--still-frame的截图来检查画面内容然后打上needs-info标签。下一步等提问者回复。关键洞察/triage的价值不是选对标签——是找到 issue body 里提问者自己没注意到的关键信息。如果维护者手动看这个 issue可能也要花 5 分钟读日志。Agent 用了 20 秒。6.5 处理 #3发现已实现的功能/triage Lets look at #3这是一个 enhancement希望能导出竖屏 9:16 格式。/triage照例收集上下文读 issue body、扫.out-of-scope/、在代码库里搜索。然后它发现了一个让人有点意外的事实代码库里已经有 60 个竖屏 composition。MCPVideo 的src/index.tsx里注册了大量width{1080} height{1920}的 compositionem-s1-compare、dc-s1-hook、ti-s1-opening等等。竖屏模式不仅存在而且是项目中占比最大的格式。/triage汇报了代码库摘要项目中绝大多数 composition 已经使用竖屏 1080×1920 分辨率。src/index.tsx第 426 行起em-s1-compare竖屏等 composition 都已注册。自注意力视频系列oss-s1-hook等也全部是竖屏。建议wontfix已实现。这不是不做而是已经做好了。提问者可能不知道项目里已经有竖屏输出或者只是没有在自己创建的横屏 composition 上找到设置竖屏的地方。/triage关闭了 issue注释写明了原因和竖屏 composition 的位置。没有写入.out-of-scope/——因为这是已构建的功能不是被拒绝的请求。6.6 快速处理剩下的 #1 和 #5 不需要一步步走。维护者有权直接下命令/triage Move #1 to needs-info ← 没有复现步骤 /triage Move #4 to ready-for-agent ← 描述足够清晰agent 可以直接修 /triage Move #5 to needs-info ← 太慢了没有具体数据5 分钟前项目有 5 个未分类 issue。5 分钟后#标题最终状态#1视频渲染到第 300 帧时卡住不动needs-info#2视频导出后画面全黑needs-info等提问者确认 composition ID#3竖屏格式wontfix已实现#4Scene3 过渡动画闪烁ready-for-agent#5渲染速度优化needs-info缺少具体数据没有悬而未决。没读任何日志。只做了判断。6.7 这条流水线改变了什么回过头看这 5 分钟装了一个 skill建了 5 个 issue对着 Claude Code 说了不到 10 句话。结果每个 issue 都有了归宿。/triage没有让 issue 消失。但维护者面对的从5 个需要仔细读的未知问题变成了4 个在等别人回复needs-info1 个 agent 可以直接开干ready-for-agent1 个已经关了因为早就做完了维护者自己的 todo 从 5 变成了 0。小结/triage做了三件事把 issue 管理从自由文本变成状态机每个 issue 的路径可追踪把维护者的意图固化为 Agent Brief一份不依赖文件路径、两周后仍可执行的合约把不做的决定沉淀为.out-of-scope/知识库拒绝不再是消失是归档适合什么场景开源项目有 50 个 issue 但只有一个维护者团队想引入 agent 辅助开发、但不知道从哪里开始已经在用 Claude Code / Cursor / Windsurfissue tracker 是 GitHub不适合什么场景issue 量还很少的早期项目状态机的开销大于收益维护者自己就是唯一写代码的人、不需要任何分工项目没有使用 AI agent 工具链安装方式npx skills add mattpocock/skills --skill triage # 然后在仓库里跑 /setup-matt-pocock-skills 配置标签映射整套技能不过几百行 Markdown 指令。它把开源维护里最消耗意志力的事——看 issue——从需要勇气面对变成照流程走一遍。流程走完一个问题要么成了 agent 可以拿走去写的 Brief要么成了一条有理有据的不做记录。没了悬而未决。