返回文章

ship-checklist:说「做完了」之前,把每一项真的查一遍

这是 skill 深挖系列的第十三篇。前面两个系列——五层约束、五个阶段——讲的是系统结构;这个系列逐个拆构成系统的 21 份 skill,每篇只讲一份:它为什么存在,具体怎么干。母篇是《从能跑到可交付:一套前端 AI 协作系统的设计笔记 》。我用的系统是开源的 frontend-harness-cli ,文中所有规则都能在仓库里找到对应实现。

交付组有两份 skill:ship-checklist 和 sync-overview。这篇讲第一份。交付阶段 那一篇里它只有一段话——一份在门禁前后逐项核对的清单,不做任何判断,只防漏项。这一篇把它展开:它到底查哪七件事,为什么查法是「清单」这种看起来最笨的形式。

全绿的功能,栽在上线那天

先看一个具体的场景。

一个支付功能做完了。任务逐个完成,类型检查绿,评测按 rubric 打了高分,确认阶段换人复验也过了。代码本身挑不出毛病。

上线那天,生产环境报错:支付接口全部失败。原因不在代码里——新功能要读一个环境变量 PAYMENT_API_KEY,开发者在自己机器的 .env 里配了,代码、测试、评测全都在这台机器上跑,全绿。生产环境的配置里没人加这一项,而这件事不属于任何人的检查范围:类型检查看不见环境变量,rubric 里没有「生产配置里有这个键」这一条,评测更不会去翻部署配置。

类似的还有一串:数据库迁移写好了但没人执行;文档里链接还指着改名前的旧路由;调试时留下的一行 console.log 把用户数据打进了日志。这些失败有个共同点——它们都不在功能里,在功能周围;而且没有一件是「不会」,全都是「忘了」。

评测回答的问题是「这个功能对不对」。上线那天炸掉的,是另一类问题:「该带的东西,都带齐了吗?」这份 skill 管的就是后一类。

这份 skill 是什么:一份不做判断的清单

它的开场白是这么写的:

「做完了」是一个断言,断言需要证据。在你脑子里、在你机器上能过类型检查的代码,不叫做完。

它对自己的定位是「『我写了』和『它完成了』之间的最后一道门」。使用时机写得很宽:宣布任何任务、功能或修复完成之前;开 PR 或合并之前;长会话之后,改动多到记不清的时候;把工作交给另一个人或 Agent 评审之前。

注意它和评测的分工。评测是判断:功能符不符合契约、品质够不够格,打分、有一票否决。这份清单不做任何判断——功能好不好,它一个字不说。它只核对一件事:那些不需要判断、但漏掉就出事的事项,每一项到底查过没有。

所以它的结果形态不是分数,是一张表:每一项要么实际跑过、读过,要么明确标注「未跑」——并写明原因。没有第三种状态。「大概查过了」在这张表里没有位置。

它怎么干:七步,每步都要有产出

流程一共七步,每一步后面都跟着一句「产出是什么」——查完手里要留下东西,不许只留下一个印象。

第一步,类型与 lint,完整地跑。 跑项目完整的类型检查和 lint 命令,不是只跑改动文件(除非项目把门限定成那样)。产出:两者干净退出,或者一份失败清单。

第二步,存在的测试,真的跑。 跑测试套件或项目定义的子集,记下真实计数:通过多少、失败多少、跳过多少。产出是这次运行的数字——永远不用记忆里的、上一次运行的数字。测试是三步之前跑的,那次结果和现在的代码没有关系。

第三步,按 CI 的方式构建。 跑生产构建,产出是构建结果。这一条特意写明:开发服务器冒烟测试不能替代构建——如果 CI 是构建制的话。开篇那个环境变量就是在这一层现形的:本地开发服务器跑得好好的,打包、资源处理、环境变量这些事只有真构建才碰得到。

第四步,以评审者的眼光读自己的完整 diff。 不是扫一眼改动文件列表,是逐行读。找四样东西:残留的调试输出、注释掉的代码、无关改动、误改的文件。产出是「找到并清除了」,不是「看过了,挺干净」。

第五步,列爆炸半径。 枚举这次改动可能波及什么:动过的共享组件、变了的公共 API、其他消费了这些代码的页面和功能。产出一份书面清单,每条都要有处置结论——实际走过一遍、论证过安全、或者明确标为风险,三选一,不许留空。

第六步,验收标准逐条对。 拿回最初的计划或需求,一项一项标:达成、未达成、或者经需求方同意后砍掉。产出的硬要求是:没有一条标准停留在「大概做完了」。

第七步,写诚实的总结。 交付说明里写清:交付了什么;各项检查的结果,带数字;什么没有验证、为什么;已知风险。这一条的标准定得很有意思:评审者不需要重跑任何东西来核查你的诚实度——只需要核查你的工作。换句话说,总结本身要经得起不复查的信任,靠的是把没做的也写出来。

设计一:为什么是清单,而不是更聪明

这七步里没有一步需要聪明才智。类型检查怎么跑、diff 怎么读,任何一个能写出这个功能的执行者都会。那为什么还要把它们写成一份 skill,逐项核对?

因为这类失败的病因不是无知,是遗漏。忘了配环境变量的人,完全知道环境变量要配;他只是在凌晨一点、长会话的结尾、注意力全在功能逻辑上的时候,漏了。而「更聪明」治不了遗漏——再强的判断力,也不会自动想起一件没人提醒的事。

飞行员用起飞检查单,不是因为他们不会开飞机。恰恰相反:清单上的每一项他们都做过几千次。清单防的正是「做过几千次所以这次凭感觉跳过」的那一跳,以及忙乱中顺序错位的那一下。对 AI 执行者这件事更成立:模型最典型的失败模式不是不会,是自信地以为自己都做了。一张清单不关心自信,它只问:跑了吗,数字是多少。

所以这份 skill 的形式本身就是设计:把「完成之前要查的事」从执行者的记忆里搬出来,变成一份不依赖状态、不依赖心情的外部结构。这也是它和评测放在一起看才完整的原因——评测关判断,清单关记忆,两个失败来源,两种工具。

设计二:证据会过期,结论不许含糊

skill 里列了四个反模式,条条都冲着同一种东西:含糊。

第一个:报告「测试通过」,而那次运行发生在最后三轮编辑之前。原文的说法是:**证据在代码改变的那一刻过期。**所以完成判据里写死了顺序——类型、lint、测试、构建,全部必须在最后一次编辑之后跑过,结果连同计数一起记录。先查后改,等于没查。

第二个:把类型检查绿当作构建绿。打包、资源处理、环境变量只在真构建里失败——开篇的场景就是这一条的实例。

第三个:悄悄缩小「完成」的定义。「就这一次」跳过那个慢的测试套件,总结里却写得像跑过一样。这是四个里面最严重的一个:前两个是疏忽,这个是把疏忽伪装成完成。清单的整套设计——每项标注跑没跑、没跑要写原因——就是为了让「缩小定义」这件事无处藏身:你可以跳过,但必须当着评审者的面跳过。

第四个:把未验证项埋进散文。写一句「生产上应该没问题」,读的人滑过去了,没人意识到这里有一项没查。而把它列进「未验证」一节,评审者能自己权衡这个风险要不要接受。点名,是为了把决定权交还给看的人;埋进散文,是替所有人把这个决定偷偷做了。

它管不了什么

把边界说清。

第一,它只能核对清单上有的东西。 项目没有测试套件,第二步就是「跑了,零个测试」,清单不会凭空变出一套测试来。它保证的是「列出的事项都查过」,不是「该查的事项都列全了」。新栽过的跟头要靠人把新条目加进清单,清单自己不会长。

第二,它不判断品质。 功能做得好不好、页面像不像,是 rubric 和评测的事。一份全数通过的 ship-checklist 只说明交付动作完整,不说明东西好。把「清单全绿」当成「功能优秀」,是拿错了工具的刻度。

第三,它约束结构,不保证诚实本身。 它能规定总结必须有「未验证」一节,规定不了写总结的人不往里面藏东西。它能做的,是让不诚实的成本变高:每一项都要留数字、留处置结论,编造的痕迹比含糊的散文容易被对出来。

交付完成之后,还有一件事没干:这次改动里沉淀下来的产品与技术变化,要折进项目的 overview 文件,让下一个功能的澄清阶段从最新事实出发,而不是对着一份过期的总览。下一篇讲交付组的第二份 skill,sync-overview。