一、代码没错,测试全绿,但不是我想要的
先讲一件真事。前阵子我让 agent 做一个业务功能,它交得很快:代码挑不出毛病,单测全绿,附带的说明写得头头是道。真正点开页面用的时候我才反应过来,它实现的是它理解的那几个场景,不是我理解的那些。
这件事最堵的地方在于,我甚至没法指着某一行代码说它错了。后来复盘时我写下这么一段:
我想的,我说出来的,LLM 理解的,逐级递减+偏差;你说他错了吧,基于他自己的理解,也没错,代码没问题,质量也还可以,test 都过了,但是实现和覆盖的业务场景,不是我理解的那些。
把这段话拆开,是一条四格的衰减链:想的→说的→LLM 理解的→落成代码的。每一格都在丢信息,丢法还不一样。想的时候,场景在脑子里是一整片;说的时候,语言只能线性展开,边角场景顺手就省了;LLM 听的时候,拿你说的每个词去匹配它见过的最像的东西,你说上传附件,它想到的是它最熟的那种上传,未必是你系统里这个;落代码的时候,测试写的是它理解的那些场景,于是"测试全绿"和"做对了"成了两回事。绿只能证明代码符合它自己的理解,闭环闭合得很漂亮,只是闭错了圈。
这条链不是 AI 时代的新发明。把需求口头转给外包、转给隔壁组,一样衰减。不一样的是反馈速度:以前沟通慢,偏差在评审会上就被摁出来了;现在 agent 分钟级交付,你会误以为沟通成本也降到了分钟级,于是跳过确认直接验收。偏差不是消失了,是被快藏起来了。而需求偏差这种东西,发现得越晚,返工面越大。代码是便宜了,"做错了"这件事一点没便宜。
这几个月我把翻车记录归拢了一下,反复出现的是三件事。一是衰减,就是上面那条链。二是质量幻觉:测试全绿不等于对的。我有一次靠一轮评审才证伪"模块回归全绿":旧的编译产物让残留的旧用例继续跑出了绿。怎么被证伪的,第七节细说。三是选择困难:市面上的开发流程套件一大把,superpowers、OpenSpec、spec-kit、Kiro,个个都说能管住交付,我装了卸、卸了装,一度不知道该把宝押在哪。
后文会反复出现 skill 这个词,先交代一句:在 agent 编程工具里,skill 是一份写给模型看的作业指导书,什么时候该做什么、按什么步骤、产物长什么样,模型读到就照着执行。可以理解成装进上下文的说明书,只是读它的是模型。
折腾到今天,我的结论是一句话:瓶颈已经不在写代码了,在需求交付。写代码的成本塌下去了,把需求想清楚、说清楚、核对清楚的成本没塌,它成了新的瓶颈。这篇文章讲我怎么把这件事管起来:换过四套流程套件,最后留下一套主干、一个代码库图谱、一条六步流水线,外加一组把流程焊死的 hooks。
二、先说清两个词:SDD 和 TDD
这一节给"听过没用过"的读者垫平两个词,后文全是它们的变体。
SDD,Spec-Driven Development,规格驱动开发。核心动作:动代码之前先立规格(spec),代码照规格验收。听着像我们写了二十年的需求文档,区别在受众。以前的需求文档写给人看,看不懂可以追会;现在的 spec 写给 agent 看,agent 不会回头找你澄清,它照字面执行,你写漏它就漏做。所以各流派真正在拼的是同一件事:怎么让 spec 这份合同被完整地立起来、被执行、被守住。
市面上的玩家大体四个流派,一句话各给定位:
| 流派 | 代表 | 形态 | 核心机制 | 适合谁 |
|---|---|---|---|---|
| 纪律派 | superpowers | skills 集合/插件 | 不做规格体系,直接给 agent 上纪律:强制 TDD、验证过才算完成 | 想先立规矩的个人和小团队 |
| 宪法派 | spec-kit(GitHub 官方) | CLI+slash 命令 | 先立项目宪法,再走 specify→plan→tasks→implement 阶段门禁 | 要强流程治理的团队 |
| 轻量派 | OpenSpec | npm CLI | 每个变更一个文件夹,归档后增量合并为唯一事实源 | 存量项目、想轻量起步 |
| 产品派 | Kiro(AWS) | 独立 IDE | requirements/design/tasks 三件套内置,该结构已成社区事实标准 | 接受 IDE 与计费锁定 |
TDD,测试驱动开发,老词,但 agent 时代的用法有两处值得垫平。
红绿循环本身不新:先写一个会失败的测试(红),用最少的代码让它通过(绿),一次一小片。Java 老兵都会,不展开。
值得展开的是 seam(缝)。Michael Feathers 的定义:不编辑该处即可改变行为之点。写 Java 的,翻译成 Spring 方言:你换成 mock Bean 就能改变行为的地方,依赖注入点是最典型的缝。不写 Java 的,换成前端的说法:把组件的数据源换成假实现、页面行为就跟着变的地方,就是缝。后文会反复出现 pre-agreed seams(预先商定的缝):写测试之前,先说清楚测试从哪条缝插进去,而且这条缝要经过人确认。看着繁琐,它防的是 agent 在实现细节上乱开洞。
第二处更反直觉:Matt Pocock 的 tdd skill(这套系列第三、四节细说)明确把重构移出了红绿循环。重构不属于实现循环,属于 review 阶段,移交给 code-review。教科书里红绿重构三位一体,它拆开了。为什么,第四节讲到 code-review 的双轴设计就明白了,这里先记一笔。
这两个词其实共享一个底座:把"对"的定义前置。SDD 把它前置到 spec,TDD 把它前置到测试。后面你会看到,我的整条流水线就是在不同环节反复前置这个定义。衰减链的每一格,都对应一次前置。
三、四套 skill 套件,我的亲历横评
我的路线不是选型选出来的,是被上一套的毛病赶着走的。四站:自研工作流、superpowers、OpenSpec、mattpocock。每一站先说它好在哪,再说我是为什么走的。劝退点比推荐语有用。
第一站,自研。动机很正当:当时市面上的套件要么太重,要么太散,控制不住交付过程,我干脆自己攒了一套。六阶段串行管线,澄清、规格、设计、实现、验证、评审各占一相,配自动校验、三轮自修正、Spec ID 全链追溯、多 agent 上下文隔离。听上去很完整,我也真迭代了几版,前后折腾了大半个月。问题出在执行:一份大而全的说明书丢给模型,它分不清主次,前几步规规矩矩,到中段就开始自由发挥,产出和设计对不上。最终让我死心的是我自己写下的一句话:
不能大而全,LLM 会分不清主次;要小而精,每次精确解决具体痛点。
这套自研现在封存了,但我不觉得亏。它逼我把交付流程从头到尾想了一遍,后来换任何套件,我都清楚自己要的是什么。
第二站,superpowers。Jesse Vincent 的作品,纪律派代表。我本地装的是 5.1.0,实测 14 个 skill。社区传言"几十个",数下来没那么多,但"太多"这个体感成立。它的纪律是真硬:Evidence over claims,验证过才算完成;无条件 TDD,先写代码没测试会被直接删掉;每个任务派一个全新子 agent 去干,干完还有审查。强项不虚。劝退点是场景迷雾,我的原话:
感觉有点复杂,太多 skill,不清楚什么场景用什么,怎么组成交付需求流水线。
14 把好刀,可它没告诉我哪道菜用哪把。头脑风暴完是先写计划还是先开 worktree,得自己拼,拼错一两回就烦了。
第三站,OpenSpec。轻量派,Fission-AI 的 npm CLI。机制清爽:每个变更一个文件夹,proposal 讲为什么和做什么,specs 用 WHEN/THEN 写需求增量,design 和 tasks 各司其职;四条命令走完 explore→propose→apply→archive,归档时 spec 增量合并回总规格,守住唯一事实源。优点是真的:轻,对存量项目友好,归档这个设计我到现在都觉得好。但当时我留了一句体感:
好像只有 4 个吧,感觉控制不住。
冷静拆开,"控制不住"是三件事。第一,它没有需求澄清环节。四条命令从 propose 起步,explore 只算无压力头脑风暴,输入质量完全押在我自己身上。我当时的原话,自己都标注了不一定准:
对于需求的理解依靠我提供的需求内容,我提供需求质量高,就可以,需求质量低就不行。这个我的感觉也不一定准。
第二,阶段之间没有强制力,proposal 批了,apply 跑偏没有任何拦截。第三,它把规格当静态文书。写完即定稿,归档只是把文件搬进档案柜,没有"被拷问过"这道工序。一句话总结:OpenSpec 解决了"规格写在哪",没解决"规格从哪来、怎么保质"。就是这后半句,把我送到了下一站。
第四站,mattpocock 系列。Matt Pocock(写 TypeScript 教程那位)攒的一整套开发 skill,全套十几件,主流程一条线:grill 拷问式澄清需求,to-spec 综合成规格,to-tickets 拆票,implement 领票实现(内嵌 tdd),code-review 双轴评审收尾。我的定调原话:
算是比较顺手,有一系列开发的经验核心的。
我觉得这整套都很好啊,流程主要就是他,辅助一些外部评审的 skill。
“流程主要就是他”,主干定了。它接得住 OpenSpec 没解决的问题,因为它把"规格从哪来"做成了一等公民:主流程第一站就是拷问式访谈,规格是被问出来的,不是我倒出来的。细节下一节展开。
两家没亲历的,连同前四站并成一张表,不给推荐语:
| 套件 | 一句话定位 | 我的结局 |
|---|---|---|
| 自研工作流 | 六阶段管线+全追溯,什么都想管 | 封存。大而全,LLM 分不清主次 |
| superpowers | 14 个 skill 的纪律工具箱 | 弃用。刀好,没告诉我哪道菜用哪把 |
| OpenSpec | "规格写在哪"的轻量答案 | 弃用。没解决"规格从哪来" |
| mattpocock | 规格被问出来的全流程 | 主干,在用 |
| spec-kit | 宪法+严格阶段门禁 | 没上手。调研口碑:小项目嫌仪式重;有人写了千行宪法 AI 仍会偏离。流程再硬,执行者还是会漂 |
| Kiro | 三件套内置的 agentic IDE | 没上手。三件套结构被社区广泛复刻,影响力是真的,锁定也是真的 |
热度只交代一句:这几个的 star 数我真去核过,同一家的数据不同页面能差出一半,superpowers 就同时存在 19 万和 29 万两种口径。所以本文一律不引精确数。热度说明不了对不对路,我的横评只认亲历。
四、mattpocock 系列:主流程四件套怎么咬合
这节讲机制。先给全景:主流程四个开发环节,加一道评审收尾,产物接力。grill 把模糊的想法拷问成收口的需求,顺手留下词汇表;to-spec 把需求综合成一份 agent 可领取的 spec;to-tickets 把 spec 拆成带依赖关系的工单图;implement 领票实现,tdd 管测试怎么写;code-review 对标准和 spec 双轴验收。每一站的产物是下一站的输入,中间没有人肉搬运。
grill-with-docs:规格是被问出来的
grill-with-docs 是两个协议拧在一起。grilling 负责拷问:agent 拿着我的想法画一棵设计树,一轮把所有待澄清的问题摆完,不是挤牙膏式一问一答;每题都带推荐答案,事实它自己查,决策交给我;问题清单清空才收口。domain-modeling 负责词汇:模糊词当场磨利,术语冲突当场叫出来,定下来的词当场写进 CONTEXT.md。这份词汇表跨会话生效,下个会话的 agent 上来先读它,术语不漂移。至于架构决策,它也管得很有纪律:只有难逆转、没背景会看不懂、真取舍过,三条全占才值得立一条决策记录,不是什么都往档案里塞。
为什么这套对我有效,回到第一节的衰减链就看得见:衰减的源头,是"我以为的"和"我说出来的"之间的差。grill 干的事像一次编译期类型检查,把所有隐式转换摆上桌面显式化,对不上的当场报错,而不是等验收时才炸。我的两句体感:
grill-me 这个,真的是正中靶心,解决了我的核心痛点,以前提供给 LLM 的需求质量不稳定。
然后 grill-me,在这个过程中不断的锤炼,你会有新的想法;会发现自己以前想法不合理;会发现想要的与已有冲突;其实就是一个逼迫自己深度思考的过程。
第二句我要强调:拷问表面在磨需求,实际在磨我。三个"会发现"里最值钱的是第二个,想法不合理,只有被追问的时候才暴露。
词汇表不是副产品,是正产品。工作项 35(第七节的日常态案例)的澄清会上,四个分层缩写的规范名(Controller、Business、Service、DAO 那几层的统一叫法)加一条"通道二分"禁则——哪些操作必须登录态、哪些允许匿名外链,两条通道分开各管各的——当场写进 CONTEXT.md,变更记录在案;此后所有会话、所有 agent 提到这几个词都是同一个意思。
to-spec:不访谈,只综合
to-spec 的自我描述很干净:no interview, just synthesis。访谈是 grill 的事,到它这里只做综合。它先探索一遍代码库,用项目词汇表的词写 spec,尊重已有的架构决策记录;然后做全流程我最看重的一个动作:勾测试缝跟我确认。优先复用已有缝;要用新的,就开在尽量高的层次,越接近对外接口越好,扎进实现细节越深越难测。全库的缝越少越好,理想数量是一。缝的提案必须过用户确认,这条我次次都过。
产物七个部分:Problem、Solution、User Stories(一条极长的编号列表)、Implementation Decisions、Testing Decisions、Out of Scope、Notes,发布即打 ready-for-agent 标签,从设计上就是给 agent 领的。
有条规矩我特别认:Implementation Decisions 禁止写具体文件路径和代码片段,理由是它们会很快过时。唯一例外是原型里那种决策密度高到散文写不准的代码(状态机、schema 这类),允许裁到只剩决策核心后贴入。spec 是合同不是快照:写"在哪改"的 spec 一个月就是废纸,写"改成什么样"的 spec 能活很久。实战印证(档案在第七节):工作项 34 的 spec 里 16 条 User Stories 全是中文用户故事形态,Testing Decisions 一句话,全用既有缝,零新缝。
to-tickets:工时单位变了
to-tickets 把 spec 拆成工单(下文简称票),用曳光弹式纵向切片:每片窄,但完整贯穿每一层;完成的片可以独立演示验证;每片大小以装进一个全新的 context window 为准。最后这条值得停一下:工时的单位变了。以前估人日,现在估"一个干净上下文装不装得下"。一张票超过一个上下文的容量,agent 干到后半段就在残缺记忆里工作,漂移几乎是必然。
票不是列表,是图:每张票声明阻塞边,哪些票完成前不能开工;没有阻塞边的立即可领。这叫 frontier 工作法,同一时刻永远有一批可开工的票。拆完还有质询三问:粒度对不对、阻塞边是不是真的在拦、要不要合并或再拆,迭代到我点头为止。(假阻塞边的典型:把"顺路一起做"写成了"必须先做"。)
大重构有专门的 expand-contract 算法,Java 后端看名字就懂,跟数据库加列三步走同构:Expand 让新旧形态并存,不破坏任何东西;Migrate 按爆炸半径分批迁移,每批一票,旧形式还在,所以每一批结束都是绿的;Contract 等旧形态没有调用方了,删旧收口。
为什么坚持拆票?我的原话:
我一般都是拆开的,因为细致,每次专注一票,不容易漂移。
一次专注一票,防的正是衰减链的最后一环:上下文越长,理解越走样。
implement 和 tdd:五行编排器,和值得保留的测试
implement 是全系列最短的 skill,正文五行。它自己不定义流程,只定义调用顺序:可能的话用 tdd、在预先商定的缝上;测试按成本三级分层,类型检查常跑,单个测试文件常跑,全量套件最后只跑一次;干完必跑 code-review;提交到当前分支。
"预先商定的缝"五个字承接 to-spec。tdd 不是泛泛的红绿循环,它开门见山:只在预先商定的缝上写测试。写任何测试之前,先写下受测的缝并确认。好测试的定义也只有一条主线:通过公共接口验证行为。代码可以全变,测试不该变;读起来像规格说明。
skill 本体里还有一张反模式清单,三类。implementation-coupled,跟实现耦合,特征是一重构就坏但行为没变;tautological,同义反复,期望值用和代码相同的方式重算出来,构造上永真,规矩是期望值必须来自独立事实源;horizontal slicing,水平切片,先写完所有测试再写实现,测的是想象中的行为,对策是垂直切片,一次只走一个测试加一份最小实现,跟 to-tickets 的曳光弹切片是同一个思想。这里要坦白:这三类是我从 skill 本体读出来的,实战里我还没抓到过典型样本,agent 写的测试目前没让我逮到过这些毛病。没验证的东西不写成战果,先记着,留作以后对照。
我在实战里真做的是盯节奏:
我肯定关注盯着红绿节奏。
后端那一千多个单测,就是这么一红一绿磨出来的。
还有一句要如实说:套件里有个 implement-spec,整份 spec 多票并行、每票一个子 agent 各自 worktree 后台跑,进阶形态。我没用过,以单票模式为主,并行的收益和风险都还停在纸面,不吹。
code-review:双轴并行的意义
code-review 对锚定提交到 HEAD 的 diff 做两轴评审。Standards 轴看代码符不符合本仓文档化的编码标准,自带 Fowler 十二种坏味道当基线,并且明确 smell 永远是判断题不是硬违规;Spec 轴看代码做没做 spec 要求的事,报三类问题:缺失或部分实现的需求、没被要求的行为(scope creep,agent 越界的克星)、看似实现了但可疑的,每条都要引 spec 原句。两轴跑在两个并行子 agent 里,互相看不见。还有个细节:派子 agent 之前先验证锚定提交能解析、diff 非空,失败要在主会话失败,别等两个子 agent 跑空。这个 fail-fast 习惯我也搬走了。
聚合时有个奇怪的规定:两份报告分列呈现,禁止合并重排,原话是"不选跨轴总冠军,那正是分离要防止的重排序"。我第一次读到觉得小题大做,想明白后认为这是整个设计里最锋利的一刀:一旦合并排序,"标准违例多但方向对"和"标准干净但做错了事"会被压成一个分数,而后者才是灾难。分离呈现,就是把"做对了事"和"按规矩做"摆成两个独立问题,谁都别想盖过谁。
到这里,第二节埋的"重构为什么移出红绿循环"有答案了。我的理解是:实现循环只保证行为对,编码标准的把关整段移交 review,纪律集中在一处执行,比散在每个循环里可靠。
这套套件我也不是全盘照收:并行模式没用过,外围件按需取用,哪些根本没上手,详见第八节一览表。
五、GitNexus:给 agent 装上代码库图谱
mattpocock 那套解决"按什么流程干",但有个前提它没管:agent 得认识你的代码库。几万文件的仓库,agent 靠 grep 和文件名猜架构,就是我们以前在 IDE 里一层层点进去的样子,大库盲摸。GitNexus 是个代码知识图谱工具(CLI 加 MCP),把源码解析成图,让 agent 用查询代替盲摸。
原理分三层。第一层,源码:npx gitnexus analyze 解析全部源码建索引,顺手生成 AGENTS.md 之类的上下文文件。第二层,符号关系图:节点是文件、函数、类、方法这些符号,边是谁调用谁、谁继承谁、谁导入谁,而且边带置信度,查询时可以按置信度过滤。这一层跟普通调用图工具差别不大。第三层是它区别于普通调用图的核心抽象:Process,具名执行流。把一笔业务从头到尾经过的符号串起来,起个名字,“登录流程”“下单流程”。调用图告诉你谁调用谁,Process 告诉你一笔业务怎么流。做过分布式追踪的人一眼就懂:这是把 tracing 的思路搬进了静态代码。
我的工作区索引规模:34240 个符号、74497 条关系、300 条执行流。三个数各答一个问题:三万多个符号说明是全仓解析,不是抽样;七万多条关系说明调用网是真密;三百条具名执行流说明"一笔业务"粒度的东西真被串出来了。
套件七个 skill,按用途点述:
- cli 管索引;
- guide 是路由表;
- exploring 回答"这段代码怎么工作"。你问一句概念,它回的是几条具名执行流加各流程的符号清单,而不是一堆文件名,最后读源码确认,图谱导航、源码兜底;
- impact-analysis 算爆炸半径;
- debugging 按症状选查询策略:报错文本找抛出点,返回值错沿调用边追数据流,近期回归直接看改动影响面;
- refactoring 管安全改名。rename 走双通道,图推导的高置信修改直接应用,AST 文本兜底扫出来的必须人工复查,这个置信度设计很诚实;
- pr-review 做 PR 审查,它有一条行级 diff 工具永远做不到的检查:改动波及的第一层调用方(就是下面那张表里的 d=1)如果不在你的 diff 里,就是潜在破坏。改的人根本没碰那个文件,但它会坏。
深度即风险,是 impact-analysis 的核心模型:改动沿调用边向外扩散,一层 d=1,两层 d=2,三层 d=3,风险等级随深度递增:
| 深度 | 含义 | 该做什么 |
|---|---|---|
| d=1 | WILL BREAK,直接调用方 | 逐个过,漏一个就是编译错或运行时炸 |
| d=2 | LIKELY AFFECTED,间接牵连 | 逐个评估,多数要跟着改 |
| d=3 | MAY NEED TESTING,远端波及 | 标记出来,靠测试兜底 |
这个模型好在把"影响面"从感觉变成清单。第七节会看到它的实战:一次影响评估跟人工普查对账,两条路径互相抓出了对方的错。
跟 mattpocock 体系的关系,一句话:前者定纪律,后者供证据。这两个体系没有任何显式引用,成稿前我把相关 skill 文件逐一 grep 过,六个文件里找不到一处它的名字。衔接全靠使用者在流程里编排:to-spec 选缝之前跑一次 impact,图谱告诉你改哪里牵连谁,缝就该开在被牵连最集中的公共边界上;implement 收尾跑 detect_changes,把"该跑哪些测试"精确到受影响的执行流。我的工作区宪法 AGENTS.md 里写死了三条:改动前必跑 impact,提交前必跑 detect_changes,禁止 find-and-replace 式重命名。
六、我的六步流水线,和把它焊死的 hooks
前面四节是零件,这节装整机。分三层讲:人怎么给需求打底子,流水线怎么走,hooks 怎么把流程焊死。基调先说清楚,免得误解:先有大流程,大流程已经比较不错了;hooks 是后来加的,作用是固化和减少需要我干预的步骤。提速从来不是目标,稳定才是。
第 0 步:人自己先把需求过一遍
把需求交给任何 agent 之前,有一道替代不了的工序。我的原话:
自己必须分析一版的需求,想的越全面越好,越具体越好。全面是说:我有 1、2、3、4、5 好几个点或者好几个方面;拆开说,不要揉成一团。具体是说,你自己想要的具体要求,都可以给他;比如上传附件,要全局控制文件类型和大小(配置文件控制?数据库表维护等)。
上传附件这个例子我一直留着,它示范了什么叫具体:"支持上传附件"六个字是不够的,要落到文件类型谁管、大小上限放哪、走配置文件还是数据库表。你给不出这种粒度,说明还没想清楚,这时候进 grill 只会被问得更狼狈。grill 能磨利你想了的,磨不出你没想的。
六步走读
流水线全貌,我的原话就是骨架:
1、自己整理出一版需求;2、grill-with-docs 澄清需求;3、to-spec 设计;然后 hooks 自动/gitnexus-impact-analysis 评审;4、to-tickets 拆任务;5、implement 开发;然后 hooks 自动子 agent 并发 code-review 和 gitnexus-review;6、e2e 回归测试。
引文里的第 1 步就是本节开头说的第 0 步,人这道工序不算在机器链里,后面五步才是流水线本体。每步为什么存在、人干什么,挑判断点说。
第 2 步 grill 收口,人判断的是"这真是我要的吗"。推荐答案可以不同意,问题可以打回重问。
第 3 步 to-spec 发布,哨兵(本节第三层细讲)触发 hooks 自动注入影响评估指令,GitNexus 对着 spec 的实现决策跑 impact。代码还没动,爆炸半径先见光。
第 4 步拆票,人质询粒度和依赖。
第 5 步实现,agent 写码写测试,hooks 自动拉起双轴评审:code-review 的两轴子 agent,加一个并行子 agent 跑 GitNexus 的变更检测,三路报告汇总,按 P0 到 P3 分级给下一步建议(P0 阻断、P1 严重、P2 提醒、P3 吹毛求疵)。有 P0、P1 出修复清单,全绿才建议提交,并且不自动 commit,这一步永远留在人手里。
第 6 步 E2E 回归。我给 E2E 也建了一套 skill 流程(怎么搭的写在《AI 写完代码,谁来验证?我的 E2E 测试自动化实战》里),31 个回归轮次攒下来,每轮都有需求、用例、结果、摘要的标准产物,还配了个带时效核验的知识库。这两次实战它都立了功,第七节讲。
人在这条链上是四个判断点:澄清收口、缝确认、拆票质询、评审结论处置。其余机械动作全是 agent 的。回头看第一节那条衰减链:想的→说的→LLM 理解的→落成代码的,每一格在这条流水线上都有一道前置工序对着它。第 0 步和 grill 对前两格,缝确认对第三格,双轴评审和 E2E 对最后一格。
hooks:把纪律从自觉变成必然
先交代 hooks 是什么。之前写过一篇《AI 编程工具钩子实战:7 个事件,7 个真实场景》专讲原理,这篇只讲实际使用,定位不同:agent 工具里,hooks 是你注册在固定事件点上的脚本,会话启动时、一轮回复结束时,工具循环走到那个点就执行你的脚本,脚本能注入指令,能放行、能拦截。写过 Java 的话,把它理解成对话循环上的 AOP 环绕通知就行:方法返回之前,你还能再插一次调用。
上 hooks 之前的症状,我的原话:
首先是我会忘记;步骤太多的话,还有回头看,执行到哪里了;其次是 implement 触发 code-review 不稳定。
三条症状对应三个设计。会忘,让脚本替我记,流程节点固化成哨兵。回头看,让会话自己往下走。触发不稳,code-review 靠模型自觉调用,模型有时会忘;hooks 是引擎层的调用,不存在忘。
机制讲准,分三层。注册层:全局配置挂 Stop 事件 hook,不设过滤条件,脚本自己甄别;会话启动另挂两个 hook,一个注入工具指南,一个注入默认约束,凡事有据、先方案后动手、不擅自扩散范围。
触发层:约定两枚哨兵行,逐字写在工作区宪法 AGENTS.md 的模板章节里。spec 收口的回复首行带 spec-done 哨兵,实现完成的回复首行带 implement-done 哨兵。Stop hook 取本轮最终回复做匹配。命中 spec 哨兵,注入影响评估指令,让 agent 按五步走:从 spec 的实现决策提取改动面;跑影响分析,d=1 先审;对外接口必查契约影响;索引滞后就回退直读源码;按固定模板渲染报告。命中实现哨兵,注入双轴评审指令:主会话拉起 code-review 的 Standards、Spec 两轴子 agent,同一条消息里再派一个并行子 agent 跑 GitNexus 的变更检测,报告合并分级给建议。两枚都没命中,放行。
防循环层:如果本轮本身就是被哨兵续出来的回合,静默放行,不许无限接力;两枚哨兵同现也跳过。顺带把第五节那句话接上:两个体系的衔接靠人编排,编排里最容易被忘的两步,选缝前的 impact、提交前的 detect_changes,现在由哨兵代管,想忘也忘不掉。
哨兵在岗的证据
效果上有个体感和机制的落差,值得写准。我的体感是:
上一步会话结束了,自动下一个会话。
机制真相:哨兵命中后 hook 注入指令,主会话自动续一轮。还是同一个会话,我没开新窗口。评审的活是并行子 agent 干的,主会话只收报告。子 agent 是我自己开的主意:
我开了子 agent,相对独立任务分出去,不在主会话做,节约上下文。
评审这类独立任务分出去,主会话的上下文留给主线,不会被报告淹没。
哨兵模板怎么来的?我的原话是"共创后拍板固化"。agent 起草,我拍板,写进宪法。这件事有点象征意味:约定是人和 agent 一起定的,定完由 hooks 无差别执行,谁也别想偷懒,包括我。
关键代码不整段贴,给一张要点图:
这套脚本是我自己写的,没有开源,但机制全在上面三层里,照着写不难。
哨兵日志的触发时间和两个工作项的提交逐笔咬合:spec 发布之后就有影响评估的触发记录,实现提交之后就有评审触发记录;期间大量的 no-match 和防循环放行记录证明它全程在岗,不是只在关键帧出场。成本侧有个数我很得意:后端全仓 1146 个单测,mvn test 全量一轮 3 分 06 秒。这个数回答的是"全量测试到底贵不贵":便宜到可以每次提交都跑;也正因为便宜,才要按 implement 的三级分层省着用,等仓库再大一圈,它就不是三分钟了。
哨兵机制的代价:引用即触发
这套哨兵有个真实缺陷,我是在用它写这篇文章的时候撞上的,实录两次。十月十一号,我跟 agent 开澄清会讨论这篇文章的骨架,第一轮提问里引用了 spec-done 哨兵的完整字样。Stop hook 的子串匹配一看回复里有哨兵行,命中,自动注入影响评估指令。当时没有 spec,没有索引,零代码改动,agent 拒绝伪造报告,静默放行。第二轮讨论哨兵模板又引用了一次,又被注入了一次。根因坐实:includes 式的子串匹配,分不清"执行到这一步了"和"正在谈论这一步";防循环守卫只防连环续跑,不防引用即触发。好在下一条正常消息一到,检测就恢复。
加固方向是明确的:只匹配回复首行,脚本开头加工作区守卫,把讨论哨兵的会话排除在编排范围外。还没动手,如实记到第九节的账上。这事是个不错的注脚:设计来编排流水线的哨兵,把讨论它自己的会话也拦下来了。机制简单到透明,也简单到误伤。
七、两个工作项的实战档案
流程讲完了,看两台真实手术。工作项 34 和 35:一个是被 P0 打出来的 24 小时应急全链,一个是平平常常的单日全链,恰好是这套流水线的两种常态。按台账口径讲,敏感信息脱敏。
工作项 34:一个 P0 的 24 小时
起爆点是晚上一轮 E2E 回归,抓获一个 P0:引擎的三个写动词,参数声明是 Object(历史原因)。某个调用方把 HashMap 当参数传了进来;下游有一段用反射读参数字段的老代码,反射闯进了 HashMap 的内部字段,被 JDK 17 的模块封锁当场炸出 InaccessibleObjectException,一切"临时数据转正式"的路径全断。HashMap 闯 JDK 17 反射这个组合多说一句:模块系统封了内部字段的反射访问之后,凡是反射读对象字段的代码,遇到不认识的类型就可能一头撞进人家没打算开放的内部结构。不是冷门坑,但藏在 Object 参数后面,编译期毫无知觉。
当晚两步。第一步 hotfix:运行时 fail-fast,参数类型不对当场报错,先止血。第二步立项,工作项 34:根治走编译期,引入信封基类,三个写动词的参数从 Object 收紧为信封类型,传非信封对象直接编译不过。Java 人最熟的思路:能在编译期消灭的错误,绝不留给运行时。
然后流水线自己转起来。grilling 收口,保留字段的排除规则升级、record 保形契约(既有的 record 写法不被迁移破坏)这类裁决逐条记进台账;to-spec 发布 16 条 User Stories;哨兵触发影响评估,GitNexus 报 d=1 直接调用方 26 个;我同时安排人工 grep 普查,两轮对不上,从 20 勘误到 40,最后图谱全量重扫定谳 40,还查出人工漏计的根因:grep 输出被截断。spec 回炉同步。这段我特别想留:两条独立路径互相抓错,图谱抓住人肉普查的截断,人肉普查抓住初次评估的遗漏。信任靠对账建立,不靠信仰。
to-tickets 拆成 expand-contract 四票:引擎扩面,新旧并存;两批调用方迁移,互不阻塞可并行;删旧收口。一个晚上四张工单加两笔评审修复跑完,19:22 到 22:03,六笔提交。拆票、写码、写测试、跑评审都是 agent 领着票干的,我守在判断点上。
真正的成人礼在评审。四张工单落完,受影响模块的回归全绿(517/257/109 的模块口径,不是全仓那 1146 个),按老习惯这就该收工了。双轴评审不认,证伪了这个"全绿":增量编译留下的旧产物,让旧形态的残留用例继续跑出了绿。clean 模式把同一口径重跑了一遍,才见真绿。评审又驱动一批修复,当晚收口。E2E 补上源头哨兵用例和历史数据深断言,防复发:10 个用例全过,源头哨兵用例绿,深断言兑现。
这件事我最想让人记住的不是快,是那句证伪:测试全绿可以被评审证伪。没有双轴评审,假绿就顺着提交进了主干,这个 P0 的根治等于没做完。
工作项 35:单日全链
工作项 35 是日常态:一个普通需求,当天走完全链。
上午的活在前半链。grilling 四轮收口,定名一轮把四个分层缩写的规范名和一条"通道二分"禁则写进 CONTEXT.md;12:40 spec 发布,23 条 User Stories;拆 8 张工单,带依赖链。
下午转进实现。14:00 过,前后端两仓各落一笔主提交,提交信息直接带工作项和工单号,跟 issues 目录的工单记录一一对应,这是台账纪律;hooks 自动拉起双轴评审,结论 LOW,小修两笔。
E2E 给这天收了尾,而且立了功。10 个用例,8 个过、2 个规程内降级;更重要的是它抓获一个前端 P1:弹窗的对话接口在 setup 里一次性求值,闭包捕获了空 prop,对话区从此恒只读。这种问题单测很难覆盖,界面上一眼就能看见。当日改成 computed,复测转绿,弹窗单测 13/13。收尾三件事:旧码退役 SQL 我自己执行,数据库实证零残留;知识库回写;台账封档。
两个工作项摆在一起,同构:澄清、spec、影响评估、拆票、实现、双轴评审、E2E,一步不缺;差别只在入口,34 被 P0 打出来,35 从需求平静起步。34 用了 24 小时,35 用了一个工作日。流程的价值在于两种入口殊途同归,而且该拦的都拦住了:34 拦住假绿,35 拦住恒只读的弹窗。也要交代一个前提:这两条时间线踩在几个月攒下的家底上,图谱、E2E 流程、词汇表都是现成的。冷启动第一周跑不出这个速度——不是流程快,是家底厚。
八、外挂工具箱:什么时候想起它
主干之外还有一圈外挂。按"什么时候想起它"组织,比按工具名组织诚实:工具是按场景长在记忆里的。
| 场景 | 工具 | 什么时候想起它 |
|---|---|---|
| 雾太大 | wayfinder | 问题本身还表述不清楚的时候 |
| 难 bug | diagnosing-bugs + gitnexus-debugging | 反复修不好、开始怀疑人生的时候 |
| 设计拿不准 | codebase-design | 要给模块定型、缺判断词汇的时候 |
| 要外部视角 | open-code-review | 怀疑"我们都这么写"本身就是问题的时候 |
| 架构变深 | improve-codebase-architecture | 想系统找浅模块的时候 |
雾太大——wayfinder。它管的事一句话:交接,不建造。雾大到连问题都表述不清的工程,它规划成一张决策票共享地图:目的地先命名,雾区标出来,一次消解一张票,直到去路清晰。判据很妙:能不能现在把问题表述清楚,而不是能不能现在回答它。问得清就建票,问不清就老实写进"暂未明确",禁止预先把雾切成票的大小。它在工作区留下 5 张决策地图,其中一张末尾标着"地图完成:spec 已发布"。雾散了,汇入主流程,这是它和主干衔接的标准姿势。
难 bug——双通道。diagnosing-bugs 管的是怎么查,核心信条一句:没有紧反馈回路,不许理论化。先建回路再建假设:30 秒的 flaky 回路几乎等于没有回路,2 秒的确定性回路才值得托付假设;假设先排位三到五个再动手测,防锚定;每条调试日志打唯一前缀,清理时一次 grep 全带走。gitnexus-debugging 给证据:按症状查图谱,报错文本找抛出点,返回值错沿调用边追数据流,近期回归直接看改动影响面。工作区里躺着 4 份结构化缺陷清单,最狠的一份是匿名访问链炸断上传功能的 P0,堆栈逐帧定位加十用例影响面分析,这几条纪律在清单里全有实例。
设计拿不准——codebase-design。一份深模块设计的共享词汇表,它有一条款好用的 deletion test:想象把模块删掉,复杂度跟着消失,说明它是传声筒;复杂度在 N 个调用点重现,说明它才在挣饭吃。一秒判别模块深浅。还有一条"一个 adapter 是假想的缝,两个才是真的缝",评估抽象成本时特别好用。
要外部视角——open-code-review,阿里开源 ocr CLI 的 skill 适配层。系列自带的 code-review 看的是这次改动,它看的是整个仓库。跑过一次四仓全量静态审查,68 条发现,代表性的包括明文凭据入库、弱口令哈希。细节不便展开,性质你们品。外部视角的价值就在这:自己的标准轴查不出"大家都这么写但其实不该写"的东西。
improve-codebase-architecture 一笔带过:扫代码库找"浅模块变深"的机会出 HTML 报告。我没攒下专名产物,选题都进了规划清单的远期池,按需再启。
mattpocock 全家福,诚实标注用没用过:
| 工具 | 干什么 | 我的状态 |
|---|---|---|
| grill-with-docs | 拷问式澄清+词汇淬炼 | 主力,次次用 |
| to-spec / to-tickets | 综合 spec / 拆票 | 主力,次次用 |
| implement + tdd | 单票实现+红绿循环 | 主力,单票模式 |
| code-review | 双轴评审 | 主力 |
| setup-matt-pocock-skills | 一次性 repo 配置 | 工作区的本地 markdown 票仓就是按它的约定长出来的 |
| wayfinder / diagnosing-bugs / codebase-design | 决策地图 / 调试纪律 / 设计词汇 | 各有实战,见上文 |
| open-code-review | 外部全仓静态审查 | 用过一次,四仓 68 条 |
| improve-codebase-architecture | 深化机会扫描 | 扫过,选题进远期池 |
| ask-matt | 全系列路由图 | 系列自带地图,需要时翻 |
| implement-spec | 整 spec 多票并行 | 没用过,如实说 |
| wait-what / triage / prototype / git-guardrails | 措辞修正 / 外来需求分流 / 原型 / git 命令护栏 | 没上手 |
"我没有全用"在这里反而是可信度。工具箱的正确姿势是按痛点半径取用,不是集邮。
九、三条心得,和还没解决的事
拉直了看,这几个月真正留下来的,是三个判断和一份没解决的账单。
心得一:大而全不如小而精。这是我用一套自研六阶段管线的封存换来的:LLM 分不清主次,你给它的说明书越厚,它自由发挥的空间越大。后来留在我工作区里的每个 skill 都只解决一个具体痛点。grill 只管拷问,tdd 只管测试长什么样,hooks 只管哨兵。小而精的组件靠编排组成流程,编排权在人手里,这比把流程写死在一个大 skill 里可靠。
心得二:需求质量是天花板。一千多个单测、双轴评审、代码图谱,这一切都在保下限;上限由 grill 收口那一刻的需求质量决定。agent 把写代码的成本打了下来,把想清楚的成本显性化了:以前糊弄的需求可以糊弄过去,反正实现也糊弄;现在糊弄的需求会被忠实执行,一路糊弄到验收才爆。所以第 0 步省不得,grill 的时间是全流程回报率最高的时间。
心得三:人管判断,agent 管执行。这条流水线里,拍板、质询、验收是我的;拆票、写测试、跑评审、追依赖是 agent 的。这条线上最险的一次漂移苗头出现在工作项 34:影响评估和人工普查各跑各的,结果对不上。那天要是我偷懒信了任何一边,带着漏计的数字就进了 spec。判断权的移交是最容易发生、也最危险的漂移,所以我把对账做成了流程里的固定动作:两条独立路径互相抓错,收口由人拍板。
然后是还没解决的事,如实列:
- 哨兵的子串匹配边界。第六节末那两次误触发,加固方向有了,还没动手。
- hooks 的防循环守卫只防连环续跑,防不了别的形状的意外注入。
- E2E 环境的维护成本。31 轮回归攒下来,环境本身要养:35 那天两个用例走了规程内降级,按规程记录在案,根因我还没有好答案。
- implement-spec 的并行模式没实测,多票并行的收益和风险都停在纸面。
- tdd 的三类反模式没抓到过实战样本,机制的判断没法用战果校准。
最后回扣开头那个场景:代码没错,测试全绿,但不是我想要的。现在我的答案是,让"我理解的那些"在动代码之前就被写下来(spec)、被拷问过(grill)、被图谱核对过(impact),再被评审证伪过(review)。偏差还是会有,但它出现的位置从验收现场挪到了澄清会桌上,返工的代价也完全不同了。这套东西只是把瓶颈搬回了它本来该在的地方:人把自己要什么想清楚。这活儿从来就不便宜,只是以前我们可以假装它便宜。
AI 编程的需求交付,我是这样管起来的
2026.10.11 · 阅读约 28 分钟
如果这篇文章对你有帮助,欢迎转发给需要的同事和朋友。