返回文章

fix-from-report:报告点名哪条,就只修哪条

这是逐份拆 skill 系列的一篇,拆的是 fix-from-report——开发者侧的返修 skill:评测没过、功能被打回来时,开发者用的就是它。上一篇评测阶段 讲了报告是怎么产生的、为什么打回来的只有开放项清单;这一篇讲拿到清单之后怎么修。母篇是《从能跑到可交付:一套前端 AI 协作系统的设计笔记 》。我用的系统是开源的 frontend-harness-cli ,文中规则都能在仓库的 templates/skills/fix-from-report/SKILL.md 里找到原文。

一轮越修越乱的修复

先看一个具体的场景。

评测报告回来了:四十个条目,十一条没过。开发者拿到报告,开修。

一轮之后盘点:十一条里,他修好了六条;漏了三条——修到一半被别的问题带跑了;还有两条,他认为评测判错了,写了长篇回复逐条辩论,没动手。另外,他还顺手"改进"了十二处报告压根没提的地方,每一处单看都说得通。

再评测一轮:漏掉的三条还在,辩论的两条还在,十二处顺手改进里有两处把原本通过的条目改坏了。开放项从十一条降到七条,其中两条是这一轮新弄坏的。修复的往返次数是写死有上限的(评测阶段那篇讲过),这一轮实打实烧掉了,却没有让问题集合干净地变小。

问题不在手艺,在没有边界。清单内的会漏,清单外的乱入,不认同的靠辩论拖着。没有边界的修复,引入的变化比消除的还多。

fix-from-report 就是给修复轮划边界的。SKILL.md 开头第一句:评估失败并列出了待修项——“只修这些,并证明每个修复确实改变了行为。”

这份 skill 干什么:报告就是工单

在 Harness 的流程里,评测不过,内核不会把功能笼统地"打回重做",而是把开放项清单交给开发者。fix-from-report 由 harness next 返回 remediate 时触发,干的事一句话:把清单上的每一条,逐条复现、找到根因、最小修复、证明生效、精确提交。清单之外的事,一件不做。

它和 implement-task 的分工不同。implement-task 是照计划把功能做出来,输入是计划和任务,做新功能时该有的发挥空间都在;fix-from-report 是照报告把点名的问题关掉,输入是报告。修复的全部正当性来自那份报告——超出报告的动作,在这一轮里都没有名分。

输入:只有症状,没有评分表

开发者修复时拿到的东西,由 harness packet <feature> 给出,两部分:items——没过的条目编号(rubric 的条目 id,或者 gate:<命令>);findings——每条观察到的症状。

注意它没给什么:完整的 rubric.json。skill 原文写得很直白:“你仍然不读 rubric.json;finding 已经告诉你症状。”

这和实现阶段是同一条规矩:干活的人看不见考卷。区别只在粒度——评测之后,开发者可以知道"哪几条没过、症状是什么",没有这个信息就没法修;但评分标准全文、通过的条目判得多松、每条占多少分,依然不给。知道症状是修复的必需,知道考卷就会开始对着考卷修:分高的先修,查得松的糊弄。

除了 packet,输入还有三样,都是文件:原来的任务(plan.md)、tech-design.md、vision.md 与 .harness/standards.md——当初的意图与规范;以及开发者先前写在 developer/ 下的报告。最后一样值得单说:修复这一轮的开发者,未必是写代码时那个上下文,它可能是一个全新的上下文。它的"记忆"不在脑子里,在这些文件里:当初任务怎么写的、先前报告里记了什么、这次的症状是什么。上下文可以换人,工单和档案不能丢。

还有一类特殊的 item:gate:<命令>。它不是 rubric 条目,是交付门禁在某条命令上挂了。处理方式也不同,不用猜:运行那条命令,读第一个失败,修那个。

七步:复现、根因、最小修复、证明

skill 的流程是七步,每条 item 走完一遍:

  1. 复现。 先拿出能展示症状的东西:一个失败的测试、一条命令、一段脚本。复现不了怎么办?skill 的规定是:把试过的一切记录下来,把这一条报告为 blocked——不许盲修(原文:do not “fix” blind)。
  2. 根因。 走 systematic-debugging 的纪律,一次只验证一个假设。
  3. 最小修复。 只消除根因,不顺带重构。只有正确性确实要求时才扩大范围,并且要说明为什么。
  4. 证明。 刚才的复现现在要通过;原任务的检查和受影响的测试要通过,而且手里要有新鲜的输出——不是"我记得跑过",是现在跑、现在看。
  5. 精确提交。 git add 只加这次修复的路径;提交信息写成 fix(<item ids>): <原因>——条目编号和根因都进提交信息。
  6. 写报告。 追加到对应的 developer/<TASK-ID>-report.md:哪一条、根因(一行)、修法、提交 SHA、测试结果计数。
  7. 登记。 harness remediate-done <feature> --sha <sha>,把这一轮的修复正式记给内核。

规则部分只有三条,一条比一条硬:

  • 没有复现不修;没有新鲜的通过输出不提交。
  • 不提交无关文件;永远不许 git add -A。
  • 没提交的修复不算修复——remediate-done 需要 SHA,拿不出提交编号,这一轮在内核眼里就不存在。

还有一条藏在第七步里:不要自己运行 harness eval。 修好没有,由一个全新的评判者来判——原文:“a fresh evaluator judges the fix”。修复者给自己判卷,正是评测阶段那篇里满分评测的老问题,换个环节再犯一遍而已。

设计一:清单之外,都是新需求

为什么范围要卡这么死?因为评测的边界就是修复的边界——这句话评测阶段那篇说过,这里落到操作上。

清单上的每一条,都是评判者拿证据判出来的:有症状、有位置。修复只动这些地方,下一轮评测的对照关系就是干净的:这条关了没有、那条的证据变了没有,一一对应。

清单外的改动没有这个待遇。你"顺手优化"的一个函数,评测这一轮没查过它;它要是出了问题,要到再下一轮、甚至上线之后才暴露,而且到时候没人说得清它是这轮修进来的,还是原来就有的。所以修复轮里的每一处清单外改动,都是在给系统注入未经检验的变化。变化本身不是错,错的是它出现在修复轮。真觉得某处该改,那是一个新需求:走澄清、进计划、被评测,而不是混在修复提交里蒙混过关。

所以"只修点名的"不是繁文缛节,是对账的需要:报告是完整的工单,不多一条,也不少一条。 少一条——漏修、靠辩论拖着——是没干完;多一条——顺手改进——是干了没人验收的活。

设计二:对报告有意见,走申诉,不走改单

修复者总会遇到觉得判错了的条目。这份 skill 给这种情况留了什么出口?

先看它没留什么。报告本身不许改——评测报告写完不可变,是评测阶段的纪律,修复者没有权限动它一个字。条目也不许单方面跳过:完成标准写得明白,每条列出的 item,要么复现、根因、修复、重测全套走完,要么报告 blocked 并留下试过什么。没有第三个选项,叫"我觉得这条不对,所以不修"。

那意见往哪去?往上走,不在修复轮里解决。评判者认为评分表本身错位,走的是给规划者的 rubric 改进建议,而且只能收紧、放松要人批(评测阶段那篇的规矩);修复者这边,对应的是把复现不了的条目如实记成 blocked,尝试记录一起留在报告文件里,交给上层裁决。修复轮次耗尽还没收敛,内核置 paused 停下来等人——决定权在人,不在修复者的辩论技巧。

原因和评分表的存在理由是同一个:如果干活的一方能一边干活、一边靠争论把条目争没,评测迟早退化成另一种满分评测。标准只能在流程的上游改,改的动作本身还要留痕、要批准。 修复轮里唯一合法的"异议",是带着尝试记录的 blocked。

设计三:这样修,回路才会收敛

评测→修复→再评测,是一个回路。回路最怕的不是慢,是震荡:这轮修好的下轮又坏,条目在开和关之间来回跳,永远收不了尾。这份 skill 的规矩,几乎每条都在给回路减震:

  • 范围锁定。 只动点名条目,别处一律不碰——已经关掉的条目,不会被清单外改动重新打开。开头场景里"顺手改进弄坏已过条目"的回归,在这条规矩下没有入口。
  • 关一条,要有一条的证据。 每条先复现,证明它确实坏着;修完再跑一遍复现,证明它确实好了。不存在"应该修好了"这个状态——复现从红到绿,就是这条关闭的凭证。
  • 一条 item 一次精确提交。 提交信息带条目编号和根因。下一轮评判者拿着报告对提交,一条一条对得上;万一某个修复引入了回归,出问题的就是那一个提交,定位和回滚都精确到条。
  • 判卷换人。 修完由全新评判者重判,修复者的自我感觉不进入结论。回路的每一轮都以外界的新证据为准,不以当事人的声明为准——提交编号要验真、测试输出要新鲜、判卷要换人,整套系统是同一条原则。

四条合起来,开放项集合每一轮只许变小,或者原地 blocked 等人裁决,不许因为修复本身长出新问题。这就是收敛:不是保证一轮修好,而是保证每一轮都真的比上一轮少几个问题。

它管不了什么

边界照例说清。

第一,它救不了错的评分表。如果某条 item 本身就和真实需求对不上,修复者在这份 skill 里没有改它的权限:只能修,或者 blocked 上报,等规划者和人来裁。裁决到来之前,这条会一直卡着回路——这是故意的代价:宁可卡住等裁决,不许干活的人自己放宽标准。

第二,它依赖 findings 的质量。复现这一步能成立,前提是评测给的症状够具体:哪个页面、什么操作、期望什么、实际什么。症状写得含糊,修复者要么复现不了、报 blocked,要么对着含糊症状猜——猜的修复最容易修错地方。这份 skill 能保证"按报告修",保证不了报告本身写得好;那是评测阶段和评分表的责任。

第三,它只对清单负责。评分表没覆盖到的缺陷,这份 skill 按设计看不见——它修的是"被点名的问题",不是"所有问题"。清单之外的质量,靠评分表在澄清阶段写得够不够全,靠确认阶段的独立重验,不靠修复轮的自觉。

修复收敛、再评测通过之后,功能还要过最后一道出货前的检查。下一篇拆 ship-checklist :交付之前那份不许凭记忆、只能逐项核对的出货清单。