grill-with-docs 与 Trellis 协同编码实践
副标题:从审问到可运行代码——以「API 速率限制中间件」为例的全流程新手实操(Claude Code + Codex 双平台)
你将完成什么
跟着这篇做一遍,你会得到一个 可运行的功能:一个挂在 /api/* 上的速率限制中间件,已认证用户按 user.id 限流、未认证按 IP,令牌桶算法,超额返回 429。更重要的是,你会完整走一遍两个工具怎么配合:
- 用 grill-with-docs 在动手前把”按什么限、用什么算法、计数器存哪、超额返回什么”一个个审清楚,审的同时自动产出三份文档。
- 用 Trellis 把这三份文档在代码生成的那一刻交给写代码的 agent,并用质量门把守”没过 lint/type/test 不准交”。
两个平台都覆盖:Claude Code 全程自动化最完整;Codex 同样能走完全程,只是其中两步(规范注入、质量门)从”自动”变成”手动”。文章对每一步都给出两个平台的做法。
命令记法:凡是 Trellis 命令,/xxx 表示 Claude Code,$xxx 表示 Codex(如 /start ↔ $start),两平台命令一一对应。真正有差异的只有 Step 4(规范注入) 和 Step 5(质量门),那两步会单独分栏说明。
前提 :一台装了 Claude Code 或 Codex CLI 的机器,一个已有的小后端项目(Node/Express 之类,有几个接口)。还没有的话,照 附录 A花一分钟搭一个最小的。
文中的对话样例和 agent 输出是为讲解而构造的示意(不是某次真实运行的逐字记录),但每一步的命令、文件内容、最终代码都是可以直接照抄运行的。
1. 两个工具分别干什么(30 秒)
| grill-with-docs | Trellis | |
|---|---|---|
| 干什么 | 动手前 逐题审问 需求,边审边产出术语表和决策文档 | 把项目规范在代码生成的那一刻交给写代码的 agent,并自动把关质量 |
| 产物 | CONTEXT.md(术语表)、docs/adr/(架构决策)、编码约定 |
代码 + 通过 lint/type/test 的质量门 |
| 解决的痛点 | “想清楚再写” | “写下来的标准,AI 真的会读” |
它们天然互补:grill-with-docs 出料 (决策和文档),Trellis 送料(把文档送到代码生成的那一刻)。单独用 grill-with-docs,审出来的决策写完就躺在仓库里,下次写代码没人保证它被读;单独用 Trellis,注入机制很强,但 .trellis/spec/ 在 trellis init 后是空的,送的是空气。
先把 grill-with-docs 的全部内容贴出来——它只有五行:
1 | --- |
一个壳,真正干活的是另外两个 skill:grilling(审问)和 domain-modeling(写文档)。所以装的时候要三个文件一起装,内容见 附录 B。
平台能力对照(来自 Trellis 官方文档第 13 章):
| 能力 | Claude Code | Codex | 差异说明 |
|---|---|---|---|
| 会话上下文注入 | ✅ 自动 | ✅ 自动 | 都有 session-start.py,开会话即注入身份 / 历史 / 任务 |
| 把 spec 注入写代码的 agent | ✅ 自动 | ❌ 手动 | Codex 无 PreToolUse Hook,Step 4 改手动点名 |
| 质量门(lint/type/test 不过打回) | ✅ 自动 | ❌ 手动 | Codex 无 Ralph Loop,Step 5 改手动跑验证 |
| 多 agent 自动流水线 | ✅ 自动 | ⚡ 半自动 | Codex 有 .codex/agents/*.toml,但需手动串步骤 |
| 命令前缀 | / |
$ |
/start ↔ $start |
| Skill 目录 | .claude/skills/ |
.agents/skills/ |
初始化时分别生成 |
| 命令数量 | 13 | 13 | 一一对应 |
一句话:两个平台都能跑完全程,区别只在 Step 4、Step 5 谁来按”读规范”和”跑测试”这两个按钮——Claude Code 是机器按,Codex 是你按。
2. 环境准备(Step 0)
装 Trellis 并初始化。按你的平台二选一:
1 | npm install -g @mindfoldhq/trellis@latest |
-u 你的名字 会成为你的开发者身份,并创建个人工作区 .trellis/workspace/ 你的名字 /。两边都会生成 .trellis/(spec / workspace / tasks / scripts)。区别在配置目录:
| 平台 | 生成的配置 | 关键文件 |
|---|---|---|
| Claude Code | .claude/ |
commands/(13 个命令)、agents/(6 个 agent)、hooks/(3 个 hook) |
| Codex | AGENTS.md + .agents/ + .codex/ |
.agents/skills/、.codex/agents/*.toml、.codex/hooks/session-start.py |
验证装好了——同样按平台:
1 | # Claude Code:检查三个 hook(这是它的发动机) |
Claude Code 那三个 hook 各管一段:
session-start.py—— 每次开会话自动注入上下文(你是谁、上次干到哪、有哪些任务)。inject-subagent-context.py—— 主 agent 调子 agent 时拦截,按 JSONL 把 spec 塞进子 agent 的 prompt。Codex 没有它,所以 Step 4 要手动替代。ralph-loop.py—— check agent 说”做完了”时拦截,跑验证命令,没过就打回去修,最多五轮。Codex 也没有它,所以 Step 5 要手动替代。
Codex 有自己的 session-start.py(会话上下文照常自动注入),所以开会话、接上历史这两件事,两边体验一致。
可选:跑一次 /start(Codex 是 $start),AI 会读 workflow、读 spec 索引、报上下文,然后问你 “What would you like to work on?”。两边因都有 session hook,这条命令其实可选,但第一次建议跑一遍看清流程。
3. 装上 grill-with-docs(Step 1)
grill-with-docs 是个壳,需要连 grilling 和 domain-modeling 三个 skill 文件一起放进 你平台的 skill 目录 。完整内容在 附录 B,目录结构:
1 | # Claude Code |
把附录 B 的三段内容分别存成这三个文件即可。
disable-model-invocation: true 的意思是它不会自己跳出来——你必须主动触发。触发方式(两平台都适用):
- Claude Code:直接打
/grill-with-docs; - Codex:直接用自然语言触发——“用 grill-with-docs 审一下:给现有 API 加速率限制”。
官方捷径(两平台都有):用 Trellis 自带的
/integrate-skill(Codex 是$integrate-skill)一键集成,它会自动在spec/guides/写集成文档、生成包装命令,给你统一入口/use-grill-with-docs(Codex 是$use-grill-with-docs)。手动建文件的好处是你清楚里面是什么,下面的步骤就照手动的来。
4. 用 grill-with-docs 审问需求(Step 2)——核心
进你的 AI 编程工具,触发 grill-with-docs,把需求丢给它:
1 | 你:用 grill-with-docs 审一下:给现有 API 加速率限制。 |
这一步 两个平台完全一样——审问靠的是 skill 里的文字规则,Claude Code 和 Codex 读到的指令相同,行为一致。
grilling 的四条规则决定了接下来这场对话的形状:
- 沿决策树 一题一题问,不一次抛一堆;
- 每题 先给推荐答案,你来确认或推翻;
- 能用环境查到的事实别问人——交给 research 子 agent 去翻代码;
- 达成共识前不动手写代码。
下面是一场完整审问的样子(示意)。AI 一次只问一题,你回答后它才问下一题;同时 domain-modeling 在后台把结论写进文件。
Q1:限流的维度——按什么限?
AI(推荐):已认证请求按
req.user.id,未认证按req.ip。理由:按用户更精准,IP 兜底匿名流量。
你:同意。
Q2:用什么限流算法?
AI(推荐):令牌桶(Token Bucket)。允许短时突发(桶里有令牌就能消费),符合人类交互节奏。备选固定窗口 / 滑动窗口不允许突发。
你:同意,令牌桶。
→ domain-modeling 把 Token Bucket / Sliding Window / Burst 三个术语写进CONTEXT.md。
Q3:计数器(令牌桶)存哪? ← 这题值得 ADR
AI(先调 research 子 agent 查事实):查了一下,当前是单实例部署,没有 Redis。基于这个事实,推荐 进程内 Map,但把存储抽象成可替换接口(
get/set),以后迁 Redis 只换实现。
AI(再问决策):你接受”现在用进程内 Map、留好 Redis 迁移口子”吗?注意多实例部署会失效。
你:接受,单实例够用。
→ 这条同时满足 ADR 三条件(难推翻 + 没上下文会困惑 + 真权衡),domain-modeling 写docs/adr/0001-in-memory-rate-limiter-counter-store.md。
Q4:窗口大小和配额?
AI(推荐):容量 60、每秒补 1 个令牌(即每分钟 60 次上限,允许一次性突发 60 次)。
你:同意。
→ 写进.trellis/spec/backend/rate-limiting.md。
Q5:超额返回什么?
AI(推荐):状态码
429,响应体{ "error": "rate_limit_exceeded", "retryAfter": < 秒 > },headers 带Retry-After+X-RateLimit-Limit+X-RateLimit-Remaining。
你:同意。
→ 写进.trellis/spec/backend/rate-limiting.md。
Q6:中间件挂在哪?哪些路由豁免?
AI(查事实后推荐):挂在
/api子路由上;/health直接挂在 app 上、不经过/api,天然豁免。豁免在路由层声明,不在中间件内部硬编码。
你:同意。
Q7:怎么测?
AI(推荐):单元测试令牌桶算法;集成测试覆盖”连续 60 次后第 61 次 429 带 Retry-After”和”健康检查连续请求不限”。
你:同意。
审完,domain-modeling 已经产出三份文件。下面是它们的完整内容,你可以直接核对 / 照抄。
4.1 CONTEXT.md(术语表——只有术语,零实现细节)
1 | # Domain Glossary |
注意:这里 没有 任何”用 Map 存储””返回 429”——那些是实现规约,归 spec。domain-modeling 的纪律就是 CONTEXT.md 只装业务词汇。
4.2 docs/adr/0001-in-memory-rate-limiter-counter-store.md
1 | # ADR-0001: 速率限制计数器使用进程内存储 |
4.3 .trellis/spec/backend/rate-limiting.md(编码规范——这才是会送到代码生成那一刻的东西)
1 | # 速率限制规范 |
审问到这一步,”想清楚”的部分已经完成,而且每一份结论都落在了 它该去的地方:术语进 glossary,架构决策进 ADR,编码约定进 spec。
5. 把审问结果变成 Trellis 任务(Step 3)
现在让 Trellis 把这个需求变成一个 任务 ,并告诉它”写代码时读哪些文件”。这一步 两平台产物相同(任务目录结构一模一样)。
最省事的方式是直接对 AI 说:”按这个需求创建任务并开发”。AI 会走 Trellis 的 Task Workflow:调 research agent 扫代码 → 建任务目录 → 写 task.json / prd.md / implement.jsonl 等。但我们想确保 grill 产出的三份文件被挂进去,所以这里看清楚 implement.jsonl。
任务目录长这样(名字是 月 - 日 - 任务名):
1 | .trellis/tasks/08-10-rate-limiting/ |
5.1 prd.md
1 | # Rate Limiting |
5.2 info.md
1 | # 技术设计 — Rate Limiting |
5.3 implement.jsonl(最关键——它决定 implement agent 读到什么)
1 | {"file": ".trellis/workflow.md", "reason": "Project workflow and conventions"} |
注意第 2、3、5 行——这正是 grill 产出的三份文件。这一步是两个工具真正接上的地方:
- Claude Code:只要它们出现在
implement.jsonl里,inject-subagent-context.py会在 implement agent 跑的那一刻把它们拼进 prompt——自动、无感。 - Codex:没有那个 Hook,
implement.jsonl不会被自动拼进 prompt。但它的价值不变:它是 implement agent 的 必读清单 。在 Step 4,你要照着这份清单 手动点名 让 Codex 读这三份文件。
5.4 check.jsonl
1 | {"file": ".trellis/spec/backend/rate-limiting.md", "reason": " 核对算法、限流维度、429 响应格式、豁免 "} |
激活任务(设置 .trellis/.current-task 指针,两平台通用):
1 | python .trellis/scripts/task.py start .trellis/tasks/08-10-rate-limiting |
6. 让 Trellis 写代码(Step 4)
这一步两个平台 做法不同,产物相同——最终代码一字不差,区别只在”那三份文档怎么进到 agent 眼前”。
Claude Code:自动注入
对 AI 说:”开始开发这个任务”。Trellis 调 implement agent,inject-subagent-context.py 拦截这次调用,按 implement.jsonl 拼出新 prompt。implement agent 实际收到的开头长这样:
1 | # Implement Agent Task |
Codex:手动注入(替代方案)
Codex 没有自动注入的 Hook,所以这步你手动保证三份文档被读到。两种等价做法,选其一:
做法 A(推荐,最省心):先把规范读进上下文,再下任务。
1
$before-backend-dev # 读取后端 spec 索引 + 相关规范
然后 点名 补上 before-dev 默认不读的两份文件,再下任务:
1
2
3你:实现前先读 CONTEXT.md、docs/adr/0001-in-memory-rate-limiter-counter-store.md、
.trellis/spec/backend/rate-limiting.md,然后按 .trellis/tasks/08-10-rate-limiting/
开发 /api 速率限制。做法 B(一劳永逸):把”必读清单”写进 Codex 的 implement agent 配置
.codex/agents/implement.toml,让每次实现都默认读CONTEXT.md+docs/adr/+ 任务 spec。配一次,以后免点名。
implement.jsonl 在 Codex 上的角色就是这份”必读清单”——你照着它点名,效果和 Claude Code 自动注入一致。
两平台共同的产物:代码
implement agent 写出来的代码(完整、可运行):
src/middleware/rateLimit.js
1 | class TokenBucket { |
src/app.js
1 | const express = require('express'); |
注意代码和文档的对应关系:X-RateLimit-Remaining、429 响应体、可替换的 keyFn、导出的 _buckets——全是 spec 和 ADR 里定的。不是 agent 随机发挥,是它 读到了 那三份文件(Claude Code 靠 Hook 自动读,Codex 靠你点名读)。
7. 让质量门把关(Step 5)
implement agent 报”完成”后,要验证代码确实满足 spec、且 lint/type/test 全绿。这一步两平台 机制不同。
先配验证命令(两平台都写这份文件):
.trellis/worktree.yaml
1 | verify: |
Claude Code:Ralph Loop 自动把关
Trellis 自动调 check agent,ralph-loop.py 在 check agent 想停的时候拦截,按 worktree.yaml 的 verify 跑命令。假设第一次 check 跑挂了——比如 implement agent 忘了把 /health 挂在 /api 外面,导致集成测试里健康检查也被限流,测试红:
1 | check agent: 我做完了 |
如果没配 verify,Ralph Loop 会退化成 Mode B(completion markers)——从 check.jsonl 的 reason 字段生成标记,要求 check agent 输出里包含这些标记才能停。程序化验证更可靠,优先用上面的 verify。
Codex:手动把关(替代方案)
Codex 没有 Ralph Loop,”lint/type/test 不过就自动打回”的环不存在。手动等价做法分两步:
- 核对 spec:跑
$check-backend,它重读rate-limiting.md、逐条对比代码、自动修违规(这步两边都有)。 - 跑验证命令:自己执行
npm run lint && npm run typecheck && npm test;红了就把报错贴回去,再$check-backend,直到全绿。
worktree.yaml 里的 verify 在 Codex 上不会被 Hook 自动跑,但它是你 手动的检查清单——照着跑就行。手动多按两下按钮,结果和 Ralph Loop 一样:代码得过这三关才算完。
两平台共同的产物:测试
集成测试(正是它抓住了上面那个错):
test/rateLimit.test.js
1 | const request = require('supertest'); |
8. 收尾:完工检查 + 记录会话(Step 6)
跑 /finish-work(Codex 是 $finish-work),Trellis 给一张六维清单:代码质量(lint/type/test)、文档同步、API 变更、数据库变更、跨层验证、手动测试。你按清单跑一遍,手动测一下:
1 | curl -i http://localhost:3000/health # 200,无限流头 |
确认没问题,你(不是 AI)来提交:
1 | git add src/middleware/rateLimit.js src/app.js test/rateLimit.test.js \ |
记录会话(让 AI 记住这次做了什么):
1 | /record-session # Claude Code |
它调 add-session.py,把这次工作追加到 .trellis/workspace/ 你的名字 /journal-N.md。
第二天 回来,开新会话。两平台的 session-start.py 都会读 journal,AI 接上:”昨天你完成了速率限制(commit abc1234),令牌桶、进程内存储、429 响应。今天想做什么?”——决策跨会话存活了。
9. 固化(Step 7):让这套流程成为默认
跑通一遍后,把它变成项目默认,下次不用手动触发:
- ** 替换
/brainstorm**(Codex 是$brainstorm):Trellis 自带的 brainstorm 命令干规划阶段的活。把它的内容改成”调用 grill-with-docs”,于是每个新任务进入规划阶段自动走逐题审问。grill-with-docs 比 brainstorm 多了”逐题、给推荐答案、只问决策、边问边记文档”四条硬规则,是它的加强版。 - 把 glossary / ADR 挂进默认模板:在
backend/index.md或任务 JSONL 模板里默认带上CONTEXT.md和docs/adr/的引用,省得每个任务手写implement.jsonl时忘加。Codex 用户更省事——直接写进.codex/agents/implement.toml,一次配好永久生效(见 Step 4 做法 B)。 - 给 grill 定门槛:不是每个任务都值得完整审问。用 ADR 三条件当闸门——改个按钮颜色跳过,搭鉴权 / 限流这种才跑完整 grill。
10. 新手踩坑清单
- 在 Codex 上期待全自动:以为命令一敲,spec 自动注入、测试自动把关。→ Codex 缺 PreToolUse 注入 Hook 和 Ralph Loop,Step 4 必须手动点名读文档、Step 5 必须手动跑验证。要全自动,用 Claude Code;用 Codex 就接受这两步手动。
- 命令前缀用错:在 Codex 上打
/start没反应。→ Codex 用$,Claude Code 用/。记法:/xxx↔$xxx。 - grill 一次问一堆:AI 把五个问题一次抛出来让你懵。→ 违反 grilling 规则,让它”一次一题、每题先给推荐答案”。两平台都适用。
- CONTEXT.md 混进实现细节:审到”用参数化查询”顺手写进术语表。→ 那是 spec 的活,拉回来;glossary 只装业务词汇。
- 三份文档没进 implement agent 的视线:审得很彻底,但写出来的代码完全没遵守。→ Claude Code 上多半是 CONTEXT.md / ADR / spec 没写进
implement.jsonl;Codex 上多半是 Step 4 忘了$before-backend-dev+ 点名。注入不到 / 没读到,决策白审。这是最常见的失败。 - 什么任务都全量 grill:小改动也走七题审问,团队开始绕过它。→ 用 ADR 三条件(难推翻 + 没上下文会困惑 + 真权衡)当门槛,只有值得的才全量审。
- Codex 上忘了跑验证:以为
$check-backend跑完就万事大吉。→ 它只核对 spec,不替你跑 lint/type/test。那三条命令(或worktree.yaml的 verify)你得自己按,红了再修。
11. 换成你的真实需求:值不值得走完整 grill?
你刚拿速率限制走了一遍完整流程。但不是每个需求都该这么干——grill 的真实成本是 人得在线一题一题答 。所以判断的核心不是”这功能重不重要”,而是” 提前问清楚能省下的返工,值不值得你花这 20 分钟在线“。
返工成本高(决策难推翻、跨层、领域有歧义)→ 值得。返工成本低(单文件、可逆、无歧义)→ 不值得。
四个问题自测(每个”是”计 1 分):
- 可逆性:做错了,改回来的代价大吗?(比如已对外、客户端已依赖响应格式)
- 意外性:半年后回来看这段代码,会问”为什么这么做”吗?
- 歧义性:需求里有”一个词,团队三个人三个意思”的术语吗?
- 跨层性:它会同时碰前端 / 后端 / 数据库 / 外部系统里的两个或以上吗?
按分数分三档:
| 分数 | 档位 | 怎么做 | 在 Trellis 里落法 |
|---|---|---|---|
| ≥3 | 全量 grill | 完整逐题审问,写 glossary + ADR + spec | CONTEXT.md + docs/adr/ + spec,全挂进 implement.jsonl |
| 1–2 | 轻量 grill | 只审 2–3 个关键决策,写 spec 条目,不写 ADR | 只更新相关 spec 文件,任务照常建 |
| 0 | 跳过 | 不 grill,直接描述需求 | 直接走 Task Workflow,让 Plan agent 评估(需求不清它会拒绝并让你澄清) |
ADR 三条件(难推翻 + 没上下文会困惑 + 真权衡)就是上面第 1、2 问再加一条”有没有真备选”——分数高且满足三条件的决策才值得写 ADR。
常见需求归档(拿来对照):
- 全量:支付回调、权限模型、订单 / 工单状态机、限流 / 熔断、多租户隔离、搜索 / 排序 / 分页口径、缓存策略、批量导出。共同点:决策多、难推翻、跨层、术语有歧义。
- 轻量:新增一个 CRUD 资源(表结构已清楚)、给接口加筛选参数、加日志埋点、给一个查询加缓存。共同点:1–2 个值得记的点,但领域清楚。
- 跳过:改文案 / 样式 / 常量、重命名、加可选字段、调阈值、修明显 bug。共同点:可逆、单文件、无歧义。
两个最容易踩的误判:
- “这是新功能” ≠ 要全量 grill。 一个全新的 CRUD 资源,字段和表结构都清楚、领域无歧义,就是轻量甚至跳过。新不等于难。
- “代码量小” ≠ 该跳过。 一个几行的改动只要碰了状态机边界或鉴权逻辑(比如”取消订单”到底允不允许部分取消),就该全量。判断看 决策密度,不看代码行数。
一句话记住:grill 治的是”返工”,不是”工作量”。
附录 A:还没有后端项目?真能跑通的最小 scaffold
建一个目录,放四个文件,npm install 后 npm test 直接通过。下面这版我在 Windows + Node 24 上实测过:npm test → 2 passed,npm run lint / npm run typecheck 都返回 0,npm start 起服务后 curl /health 拿到 200。两平台通用(scaffold 本身和 AI 工具无关)。
package.json
1 | { |
lint / typecheck 是 占位脚本,永远返回 0——它们存在的唯一目的是让 Step 5 里质量门的 npm run lint && npm run typecheck && npm test 链子从第一天就能端到端跑通(Codex 上是你手动跑这三条,Claude Code 上是 Ralph Loop 自动跑)。等你接了真实的 eslint / tsc,把这两行换掉即可。
src/app.js
1 | const express = require('express'); |
**src/server.js**(让 npm start 能起服务,供 Step 6 的 curl 验证)
1 | const app = require('./app'); |
test/app.test.js
1 | const request = require('supertest'); |
装依赖并验证:
1 | npm install |
三个脚本都过、服务能起,说明工程骨架是好的。然后初始化 Trellis(按平台二选一):
1 | trellis init -u 你的名字 # Claude Code |
再从 Step 1(装 grill-with-docs)开始。
说明:这里的
src/app.js是 初始状态 (扁平路由、无限流)。Step 4 的 implement agent 会把它 重构成/api子路由并挂上限流中间件,同时新建src/middleware/rateLimit.js——所以你在 Step 4 看到的src/app.js是重构后的样子,不是从零写的。
附录 B:三个 skill 文件完整内容
两个平台用的是 同一份内容,只是存放位置不同:Claude Code 放 .claude/skills/,Codex 放 .agents/skills/。
grill-with-docs/SKILL.md
1 | --- |
grilling/SKILL.md
1 | --- |
domain-modeling/SKILL.md
1 | --- |