RFC 编写与维护
本技能负责编写和维护轻量级 RFC——编号的 Markdown 设计提案,与它们描述的代码一同存放在仓库中。RFC 在构建之前(或期间)捕获设计:包括问题、代码当前状态、已锁定的决策及其理由,以及明确不作为范围的内容。所有 RFC 通过单个 INDEX.md 文件进行跟踪,使整个设计历史可在一个表格中快速浏览。
此处的 RFC 是工作文档,而非官僚流程:它们确保决策不会因上下文丢失而消失,使重新拾起的设计仅需“一个小型 PR”即可完成,并防止被否决的替代方案被反复质疑。
何时使用此技能
- 用户要求编写 RFC、设计文档、设计提案、技术规范或架构提案
- 用户希望在实现之前记录或锁定设计决策——包括它击败的替代方案,或某个方法被否决的原因
- 用户要求更新 RFC(状态变更、执行注释、标记为已发布或已否决)
- 用户要求创建、索引或重新组织
rfcs/(或rfc/)目录 - 一个大型功能讨论需要被固化为一篇持久的文档
何时不要使用此技能
- 用户想要产品文档、README 或面向用户的文档(非设计提案)
- 用户希望在一个已有成熟 ADR 约定的仓库中创建 ADR——请遵循已有约定
- 变更过于微不足道,通过提交消息或 PR 描述即可承载
目录和索引
存放位置。 RFC 存放在仓库根目录下的单一扁平目录中:rfcs/(首选默认)或 rfc/。创建任何内容之前,请先查找是否存在具有这两个名称之一的目录,并遵循其约定。如果两者都不存在,则创建 rfcs/。
Gitignore 由用户决定,而非你。 某些项目会提交 RFC;其他项目则将其作为本地工作笔记而添加到 gitignore 中。除非明确要求,否则切勿为 RFC 目录添加或删除 .gitignore 条目。如果该目录被 gitignore,则 INDEX.md 头部应说明此情况(参见模板),以便读者知晓其为何不在仓库历史中。
INDEX.md 是整个集合的事实来源。 它承载了三个内容:
- 下一个可用编号,需显式声明——编号在并行创建时会发生冲突,因此索引会指明下一个编号,每次创建 RFC 时都会在同一个变更中更新它。
- 索引表格:
| # | 标题 | 状态 | 一句话描述 |,每行对应一个 RFC,编号链接到对应文件。 - 状态图例。
如果目录存在但有一个 README.md 充当索引角色,请将其视为索引。如果要求全新设置,则使用 INDEX.md——复制 references/index-template.md。
编号和文件名
- 编号为 4 位数字,零填充,单调递增:
0001、0002…… - 分配方法:从
INDEX.md中读取“下一个可用编号”,与目录的ls输出进行交叉检查(索引可能已过时),然后取下一个未使用的整数。 - 文件名:
NNNN-kebab-case-title.md。确保文件名中的编号与 H1 标题# RFC NNNN — 标题保持一致——否则两者会漂移,链接也会双向失效。 - 切勿对已有 RFC 重新编号。编号是标识符,而非需要整理的排序。
状态
- 📝 草稿 (Draft) — 已提案,但尚未开始(“设计已锁定、需求待确认”的 RFC 仍为 Draft)
- 🚧 进行中 (In progress) — 已部分发布
- ✅ 完成 (Complete) — 已完全发布
- ❌ 已否决 / 已撤回 (Rejected / withdrawn) — 保留文件;记录否决可防止反复质疑
状态存在于两个必须一致的位置:RFC 头部的 **状态:** 行以及索引表格的状态列。两者必须在同一个变更中更新。
RFC 结构
在编写新的 RFC 之前,请阅读 references/rfc-template.md 并以其为起点。其结构如下:
头部块(H1 标题下方的项目符号列表,位于任何节之前):
**状态:**— 表情符号 + 文字,可选附注(“可执行——一个 PR”、“设计已锁定,未安排”)**范围:**— 一个紧凑的段落:描述此 RFC 涵盖的内容以及明确不涵盖的内容。这是读者决定是否继续阅读的段落。**相关:**— 指向被涉及代码的链接(仓库内的相对路径)、其他 RFC、先例、外部参考**讨论:**(可选)— 设计正在进行或已进行辩论的链接(PR、问题、讨论串);持不同意见的读者可前往此处,而非分支该文档**来源:**(可选)— 设计从何处移植或概括而来(如有)
编号节。 对于一份完整的 RFC,包含以下部分:
- 摘要 — 几句话说明即将发布的内容
- 动机 — 问题及来自实际代码库的证据
- 当前状态 — 当前存在的状况,需与代码核实,而非凭记忆
- 目标 / 非目标 — 明确两方面
- 设计 — 核心;按工作流程或组件划分子节,包含实际的签名/模式/代码块(用于固定设计)。如果某个选择存在争议,保留被否决的替代方案及其原因——对于小问题,一句话即可;当该选择塑造了设计时,使用
### 考虑的替代方案子节 - 测试 — 设计如何被验证
- 文档 — 随设计一同交付的文档
- 不在范围内 — 明确列出每个排除项并给出理由:说明为何排除以及什么情况会改变这一决定
- 风险 — 诚实的失败模式,包括文档被误读的风险
- 未解决的问题 — 必须在设计被视为锁定前解决的内容,与实现可以自由决定的内容之间的区别;明确未知事项比在构建过程中静默解决更好
- 决策 — 一个编号的决策表格;这是使拾取设计变得便宜且无需重新争论的关键。如果某个决策以非显而易见的方式限制了未来,表格行中应说明——一个决策的后果是下一个决策的上下文
- 分阶段实施 — 哪些内容先落地,哪些内容依赖于其他内容
按 RFC 的重要性调整规模。 一个用于锁定简单设计的 RFC 只需头部块、设计、非目标和决策表格。不要将只有两页的 RFC 扩充到十二个节;也不要将一个系统范围的提案缩减到三个。无论使用哪个子集,请保持节编号的连续性。
写作风格
- 每个主张都要基于代码。 “当前状态”和“动机”需要引用文件、行级事实和可测量的数字——使用相对路径链接它们。凭记忆论证的 RFC 是带有标题的虚构作品。
- 记录决策及其原因和代价。 决策表格是契约;正文承载推理过程。被否决的替代方案应有一句话说明为何被否决(记录其权衡的替代方案不会被再次提出;仅记录为“被否决”的替代方案则会)。如果某个决策在将来封闭了可能性,应在表格行中说明。
- 及时优于精炼。 一个存在但粗糙的 RFC 胜过不存在但完美的 RFC(Oxide 的 RFD 规则:“及时而非精炼”)。草案的语言可能粗糙,但范围段落和决策表格则不应如此。
- 诚实面对限制。 如果一个机制被推迟、被门控或已知不完整,应在 RFC 中说明,而不是让读者自己发现。失败封闭的措辞(“拒绝”、“引发”、“故意不安排”)优于乐观的模糊表述。
- 精炼优于冗长。 优先使用一个承载关键信息的段落,而不是三个单薄的段落。索引中的一句话描述尤其如此:它必须足够自包含,以便读者可以跳过整个 RFC。
工作流程
簿记工作——编号分配、从模板创建文件、更新索引行和下一个可用编号、漂移检测——是机械性的,scripts/rfc_index.py 脚本可以无冲突地完成这些工作,避免了手动分配的问题:
python3 scripts/rfc_index.py check # 索引与文件对比、H1与文件名对比、状态一致性、下一个可用编号
python3 scripts/rfc_index.py next # 下一个可用编号
python3 scripts/rfc_index.py new "标题" # 分配编号 + 初始化模板 + 索引行 + 编号递增
python3 scripts/rfc_index.py new "标题" --number 42 # 分配保留编号,或重建已删除的 RFC
(路径相对于此技能的目录;从仓库根目录执行时,脚本位于 skills/rfc-writer/scripts/rfc_index.py。除 new 外均为只读操作;如果仓库不是当前工作目录,请添加 --root 目录 参数——位于子命令之前或之后均可。) 思考部分——设计内容、一句话描述的主张、状态变更时机——则由你负责。
A — 创建新的 RFC
- 找到 RFC 目录(
rfcs/或rfc/);如果不存在,请先执行工作流程 D。 - 分配下一个编号并初始化文件:
rfc_index.py new "标题"——它会生成编号、写入模板、添加索引行并更新下一个可用编号声明。步骤 3 和 4 仍由你负责:它留下的模板是未填充的,并在索引中写入字面上的TODO: 一句话摘要。手动操作:从索引读取下一个可用编号,并与ls交叉检查——编号在并行创建时会发生冲突。 - 按照
references/rfc-template.md的结构填充文件,并根据设计的重要性调整规模。在编写“当前状态”之前,请调查实际代码——这是大部分工作量所在。 - 将索引中的占位符一句话描述替换为紧凑、自包含的摘要。
B — 更新已有的 RFC
- 当工作部分或全部完成时,更新
**状态:**行——并附注说明已发布的内容和日期(例如,“Shipped 2026-06-29:…;仅剩 P5”)。 - 如果执行过程偏离了设计,请在 RFC 中记录偏差(状态注释或相关节中的修正)——不要静默地重写历史;决策日志应保持仅追加。
- 在索引表格中镜像状态(如果结构发生变化,则镜像一句话描述)。
- 被否决的设计保留 ❌ 并留在目录中。
C — 维护索引
运行 rfc_index.py check——它会报告每一条缺失索引行的文件(反之亦然)、H1 与文件名不匹配、头部与表格状态不一致、重复编号以及非空闲的下一个可用编号。修复其指出的问题(修复需要判断:哪个状态是正确的,一句话描述应该如何书写),然后重新运行直到全部通过。报告不同步的内容。
D — 初始化 RFC 目录
- 创建
rfcs/(除非用户想要rfc/或其中一个已存在)。 - 从
references/index-template.md创建INDEX.md,填写项目名称并将下一个可用编号设置为0001。 - 不要修改
.gitignore——说明提交还是忽略该目录由用户决定。
参考资料
references/rfc-template.md— RFC 骨架及每节指导;在编写任何新 RFC 之前阅读references/index-template.md— INDEX.md 骨架;初始化目录时使用
相关技能
altitude-docs— 设计发布后需要编写的面向用户的文档;RFC 的“文档”节指向它self-audit— 在合并前对执行了 RFC 的分支进行对抗性审查keep-a-changelog— 记录已发布的内容;RFC 记录为何以此方式构建