
快记快用
和 grill-me 同一套“拷问”流程,但面向代码库、有状态:边问边把术语写入 CONTEXT.md(上游 2026-08 已把 CONTEXT.md/CONTEXT-MAP.md/CONTEXT-FORMAT.md 改名为 GLOSSARY.md/GLOSSARY-MAP.md/GLOSSARY-FORMAT.md,本仓沿用旧名),把难逆转的决策写成 ADR。适合在仓库里、计划还模糊时用,是主流程的起点(接着走 to-spec)。输入 /grill-with-docs 即可。
grill-with-docs 技能的作用
grilling 解决的是 AI 没听懂就动手。grill-with-docs 解决的是下一步的问题:你和 AI 把设计谈明白了,共识却只存在于聊天窗口里,会话一关就没了。
没有它的时候,会反复出现下面这些事:
-
明天开一个新会话问 cancellation 这个词是什么意思,AI 又从头猜一遍,这一轮沟通没有结果。
-
几个会话各用各的词,account 这个业务单词在三个文件里指三种东西,没有机制逼它们统一。
-
上周讨论清楚为什么用 Postgres 数据库不用别的,下周的 AI 不知道,又和你讨论一遍。
-
AI 随口造的词没人纠正,慢慢被当成项目里真实存在的概念。
工作流中的其它访谈类技能只在会话里留下结论,而这个技能会把结论写到磁盘上。
使用方法和运行机制
输入
你的输入:一个还没想清楚的计划或设计 + 一个能安全写入文件的仓库 + 你每一轮的回答前置条件:
- 它要往仓库里写文件,所以你得待在一个写进去安全的地方。
- 它依赖另外两个技能。
SKILL.md只有一行委托:grilling提供访谈,domain-modeling提供写入。只装 grill-with-docs 会得到一个不能用的技能。 - 词表放在仓库根目录的
CONTEXT.md;如果根目录有CONTEXT-MAP.md,说明这是多上下文仓库,词表放在对应上下文的CONTEXT.md。决策放在docs/adr/。两处都按需创建,一开始什么都不存在,不需要提前搭目录。
处理
grilling(问) 把你的想法画成 design tree,每个决定分出挂在它下面的决定 算出 frontier:前置条件已经定下来、现在就能问的问题 一轮问完整个 frontier,每题编号并附上建议答案,然后等你回答 你答完一轮,重算 frontier,再问下一轮,直到 frontier 空掉 事实归 AI 查,决定归你定
domain-modeling(写) 对照词表挑刺,打磨含糊的词,用具体场景和代码给结论施压 术语一敲定,当场写进 CONTEXT.md 决策同时满足三个条件,才写成一篇 ADR输出
一场会话出来三样东西:
| 谈成了什么 | 写到哪里 |
|---|---|
| 一个术语:项目自己给某个东西起的词 | CONTEXT.md,当场写,一条一条写 |
| 一个难回退、离开上下文会让人意外、并且真的权衡过的决策 | docs/adr/ 下的一篇 ADR |
| 其他所有决定 | 只留在对话里,不写任何地方 |
第三行是故意的设计。CONTEXT.md 只是词表,ADR 的门槛是三个条件同时成立,所以大多数决定不够格,大多数会话一篇 ADR 都不出。一场会话换回一份更清晰的词表、零篇 ADR,是正常运转。这也意味着你谈成的多数内容只存在于当时那个 context window 里,所以这场会话应该直接交给 to-spec,而不是把它 clear 掉。
产物是仓库里的真实文件,它们可能在你以为该出现的时候没有出现,也可能因为不止一个人在写而各自漂移。
概念总结
grill-with-docs 围绕一个计划或设计对你做持续访谈,直到你和 AI 对这件事形成同一套理解,同时在过程中把术语和难回退的决策写进仓库。
它是一个 stateful 的技能,产物是仓库里的真实文件,而且只能由人触发,AI 不会自己调用。它自己的正文只有一行,把访谈交给 grilling、把写文件交给 domain-modeling,所以它是一层组合外壳,没有自己的行为。
在链条上它是头:setup-matt-pocock-skills → grill-with-docs → to-spec → to-tickets → implement(内含 tdd 与收尾的 code-review)。它产出的是共识和定稿的词汇,to-spec 拿着这些直接合成 spec,不再重新访谈你。
源码解读
技能本体是一行委托
技能本体的全部内容如下:
---name: grill-with-docsdescription: A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.disable-model-invocation: true---
Call the Skill tool twice, for "grilling" and "domain-modeling".译文
介绍里写着,这是一场不依不饶的访谈,用来把一个计划或设计磨得更利,过程中还会顺手产出文档(ADR 和词表)。正文是”调用 Skill 工具两次,一次 grilling,一次 domain-modeling”。disable-model-invocation: true 表示只有人输入命令才能触发它。
说明
这是最彻底的组合写法,正文里没有一条自己的指令,全部行为等于把另外两个技能加载进来。配套的 skills/engineering/grill-with-docs/agents/openai.yaml 里写着 allow_implicit_invocation: false,是同一件事在 Codex 那边的写法:AI 不会自己调用它。介绍页里还有一条没有结论的改名建议,改成 grill-domain-model 更能说明它的实际行为,目前没有动。
为什么这样写,有三个要点。
一是组合优于实现。问问题(grilling)和写文档(domain-modeling)是两件不同的事,各自已经有专属技能。这个技能只需要把两个凑到一起,不需要重写一遍。一个技能可以只是一行路由,壳里零逻辑,价值全在它把哪几个技能拼在一起。设计子技能之前先问一句:这是新行为,还是老行为的组合。
二是依赖要写成机制指令,不能写成散文。“调用 Skill 工具两次”是点名,不是“跑一场 grilling 会话”这种含糊描述。措辞一含糊,AI 可能只加载了 grilling、漏掉 domain-modeling,结果是访谈聊得很好,但一个字都没写下来,这是介绍页里报告最多的问题。仓库后来专门改过一次写法(commit 447ca70),把“要调两个技能”写死,不让 AI 猜。wayfinder 和 triage 里的对应位置用的是同一句写法。组合式技能唯一的故障点是依赖没加载,所以依赖必须点名。
三是只能人触发。它会往你的仓库里写文件,有副作用的动作必须由你明说,不能让 AI 在后台自己干。它只调 model-invoked 的依赖,依赖链因此不会失控。
grilling 提供访谈,本技能不自己提问
grilling 负责“问”,日常动作是:把你的想法画成一棵 design tree,每个决定分出挂在它下面的决定;算出 frontier,也就是前置条件已经定下来、现在就能问的问题;一轮问完整个 frontier,每题编号并附上建议答案,然后等你回答;你答完一轮,它重算 frontier,再问下一轮。查事实归 AI,做决定归你。会话的结束条件是 frontier 空掉,而且你亲口确认双方已经达成共识。
grilling 不给这个技能留任何文件,它的产出只有共识。grill-with-docs 借用的是它的问法,不改变它的规则,所以上一份深读(grill-me 篇)总结的 6 条原则在这里一条都不打折扣。凡是读代码就能回答的问题,它自己去查,不拿来问你,这条分工在这里同样成立。
domain-modeling 负责写的那半边
Actively build and sharpen the project’s domain model as you design. This is the active discipline: challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely reading
CONTEXT.mdfor vocabulary is not this skill: that’s a one-line habit any skill can do. This skill is for when you’re changing the model, not just consuming it.)
译文
在设计过程中主动构建并打磨项目的领域模型。这是一项主动的工作:挑战用词、构造边界情况、词汇和决策一敲定就当场写下来。(只是读 CONTEXT.md 查词不算这个技能,那是任何技能都能做的一行习惯。这个技能用在你要改模型的时候,不是只消费模型。)
说明
领域模型是用代码或图表,把一个业务领域里有哪些东西、它们有什么属性、有什么关系、能做什么描述出来。词表是它的文字部分,ADR 记的是它演变过程中的决定。这段话划出了一条边界:读词表谁都会做,改词表才是这个技能的职责,所以 grill-with-docs 调它的时候,目的是把会话里的结论变成模型的一部分,而不是查一遍现有词汇。
这个技能管“写”,日常动作有五个:
- 对照词表挑刺。你用的词和
CONTEXT.md里已有的说法冲突时,当场指出来:“Your glossary defines ‘cancellation’ as X, but you seem to mean Y. Which is it?” - 打磨含糊的词。你用了含糊或一词多义的词时,提出一个精确的规范词:“You’re saying ‘account’: do you mean the Customer or the User? Those are different things.”
- 用具体场景施压。讨论领域关系时,构造专门探测边界情况的具体场景,逼你把概念之间的边界说准。
- 和代码对质。你说了某个东西怎么工作,它去代码里核对,发现矛盾就摊出来:“Your code cancels entire Orders, but you just said partial cancellation is possible. Which is right?”
- 当场写进
CONTEXT.md。术语一敲定,就地更新,不许攒到最后批量补,格式按CONTEXT-FORMAT.md。
规则只有一条:CONTEXT.md 是词表,而且只能是词表。 不许放实现细节,不许放需求,不许当草稿本。实战里最常被违反的就是这一条,模型很容易把“写进 CONTEXT.md”理解成“把用户说的每个答案都存下来”,文件就会在无人察觉的情况下变成流水账。这条规则背后的原则是当场写优于事后补:敲定那一刻写下来的,是会话真正的产出;结束后批量补的,只是它的总结。
文件按需创建
Create files lazily: only when you have something to write.
译文
文件按需创建,有东西可写的时候才建。
说明
不预建任何空文件。CONTEXT.md 是第一个术语定下来时才创建的,docs/adr/ 是第一篇 ADR 需要时才创建的。没有文件,说明还没有定下来任何东西,这本身就是一个真实信号。
CONTEXT.md 的格式与四条规则
词表的格式长这样:
# {Context Name}
{One or two sentence description of what this context is and why it exists.}
## Language
**Order**:{A one or two sentence description of the term}_Avoid_: Purchase, transaction译文
文件顶部是上下文名称,加一两句话说明这个上下文是什么、为什么存在;然后在 ## Language 下面逐条列术语,每条是加粗的术语名、一到两句定义,再用 _Avoid_ 列出不要用的近义词。
说明
定义位置在模板里是占位符,实际写入时换成具体的句子,例如 Invoice 的定义是“A request for payment sent to a customer after delivery.”,Customer 的定义是“A person or organization that places orders.”。_Avoid_ 这一行的作用是把被否掉的叫法集中列在一处,看到它就知道这些词不要再用。
规则有四条:
- 要有主见。同一个概念存在多个词时,挑最好的那一个,其余全部列进
_Avoid_。 - 定义要短。最多一两句话,定义它是什么,不定义它做什么。
- 只收本项目特有的概念。通用编程概念(timeout、error type、utility pattern 之类)不属于词表,哪怕项目里大量用到;加一个词之前先问,它是这个上下文独有的概念,还是通用编程概念。
- 术语出现自然的聚类时,用子标题分组;全部术语属于同一个领域时,平铺列表也可以。
大多数仓库只有一个上下文:根目录一个 CONTEXT.md,决策放在 docs/adr/,例如 0001-event-sourced-orders.md、0002-postgres-for-write-model.md。
多上下文仓库在根目录放一个 CONTEXT-MAP.md,列出各个上下文的位置和它们的关系,例如 src/ordering/CONTEXT.md 与 src/billing/CONTEXT.md 各有自己的 docs/adr/,系统级决策放在根目录的 docs/adr/。判断规则是:有 CONTEXT-MAP.md 就按它找上下文;只有根目录一个 CONTEXT.md 就是单上下文;两者都没有,就在第一个术语定下来时按需创建根 CONTEXT.md。多上下文时如果判断不出当前话题属于哪个上下文,就问。
ADR 只在三个条件同时成立时写
Only offer to create an ADR when all three are true:
- Hard to reverse: the cost of changing your mind later is meaningful
- Surprising without context: a future reader will wonder “why did they do it this way?”
- The result of a real trade-off: there were genuine alternatives and you picked one for specific reasons
译文
只有下面三条同时成立,才提出写一篇 ADR。难回退:以后改主意的代价是真实存在的。离开上下文会让人意外:将来的读者会问“他们为什么这么做”。是真的权衡的结果:确实有别的选项,你出于具体理由选了这一个。
说明
三条缺一条就跳过。门槛写成三条同时成立,是因为 ADR 的成本不在写,而在以后有人要读它、维护它;门槛降下去,docs/adr/ 很快会变成没人读的目录。
三个条件的翻译:
| 条件 | 翻译 |
|---|---|
| 难回退 | 改了很难翻回去,比如选数据库、定架构 |
| 离开上下文会让人意外 | 一个新人看了会问“凭什么这么做” |
| 是真的权衡过 | 确实有别的选项,你因为具体理由选了这一个 |
不够格的不写:明天就能改的决定,记了也没用;没人会问为什么的决定,写了没人看;没有别的选项的决定,只是在记录你做了显而易见的事。所以大多数会话一篇 ADR 都不写,是正常运转。
这里的设计可以抽成一条原则:写入要有门槛,而且故意不对称。词表门槛低,定下来就写;ADR 门槛高,三个条件全占;其余决定故意不写。防的不是写太多,而是词表变成流水账。
什么算够格,格式文件里给了一串例子:
- 架构形态(monorepo;写模型 event-sourced,读模型投影到 Postgres);
- 上下文之间的集成方式(Ordering 和 Billing 通过 domain events 通信,不用同步 HTTP);
- 带锁定效应的技术选型(数据库、消息总线、认证服务、部署目标,不是每个库都算,只是换掉要花一个季度的那些);
- 边界与范围决定(Customer 数据归 Customer 上下文所有,其他上下文只按 ID 引用,明确的“不做”和“做”一样有价值);
- 对显而易见路径的刻意偏离(因为有某个具体原因改用手工 SQL 而不是 ORM,这类记录能拦住下一个工程师去“修好”一个本来刻意为之的设计);
- 代码里看不见的约束(因合规要求不能用 AWS;因为合作方 API 合同,响应时间必须低于 200ms);
- 拒绝掉的替代方案(如果排除 GraphQL 选 REST 的理由不显然,就记下来,否则半年后有人会再提一次)。
格式与编号:ADR 放在 docs/adr/,按顺序编号,形如 0001-slug.md、0002-slug.md,新建时扫描目录里现有的最大编号加一。模板是标题加 1 到 3 句话,讲清什么背景、决定什么、为什么;一段话写完的 ADR 完全正常,价值在于记录做过这个决定以及为什么,不在于把章节填满。只有真有价值时才加可选章节:Status frontmatter(proposed | accepted | deprecated | superseded by ADR-NNNN)、Considered Options、Consequences。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 43 天
部分内容可能已过时