RFC 编写器 是做什么的?
帮助用户编写一篇能通过审查的RFC。RFC是清晰思考的强制函数,而非事后撰写的文档。它的价值在于其迫使你进行的思考:诚实的目标、真实的备选方案、明确的风险以及具体的设计。一份漂亮但备选方案含糊、目标模糊的文档毫无价值。本技能旨在防止这种情况。
本技能天生具有交互性。它通过访谈来引导用户浮现思考,将每项主张扎根于实际代码库,并在撰写任何散文之前向用户展示设计的可工作代码。它绝不会编造困难的部分。
原则
- 思考即产品。 文档只是副产品。如果某个部分无法诚实地填写(没有真实的备选方案,没有已知的风险),请明确说明,而不是编造填充内容。
- 扎根每项主张。 在代码库中,使用文件引用来支持关于当前系统的陈述。未阅读代码之前,切勿断言代码的工作方式。
- 展示,而非讲述。 设计通过用户从真实选项中挑选的可工作API/代码示例来传达,而非通过散文。这是不可协商的(参见阶段3)。
- 被拒绝的选项不是浪费。 用户未选中的方法将变成“考虑的备选方案”部分,并附上它们的代码。这正是为什么这里的备选方案是真实的,而非假想敌。
- 只问未回答的问题。 如果用户的请求、关联的工单或代码本身已经回答了某个维度,不要重复提问。而是确认你的推断。
- 简洁无情。 每个要点都用最少的词语表达,同时保持清晰。优先使用项目符号而非段落,用一句话胜过三句。无开场白,无赘述,无填充。如果一个部分可以用一行表示,就用一行。代码片段承载了设计,因此其周围的散文应保持最少。读者应在几分钟内浏览完整个RFC。
- 在生成的文档内容中不要使用破折号。请使用逗号、冒号、句号或括号。
流程
digraph rfc {
rankdir=TB;
"解析输入" [shape=box];
"阶段1:扎根" [shape=box];
"在代码库中?" [shape=diamond];
"扫描代码 + 检测模板" [shape=box];
"阶段2:访谈" [shape=box];
"阶段3:设计选项" [shape=box];
"选项已选定?" [shape=diamond];
"阶段4:组装" [shape=box];
"阶段5:自我批判" [shape=box];
"阶段6:审查循环" [shape=box];
"已批准?" [shape=diamond];
"阶段7:输出" [shape=box];
"解析输入" -> "阶段1:扎根";
"阶段1:扎根" -> "在代码库中?";
"在代码库中?" -> "扫描代码 + 检测模板" [label="是"];
"在代码库中?" -> "阶段2:访谈" [label="否"];
"扫描代码 + 检测模板" -> "阶段2:访谈";
"阶段2:访谈" -> "阶段3:设计选项";
"阶段3:设计选项" -> "选项已选定?";
"选项已选定?" -> "阶段3:设计选项" [label="细化/新选项"];
"选项已选定?" -> "阶段4:组装" [label="是"];
"阶段4:组装" -> "阶段5:自我批判";
"阶段5:自我批判" -> "阶段6:审查循环";
"阶段6:审查循环" -> "已批准?";
"已批准?" -> "阶段6:审查循环" [label="变更"];
"已批准?" -> "阶段7:输出" [label="是"];
}
不要跳过阶段。 如果用户将多个答案打包到一条消息中,接受它们并跳过他们已经回答的部分。绝不要跳过阶段3(用户必须看到并从真实代码选项中选择设计)或阶段5(自我批判)。
阶段1:扎根
解析输入
用户可能提供:自由形式的描述、GitHub PR/Issue、Linear 工单,或者只是一个主题。提取已经回答的内容,以便在阶段2中不再重复提问。
如果在代码库内(必须完成)
扫描代码以理解用户所提议内容的真实上下文。 在不了解其所更改系统的情况下,你无法撰写一篇扎根的RFC。
- 检测仓库惯例。 全局搜索现有的RFC设置:
rfcs/、docs/rfcs/、docs/rfc/、**/*rfc*template*、adr/、docs/adr/、.github/*TEMPLATE*。如果存在模板文件,采用其章节标题及其所在目录,而不是内置模板,并向用户说明一行:使用你的仓库的 docs/rfcs/template.md;覆盖我的默认结构。无需提出需要用户回答的问题。 - 扫描相关子系统。 读取目录树(名称)、README/架构文档以及提案涉及的模块。理解:受影响区域当前的工作方式、已在使用的模式和抽象、真实的约束(类型、接口、数据流)。这将为“背景介绍”部分提供依据,关键在于,它使阶段3的代码选项能够匹配现有惯例。
- 记录文件引用,你将在RFC中引用这些引用(
path:line)。
如果不在代码库内:跳过扫描和检测;访谈将驱动一份使用内置模板的独立文档。
阶段2:访谈
朝着以下维度推进。对于每个维度,首先检查它是否已经从输入或代码扫描中得到回答;如果是,则确认你的推断(我读到X的工作方式是Y - 对吗?),而不是直接提问。一次一个地提出真正开放的问题。当代码扫描为你提供了一个候选答案时(尤其是备选方案和风险),提出它,让用户做出反应,而不是让他们从头生成。
核心维度(始终需要)
| # | 维度 | 对应的章节 |
|---|---|---|
| 1 | 问题 - 什么功能缺失/损坏,当前痛点 | 背景介绍 / 动机 |
| 2 | 为何现在 - 触发因素、不行动的成本 | 动机 |
| 3 | 目标 - 成功的样貌,尽可能可衡量 | 目标 |
| 4 | 非目标 - 明确不在范围内 | 非目标 |
| 5 | 提议的方法 - 在阶段3作为设计选项处理 | 提议的设计 |
| 6 | 备选方案 - 从阶段3中未被选中的选项免费获得 | 考虑的备选方案 |
| 7 | 风险/权衡 - 可能出错的地方,需要付出的成本 | 风险与权衡 |
| 8 | 推出计划 - 迁移、分阶段、向后兼容、测试 | 推出计划 |
| 9 | 批准人/利益相关者 - 谁做决定、谁受影响 | 批准人 |
| 10 | 开放问题 - 已知的未知事项 | 开放问题 |
条件性维度(仅在必要时询问)
- 安全/隐私 - 当提案涉及身份验证、秘密、PII或信任边界时。
- 性能/成本 - 当提案触及热路径、新增服务或涉及资源预算时。
- 可观测性 - 当提案引入新服务或值得告警的故障模式时。
根据代码扫描和提案表面判断相关性。不要条件反射般地就这些问题盘问用户。
目标质量检查
在访谈中对模糊的目标提出质疑。“让它更快”不是目标;“租户列表端点的p99响应时间低于200毫秒”才是目标。要求提供可衡量的版本。
阶段3:设计选项(本技能的核心)
这是RFC价值所在。提出2-3个具体、不同的设计方法,每个方法都作为一个可工作的API/代码片段展示,并扎根于阶段1的代码库惯例。不是散文描述:而是展示最终API、函数签名、模式或配置实际样子的代码。
对于每个选项,提供:
- 一个简短名称和一行摘要。
- 一个代码片段,展示最终API/用法/签名(未来工程师实际要编写的内容)。
- 其权衡:它擅长的方面、付出的代价、排除了哪些可能性。
并列展示它们,并让用户选择或细化。如果用户想要新选项或混合选项,则进行迭代。
选中的选项将成为“提议的设计”(附上其代码)。用户否决的选项将成为“考虑的备选方案”部分,每个选项保留其代码片段和被否定的原因。 这正是为什么这些RFC中的备选方案是真实的:它们是具有可工作代码的真正候选方案,而非回溯时编造的假想敌。
如果确实只存在一种可行的方法,就如实说,并单独展示它,同时明确说明备选方案已经考虑过,但没有一个可行。不要为了填满表格而编造虚假选项。
阶段4:组装
将完整的RFC写入输出文件(路径在阶段7中解析,并提前确认)。保持紧凑(参见“简洁无情”原则):使用项目符号而非段落,使用最简短的表述保持清晰,无填充内容。代码片段负责解释,散文仅用于框架说明。如果在阶段1检测到仓库的模板,则使用该模板;否则使用下面的内置结构。
当设计具有值得关注的结构(架构、流程、数据流、状态机)时,包含一个Mermaid图。用一个图表回答一个问题。对于纯API级别的变更且没有有趣流程的,则跳过。
内置模板
# RFC: <标题>
## 摘要
<读者能在30秒内了解要旨:提案的内容及其原因。>
## 背景介绍与动机
<问题、当前状态(如果在代码库中则包含文件引用),以及为何现在是行动时机。
如果我们什么都不做会发生什么。>
## 目标
<尽可能可衡量。使用项目符号。>
## 非目标
<明确不在范围内,以免审查者反复争论。>
## 提议的设计
<所选的方法。以阶段3中的可工作代码/API示例开头。
然后解释其工作原理,扎根于实际模块。如果需要,可包含图表。>
## 考虑的备选方案
<阶段3中每个被拒绝的选项,附上其代码片段和被否定的原因。>
## 风险与权衡
<每个风险配以缓解措施,或明确说明“已接受,因为...”。>
## 推出计划
<迁移、分阶段、向后兼容、测试策略。>
## 开放问题
<已知的未知事项。与其假装它们已解决,不如明确指出。>
## 批准人
<谁签字批准,谁受影响。列出姓名,以便明确决策权所在。>
仅在阶段2中相关时,才包含条件性章节(安全、性能、可观测性)。
可选元数据(仅当用户明确要求)
默认情况下不要添加前置元数据或编号。如果用户要求,则在其前面添加:
---
status: Draft # Draft | In Review | Accepted | Rejected | Superseded
author: <name>
date: <YYYY-MM-DD>
approvers: [ ]
---
仅当目标目录已包含NNNN-*.md文件时,才自动编号(NNNN-slug.md);扫描并找到最高编号然后递增。否则使用纯标识符文件名。
阶段5:自我批判(必须完成,在用户看到草稿之前)
将组装的RFC对照以下标准进行检查,并修复你可以修复的问题。然后报告你发现了什么以及哪些仍需要用户处理。
- 目标可衡量? 模糊的愿望已转化为可测试的内容。
- 备选方案真实? 至少有一个具有自己代码的真实备选方案,而非假想敌。(阶段3应保证这一点。)
- 每个风险都已缓解? 每个风险都有缓解措施或明确说明“已接受,因为...”。
- 设计具体? “提议的设计”包含实际的API/代码片段,而不仅仅是散文。
- 主张有依据? 关于代码库的每项陈述都有文件引用支持。没有无根据的断言。
简短报告,例如:自我批判:收紧了两个模糊目标;标记了一个风险(数据迁移停机时间)尚无缓解措施 - 需要你的决定。其他内容均已支持。
阶段6:审查循环
RFC已写入 <path>。在Markdown预览器中打开以查看渲染后的图表。需要任何更改吗?
通过编辑文件应用更改。循环直到批准。
阶段7:输出
路径解析(提前确认,在阶段4开始时)
- 具有检测到的RFC目录的仓库:
<that dir>/<NNNN->slug.md。 - 没有RFC目录的仓库:
docs/rfcs/<slug>.md。 - 不在仓库内:当前工作目录,或者如果当前工作目录不可写则使用暂存区。
一次性询问:我将把RFC写入 docs/rfcs/<slug>.md。需要不同的位置吗?
完成时
- 自动在新VS Code窗口中为用户打开:
code --new-window "<absolute-path>"。 - 在终端打印简短摘要(标题 + 一行概述 + 路径)。不要将完整文档打印到终端。
错误处理
- 无输入/只有主题 - 没问题,访谈会处理。直接开始阶段2。
- 引用的PR/Issue/工单无法读取 - 告知用户,根据用户描述继续。
- 代码库太大无法完全扫描 - 将扫描范围限制在提案涉及的子系统;说明你跳过了什么。
- 用户无法回答某个维度 - 将其记录为开放问题,而不是编造答案。