← 全部心得

我的 AI 原生开发工作流:规格 → 工单 → 实现

发布于 2026年9月28日

"我用 AI 写代码"和"AI 是我流程的一部分"是两件事。前者是把 AI 当更快的键盘,后者是重新分配人和机器各自擅长的事。

这篇文章用我刚上线的个人网站(就是你正在看的这个站)复盘我的完整工作流。它不是我的原创,而是把 Matt Pocock 的 agent skills(mattpocock-skills)链条装进了一个真实项目——每一环我都记录了它在本站的真实产出,全部可以在 git 历史与工单文件里查证。AI 原生工作流的主张应该可审计,而不是可向往。

先说为什么 agent 需要流程

和 agent 合作久了,三个特性会改变工程的经济学:

  1. 它会合理化,而且不会脸红。 人跳过测试时心里会咯噔一下,agent 没有。你说"这次先不写测试",它就真的不写,还写得理直气壮。
  2. 它的上下文是稀缺品。 一个会话塞不下一个项目,凡是"全靠记住"的东西,迟早丢。
  3. 它的产量近乎无限。 错误的产量也是。

三条加起来的结论:把决策前置,把状态外置。决策在上下文还干净时做完、落成文件;实现阶段的每一步从文件出发,而不是从记忆出发。下面的六个步骤就是这套思想的落地。

第一步:追问,而不是开工

grilling 这个 skill 的底层图景是一棵设计树:每个决策分叉出挂在它下面的决策。工作是按轮次推进的——每一轮只问"边界"上的问题:所有前置都已被回答、现在就能问的问题,一次性问完,并附上推荐答案;答案会重塑树形的问题,留到下一轮。

拿到"我要做一个个人网站"这种想法时,agent 最大的价值不是生成代码,而是逼我把模糊变清晰。我的第一轮边界问题大概长这样:

  • 首要受众是谁?(推荐答案:海外远程客户/雇主;与其他访客的需求冲突时,以首要受众为准)
  • 转化动作是什么?(发邮件或预约通话——不是涨粉)
  • 12 年企业经历里,哪些可以匿名公开、哪些碰都不能碰?

两轮问完,"个人网站"变成了有边界的东西:什么进 v1、什么明确不做。grilling 的结束条件很严格:边界为空——设计树的每根枝条都走过,没有任何东西被默默假设。没问到的地方,就是你将来返工的地方。

第二步:把语言钉死

追问的沉淀物不只是答案,还有词汇。domain-modeling 这个 skill 要求把每个敲定的术语当场写进根目录的 CONTEXT.md,并且在后续对话里主动挑衅混用:"你说的'账户'是 Customer 还是 User?这是两个东西。"

本站的词汇表里有一条我最得意:Case Study 和项目卡片是两个不同的东西。前者是对一个成果的深度叙事(背景 → 我的角色 → 技术决策 → 结果),说服潜在客户的核心内容;后者是公开 GitHub 作品的一句话陈列,密度高、深度低。混用一次,后面的规格、工单和代码就会跟着混——最后你会得到一个既不深也不密的页面。

词汇之外,还有难逆转的决策要落成 ADR(架构决策记录)。skill 给了三条硬标准,全中才值得写:难以逆转、缺少上下文会令人惊讶、是真实权衡的结果。本站 10 张工单、几十个决策,只有两条过线:

  • ADR-0001:v1 纯静态、无数据库——静态导出换来了"唯一测试接缝",第五步会用到它
  • ADR-0002:双语路由,默认英文在 /,完整中文版在 /zh

其余决策写下来只会稀释这两条。ADR 越少越锋利。

第三步:一页规格

to-spec 把对话收敛成一份规格,模板是固定的:问题陈述、解决方案、用户故事、实现决策、测试决策、明确不做的范围。两条铁律:

  • 不写文件路径和代码。它们烂得最快。规格的目的是让后续每一步可判定,不是穷举。
  • 测试接缝在这里约定,不等到写测试时才临时决定。skill 的原话:全库接缝越少越好,理想数是一。

本站的规格真的做到了理想值。Testing Decisions 一节写着:全项目唯一测试接缝 = next build 产出的静态渲染路由。一个内容站,测组件内部没有意义——组件坏了,路由断言自然会红。这个在规格阶段就和我确认过的决策,是第五步所有断言的地基。

一页规格足够。写规格的那一刻你在做设计;写到第三页,你已经在替 agent 做实现了。

第四步:tracer-bullet 工单

to-tickets 把规格拆成 9 张工单,每张是一发曳光弹:纵向切穿所有层(内容、路由、UI、测试),独立可演示,大小恰好塞进一个干净的上下文窗口——这是给"agent 上下文稀缺"特性做的针对性设计。每张工单声明它阻塞谁:

# 02: Blog Post 集合

**What to build:** 双语列表/详情路由;核心行为:
每种语言只列出该语言已有的文章。

**Blocked by:** 01(站点骨架)

- [ ] 仅中文的文章出现在 /zh/blog 且不出现在 /blog
- [ ] 构建后断言覆盖语言过滤行为

阻塞边让调度变成机械活:任何阻塞已全部完成的工单就是当前的"边界",拿起就做。"接下来干什么"这个问题,整个项目期间没有再出现过。骨架工单无阻塞先行,内容与页面工单并行推进,上线收口。

两个后来看很值的细节:

  • 语言过滤这条规则活到了今天。 断言没有硬编码"占位文章 A 出现/不出现",而是从内容目录动态推导——所以后来把占位文章换成真文(工单 08)、今天补英文版,规则依然被检验着。你此刻在英文版读到这篇文章,就是这条断言在放行。
  • 流程是活的。 上线后需要一轮视觉打磨,直接追加了第 10 张工单。没有"计划外"的恐慌,因为接缝和断言都在,任何改动被既有之网兜着。

to-tickets 还专门处理一种例外:宽改造——一个波及全库的机械变更(改列名、改共享符号类型)塞不进任何纵向切片,硬切会全库飘红。解法是 expand–contract:新旧并存,按爆炸半径分批迁移,最后删旧。本站没触发这个分支,但如果下一篇文的 sed 批量替换再大一档,就该走它了。

第五步:每张工单都是红→绿

implement 的规矩不多,但硬:

  • 红 before 绿:先写失败的断言,看着它红,再实现到绿。不预判未来的测试,不加投机功能。
  • 一个切片一个循环:一条接缝、一条断言、一段最小实现。
  • 重构不进循环。重构属于审查阶段(下一步),不属于红→绿实现循环——混进去,"绿"的含义就失真了:你不知道刚变绿的到底是新行为,还是顺手重构碰出来的。
  • 类型检查常跑,单测常跑,全量收尾跑一次,然后提交。

数字会说话:工单 02 完成时,断言从 13 项扩到 28 项;全站到今天积累 183 项,全部跑在 CI 里、全部住在同一条接缝上。唯一接缝的经济学:断言数量随内容线性增长,维护成本却恒定——因为它们只断言外部行为(路由存在、预期内容渲染、语言过滤成立),实现怎么翻新都不用改测试。

第六步:每个 diff 过两轴审查

code-review 把审查拆成两条刻意不合并的轴,各自一个隔离子代理:

  • Standards 轴:代码是否符合仓库自己的规矩——词汇表、ADR,外加一份固定的 Fowler 坏味道基线(神秘命名、重复代码、特性依恋、投机泛化……)。仓库的明文标准永远覆盖基线:仓库认可的,基线不置喙。
  • Spec 轴:是否忠实实现了工单——少做了什么、多做了什么(范围蔓延)、做错了什么。

为什么拆开?因为一个改动可以通过其中一轴而挂在另一轴:完全符合规范、却实现错了东西的代码,和分毫不差实现工单、却破坏项目约定的代码,是两种不同的病。合并成一份报告,好轴会掩盖坏轴。所以连"谁是最严重问题"都不跨轴评选。

本站的审查不是礼节,是抓虫主力。工单 02 的审查当场产出四项修复:frontmatter 缺 title/date 必须构建期报错(而不是渲染出静默坏页)、列表与详情的日期格式统一走本地化、断言改为从内容目录动态推导、补上 MDX 元素渲染检查。还有一条被 Spec 轴判为"可辩护的范围蔓延"——页头顺手加了导航链接——审查的裁决是保留而不是打回。这是判断力,不只是门禁。

(审查具体抓到过什么样的虫、为什么我说是"主力"?下一篇展开。)

最大的教训

AI 把"写代码"变便宜了,没有把"想清楚"变便宜。规格是贵的那部分,代码是便宜的那部分——把贵的部分做好,便宜的部分才会真的便宜。

九张初始工单、一张追加工单、183 项断言全绿。这条流水线本身就是本站的第三篇案例研究:每一步的产出——词汇表、ADR、规格、工单、断言脚本、审查记录——都在仓库里躺着,欢迎审计。