pstack 拆解 18 · 让验证技能保持诚实
基础设施会腐烂是默认状态。feature map 和应用一旦漂移,验证就开始说谎。这一篇讲这个技能的维护循环。
maintain-verification-skill
基础设施会腐烂,这是默认状态,维护才是需要投入的动作。feature map 和别的基础设施一样,不会自己保持诚实;应用改了而 map 没改,验证就开始说谎。所以它把"保真"也变成一个例行工作:有固定流程,有明确的三种结局,而且绝不拿文档去盖住一个真实的产品回归。
应用会变,feature map 会腐烂。maintain-verification-skill 就是来保真的。
它的单位是功能,不是每一句话。目标是每个功能都从源码覆盖到、都真机跑一遍,而不是把每条 bullet 都推翻重写。
它只有三种结局,而且要明说选了哪一种。
- clean:每个功能都拿到了源码和实机覆盖;没有值得发的东西。不建分支,不开 PR。
- changed:一个 PR,发已证明的文档、harness 或 map 修正。
- blocked:覆盖没跑完,或者一个已证明的修复没法安全发出去。说清楚到底是什么卡住了。
它有一条必须守住的编辑边界:只改验证 skill 自己的目录(它的 SKILL.md、features/,以及它自己的 harness 脚本)。运行期间绝不改产品代码。 map 描述的一个行为,应用已经不做了,那要么是文档漂移(修 map),要么是产品回归(报告它,不要在文档里盖过去)。
一轮 pass 分六步。
第 0 步:找到目标。 找到要维护的那个验证 skill:项目本地、正文里有启动和驱动小节、且有 feature map 的那个,通常在项目 skill 目录里的 verify-*/。有多个候选就问你要哪个;一个都没有就停下来,指向 create-verification-skill,不要自己发明一个目标。
第 1 步:索引卫生。 读 feature map 的 README,glob 它同级的文件。修掉缺失的、多余的、重复的、死的条目。轻量,不生成完整清单。
第 2 步:源码波。 一个只读 subagent 管一个 feature 文件,并发起。每个从源码解释"这个面向用户的功能是怎么工作的",标出可能的文档漂移并给出引用,返回一份简洁的实机验证配方。子 agent 绝不驱动应用,绝不改文件。返回形状是:功能摘要、源码入口、可能的漂移或无、一份配方。
第 3 步:对齐。 每个 feature 文件都要有一份返回的摘要。把重叠的配方合并成尽量少的应用状态。抽查被引用的漂移,干净的说法不要重新证明。扫一遍最近的改动,看有没有面向用户的界面漏在 map 之外,但把它标成"缺失"之前必须有一个具体源码路径。
第 4 步:实机波。 即使源码看起来干净,这一步也是必需的。协调者拥有全部驱动权,按验证 skill 自己的启动模型来:服务器和 UI 用一个长期实例串行驱动,短命 CLI 每次开一个新的隔离会话(由 skill 的 Launch 一节决定,不是这一步决定)。
整轮 pass 要守住三条不变式,不管遇到什么失败。
- 绝不驱动一个自上次出意外以来没做过健康检查的实例。 第一次驱动前 doctor,每个新会话开始时 doctor,任何失败驱动之后 doctor。doctor 看不到的失败(健康进程上卡住的 UI 状态),就重置到一个已知状态或者重启,不要指望它自己好。
- 已经抓到的证据要在每一次清理之后活下来,要在它指定的位置检查,不能假设。
- 一次驱动启动的任何东西,都不能活过那次驱动的有效期。 失败迭代留下的残留要清掉,不管会话是卡住的、退出的,还是共享的(共享实例就清残留,不清实例)。
由 skill 漂移导致的 doctor 失败也是漂移:在编辑范围内修掉,重试一次,只重启这个修复让它失效的东西,然后再判定 blocked。一个到不了的功能,只有带上具体前置条件(鉴权、权限、操作系统、外部状态)和尝试过的路由,才能标成 verified-unreachable;如果 map 漏了那个前置条件,那就是漂移。任何来自分诊的 harness 修复,都要在上线前重新实机驱动一遍。最后一次驱动之后再做最终拆解,确保没有东西活过这次运行。
第 5 步:分诊。 用户视角的描述错了或漏了,是文档漂移,修掉。能工作的行为但 harness 驱动不了,是 harness 缺口,修掉。应用行为真的坏了,是产品缺口,记录给你,留在这个 PR 之外。
第 6 步:发或停。 changed 就发一个已证明修正的 PR,发之前把每个改过的文件重读一遍。clean 或 blocked 就说明结局和覆盖情况,不开 PR。
它建议的节奏是至少一天一次,最好来自一个定时的自动化,这样没有人需要记得它。验证基础设施和别的基础设施一样,腐烂是默认状态,维护才是需要投入的动作。
参考
原文都在 pstack 上游仓库(cursor/plugins):