概述
最近一直在思考如何解决 AI 软件开发过程中遇到的核心问题:
- 如何确保需求自始至终都是唯一真相源, 而非将真相编码到代码中.
- 生成代码的速度越来越快, 验证质量跟不上. 结果就是 bug 陡增, 迭代开发越来越慢, 甚至到了改不动的程度.
- 开发过程中的 AI token 开销成本高, 这个不能单纯从“换便宜模型”这个角度去解决.
之前在单独使用 Matt skills, 但它只能解决部分问题. 因此需要引入更合理的工具和方法.
唯一核心问题 - 成本
上面三个问题归根结底是成本问题: 即时间成本和 token 开销.
仔细思考一下如下三个情况:
- 如果 AI 模型成本足够低, 大家可以完全不写文档, 每次任务都让若干 worker 去读代码明确现状并继续开发. 但现实是强模型的成本非常高, 在做的过程中完全使用强模型的结果往往是月度额度几天就耗光, 且时间成本会非常高.
- 如果 AI 模型成本足够低, 验证质量低也没关系, 指派多个并发去解决同一个问题, 最终肯定会更高概率拿到成功结果. 但现实仍然是额度快速耗光.
- 如果 AI 模型成本足够低, token 开销低, 自然也就不用换便宜模型, 全程任何任务都用最强模型, 也不用担心钱花得少了. 但现实并非如此.
举个例子: 新会话或上下文压缩后(特别是长时间不断迭代开发过程中), AI 就得重新去读很多相关的代码才能获得所需要的信息后, 才能接着继续进行任务. 这个过程就是在找到没有明确记录的现状细节. 并且 AI 会从现有代码和测试里推断现状, 即便现有生产代码或测试代码本身是错误的, 它也只会忠实于现有代码. 代码只记录了怎么做, 并不能说明为什么这么做, 也分不清哪些是正确的, 而哪些是实现时 AI 擅自扩大范围自己加上的. 造成的结果就是越往后 AI 使用成本越高, 且越来越慢, 因为要读的东西越来越多.
总结就是问题积累起来, 项目就会慢慢变得越来越不可维护, 甚至到了无法维护的程度. 进而导致成本飙升. 而下一轮修改又会把它们当成既有需求, 继续往上加, 整个代码库就腐烂了.
这种情况网上的说法是: “代码熵增”. 它不是一个可以测量的指标, 但只要是用过 AI 进行大项目开发的人, 应该或多或少都能感觉得出来.
而我下面要介绍的, 就是解决问题的一个实测可行方法.
尝试解决 - Spec-Driven
OpenSpec 是一个开源的 Spec-Driven 工具. 它的强项是记录和持久化真相源/需求, 结合 Matt skills 即可形成一套从需求到实现的完整流程.
OpenSpec 主要用来保存如下四类信息:
- proposal: 为什么改, 改什么, 哪些不做
- spec: 用户能够观察到的行为, 用需求和 WHEN/THEN 场景描述
- design: 实现选择和取舍
- tasks: 横向的实现任务拆解并记录完成情况
下面来讲讲整体使用过程.
需求先行
很多开发人员所犯的最大的问题, 就是在还没有摸清楚需求情况下就直接写代码, 而代码只是需求的呈现形式. 绝大部分软件开发问题实际上都是需求问题: 要么是需求理解错了, 要么是需求拆分错了.
需求讨论和拆解
拿到一个模糊的需求后, 必须要对需求进行理解和明确. 这个时候 AI 是非常好的工具, 可以和它一起讨论需求, 并进行大范围的调研.
在讨论需求时我会使用 Matt skills 的 grilling. 它可以按思维树的方式把问题和人讨论清楚. 当然在讨论的过程中很多细节问题还是要依靠人的经验, 它不会把所有待考虑问题都列出来. 所以在做的时候一定要仔细思考问题并把可能的细节都想到, 但不要把范围扩大.
需求记录
当 grilling 完成后, 下一步就是把需求记录到正式的文档中. 这个时候就需要使用到 OpenSpec. 讨论结果通过 OpenSpec 写进文档, 确认后成为实现依据.
这就解决了让需求文档成为唯一真相源的问题: 现在可以在文档中明确看到哪些行为是需求, 哪些保证明确不做等等的信息. 而非把需求看似合理地“编码到代码中”, 因为代码可能出错或扩大范围.
需求到实现的桥梁: 将 task 转换为开发 issue
OpenSpec 的 task 拆解是根据需求进行横向拆解的, 即按功能分解到不同的 task. task 要进行实现的话, 可能的问题是一个 task 的规模会比较大, 或多个 task 有相互依赖关系.
这在实现时并不合理, 因此还需要用到 MATT skills 进行 task 到 tickets 的整体映射, 这样可以完整定义出 task 的拆分, 以及 task 之间的依赖关系, 并可以保证每一个待实现的 issue 都是合理的粒度. 做这一步就相当于是产品经理把需求交给开发主管, 由开发主管对所需要执行的工作进行二次分析, 并形成开发任务.
为保证从 OpenSpec 的 task 体系向 Matt skills 的 tickets 体系的转换, 我写了一个 tasks-to-tickets skill, 用 Matt to-tickets 的纵向切片思路, 把这些任务组织成可以单独实现和验收的 issue.
需要注意的一点是: 一个 issue 可能对应多个 task, 一个 task 也可能被拆成多个 issue.
实现
实现使用的 implement-openspec-ticket 也是参考原版 implement 技能生成的一个桥接技能.
需要注意: 开始处理 issue 前, 先让 Agent 说明本轮做什么, 不做什么, 如何验证结果, 需要哪些依赖. 人批准后才开始实现.
代码 review
我写了一个 review-openspec-ticket 负责进行 review, 它只读不改代码, 也不关闭 issue. review 结果是两份报告(和 Matt skills 原版的 code-review 技能类似):
- Standards: 对照仓库工程规则检查代码和测试.
- Spec: 对照已批准需求检查行为.
和流程并行: 规则和工具
上面使用 OpenSpec + Matt skills 确定了从需求到实现的整体流程, 为保证质量, 还需要引入一些规则和工具.
工具: 我使用 Oh My Pi, 整个流程中的需求讨论和 reviewer 都设置为强模型(比如 gpt6 sol), 实现则让强模型作为编排器, 调度性价比模型(如 DeepSeek)作为 worker 进行开发.
并且通过规则来贯通整个过程: 这些规则约束是为了生成高质量代码, 以及约束需求在实现时不跑偏的.
- 事前约束: 在 AGENTS.md 中添加语义化规则约束, 其中规则作用在整个需求到开发过程.
- 事中约束: 在 implement-openspec-ticket 技能中添加, 主要约束开发过程. 详见仓库
- 事后约束: 在 review-openspec-ticket 技能中添加, 主要保证开发质量. 详见仓库
需要注意的是这些约束只是语义化的, 是否遵循完全看模型概率. 而还有一类强约束是可执行可验证的, 强约束会在之后讲到.
在 AGENTS.md 中的约束
使用了 multica-ai/andrej-karpathy-skills 的 CLAUDE.md. 它整理的规则比较直接: 写代码前说明假设和取舍, 优先简单实现, 只改当前任务需要的部分, 最后按明确的成功判据验证.
使用这些规则约束 AI 的日常行为再配合工程要求能够达到较好的实现效果. 在过程当中也形成了一份 Engineering Principles.
另外, 在过程当中没有把完整的 SOLID 原则列成强制要求. 不是说 SOLID 不好用, 而是没必要要求每个项目都严格按照 SOLID 去执行. 是否需要某种抽象要看工程规模/当前阶段, 以及已经出现的变化需求. 对一个很小的项目, 提前为扩展建立接口和分层, 可能比直接实现更难理解和维护. SOLID 主要来自面向对象设计的语境.
实现过程当中更重要的是 YAGNI 和 DRY. YAGNI 约束的是“别提前做功能”. 新增的行为保证/抽象和永久测试都应该有明确的依据, 即: 哪条需求需要它? 哪条明确的质量规则要求它? 而非以“以后可能用到”作为判断标准. 而 DRY 是看到两段相似代码且它们表达的是同一条规则, 就可以抽取.
其余事中和事后约束都固化到技能中, 不再赘述.
强约束
这一块主要是 lint 和其他可执行的门禁追加, 目前做得还不够, 等有经验了再完整分享. 有兴趣的朋友可以看看 DeepSeek Harness 的源码库, 可以有更深入的认识.
测试
主要引入对应可自动化执行的单元测试, 集成测试, 以及端到端测试. 测试数量不是重点, 我更关心它们是否真的能够去验证行为.
一些观察到的问题和经验
在使用这套流程过程中, 看到一些问题:
- 第一个问题就是需求被 AI 错误扩大: 当写测试的时候, AI 往往会去思考一些完全不存在或极低概率发生的边界情况, 此时 AI 会去根据测试写一些开销很大或者成本很高的实现, 导致的问题就是代码复杂度指数级上涨. 而这些情况实际在需求当中是没有严格要求的, 甚至是需求当中根本没有要求的.
- 此外就是 AI 出于“未来需求”考虑进行的一些不必要封装, 封装之后反而还导致了更多的问题.
因此补充了三处规则化的约束:
- AGENTS.md 要求未覆盖的边界风险先说明可信触发场景, 影响, 更简单的替代方案和成本.
- implement skill 在写红测或防御代码前, 先回答“不做它, 哪条已批准结果会在可信场景下失败”.
- review skill 检查引用的需求是否真的要求这个新增行为. 无法确定时, 报成待裁决的范围问题, 不直接判为缺陷或通过.
总结
这套流程已完整跑通, 对我而言, 目前最大的改善是:
- 需求和实现有据可查. 换任意 Agent 后, 都可以让它先读 spec 和 design, 而不是重新探索代码.
- 验证和需求扩大问题虽然无法及时暴露出来, 至少是有机会暴露出来了, 这方面还需要继续探索门禁化的可执行方法.
- 形成了一个可复用的仓库: openspec-tickets-flow.
Oh My Pi 还可以作为 Zed 的 custom agent 在编辑器里使用, 实际效果非常好. 因此我现在一直在用这一套方法.
关于模型编排和配置准备另写一篇, 这里先记录需求到交付的这一篇. 感谢阅读!