§1 / 问题:AI 跑完了,你其实不知道它好不好
用 AI 写代码最诡异的地方是:它跑得越快,你越不知道它跑对了没有。
Cursor 给你一个 diff,你 accept 或 reject。Claude Code 给你修改过的文件,你 review。这个流程解决了「看到结果」的问题,但没有解决「这次比上次好还是差」的问题。
跑了 30 次之后,你对这套 AI coding 工作流的认知其实是:感觉还不错。
感觉这东西不准。
更具体地说,有两个问题靠人眼解决不了:
跨 run 无法比较。 同一个需求让 AI 跑两次,出来的文件大概率不一样。你没有办法判断第二次是不是比第一次更好。可能是模型随机性,可能是 prompt 写法有影响,可能是项目命名约定这次记住了上次忘了。没有任何客观信号。
不知道它在哪摔过。 AI 在项目里犯过的错,下次还会再犯。你手动修了一个 phantom import,下一次跑类似功能,它大概还会再犯。没有持久化的记忆告诉它「这条路走不通」。
这两件事本质上是同一个问题:流水线缺少质量记忆。
§2 / 解决思路:每次 run 跑完算一个分
我的方案是每次 ai-spec run 结束输出一个 0-10 的分数,叫 Harness Score。
名字是从汽车行业借来的:harness 是「安全带」,它的作用不是让车跑得更快,而是在车失控时给你一个信号「现在状态不对」。Harness Score 的作用类似——不是替代你判断代码好不好,而是在人审之前给你一个客观信号。
4 个维度加权:
| 维度 | 权重 | 计算方式 |
|---|---|---|
| Spec Compliance | 30% | Spec(md) 里的验收条件 vs 实际输出的 diff |
| DSL Coverage | 25% | DSL(json) 里的 models/endpoints 有多少被实现了 |
| Compile/Lint | 20% | tsc --noEmit 的退出码 |
| 3-pass Review | 25% | LLM 评审(通过 / 需修改 / 不通过) |
除了 Review 那 25%(本来就要调 LLM 做评审),其它三个维度全是机械计算的。没有任何额外的 LLM 调用,不存在「再调一个更强的模型打分」这种循环论证。
§3 / 为什么不是「用 LLM 当裁判」
这个方向我认真试过,有两个绕不过去的问题。
第一个:循环论证。
让模型 A 生成,让模型 B 评分,然后根据 B 的反馈改 A——你在训练一个模型裁判,但这个裁判本身没有 ground truth。评分高不代表代码真的好,只代表模型 B 和模型 A 看法一致。
更准确的说法是:你在用模型的「自信度」代替真实的「质量」。模型对自己输出的评分永远偏高,因为它不知道自己不知道什么。
第二个:成本。
每次 run 额外调一次 LLM 做评分,对于高频使用的场景(我每天跑 5-10 次)是可观的额外开销。更重要的是,LLM 评分每次的输出不稳定——同样的代码两次调用可能给出 8.2 和 7.6,两个分数的差异到底反映了代码质量的变化还是模型的随机性?无法区分。
所以 Harness 的定位是温度计,不是裁判。
温度计不判断对错,它只需要读数准确。它告诉你「这次是 8.4,上次是 7.6」,你根据这个信号决定要不要多 review 一次,而不是让它告诉你「这段代码好不好」。
§4 / 四个维度各自怎么算
Spec Compliance(30%):diff 说话
Spec(md) 是 ai-spec pipeline 的第一个输出——它把用户的一句话需求扩展成一份结构化的需求文档,包含背景、模型定义、端点列表、验收条件。
跑完之后,拿验收条件和实际输出的文件做 diff。验收条件里写了「登录接口要返回 refreshToken」,实际代码里有这个字段,分数就高;没写,分数就低。
这个维度的意义:防止 Spec 写得清楚但实现跑偏。AI 有时候会把 spec 里明确写的内容漏掉,compliance 维度能把这个信号放大。
DSL Coverage(25%):路径匹配说话
DSL(json) 是 ai-spec 的第二个输出——它把需求翻译成机器可读的结构化契约,包含 models、endpoints、behaviors。
跑完之后,扫实际生成的文件路径,看哪些 models 有对应的 model 文件,哪些 endpoints 有对应的 api 文件。
这个维度的意义:防止「接口文档写了但代码没实现」。Coverage 低了说明 DSL 里定义了但实际没写,通常是 AI 在某个文件上卡住之后跳过了某些端点。
Compile/Lint(20%):编译器说话
tsc --noEmit 直接跑,退出码 0 = 10 分,非 0 = 0 分。
这个维度没有模糊空间:代码要么能编译,要么不能。能编译不代表逻辑对,但编译不过一定有问题。
20% 的权重不算高,但它是一个硬门槛。如果 compile 是 0,即使其它三个维度都是满分,总分也只有 8.0——这个门槛足够让人不能忽视。
3-pass Review(25%):LLM 评审说话
这是四个维度里唯一一个依赖 LLM 的环节。
Review 分成 3 轮:第一轮检查代码逻辑是否和 spec 一致,第二轮检查边界情况,第三轮检查是否引入了明显的性能或安全问题。
三轮评审的结果映射成「通过 / 需修改 / 不通过」,再映射成分数:10 / 5 / 0,加权均分。
§5 / Prompt hash 绑定:跨 run 比较的关键
分数本身没有意义,比较才有意义。
我给每次 run 的分数绑定了 prompt hash——相同 prompt 多次跑,分数可以横比。这才能回答「这次比上次好还是差」的问题。
Harness Score 8.4 / 10
compliance 8.6 · coverage 9.2 · compile 10 · review 7.1
prompt_hash: a7f2d3e1
run_id: 20260408-143022
同一个 prompt 跑了 3 次:
run 1: 7.2 (compliance 7.0 / coverage 6.8 / compile 10 / review 5.8)
run 2: 7.8 (compliance 8.2 / coverage 7.5 / compile 10 / review 6.4)
run 3: 8.4 (compliance 8.6 / coverage 9.2 / compile 10 / review 7.1)
趋势在变好。这比「感觉还不错」有说服力多了。
Prompt hash 绑定的另一个好处:你能定位「是什么让分数变了」。如果从 Claude 换到 DeepSeek 之后分数跌了,你就知道这个 provider 对这类任务的适配度不够,可以切回去或者调整 prompt。
§6 / 这个分数能做什么、不能做什么
它能量化的:
- 这次 run 和 spec 的吻合度
- 跨 run 的质量趋势
- 不同 provider 对同一类任务的适配度差异
- Compile 是否能过
它不能量化的:
- Runtime 正确性(业务逻辑 bug Harness 看不出来的)
- 代码可读性和可维护性(这部分还是靠人审)
- 架构是否合理(Harness 只看你写没写,不看你写得好不好)
Harness 的价值是把「我这次跑完觉得还行」变成一个可记录的量化数字。你仍然要 review,但 review 的切入点从「全看一遍」变成「看分数低的地方」。
§7 / 现在的状态
Harness 已经集成在 ai-spec 的 pipeline 里,不需要额外配置。跑完任何 ai-spec create 命令都会自动输出分数。
$ ai-spec create "给订单模块加退款功能"
✔ Spec generated .ai-spec/specs/order-refund.md
✔ DSL validated .ai-spec/dsl/order-refund.json
✔ data src/models/Refund.ts src/models/Order.ts
✔ service src/services/RefundService.ts
✔ api src/api/refund.ts
✔ test src/tests/refund.spec.ts
3-pass review ✔
Harness Score 8.1 / 10
compliance 8.4 · coverage 9.0 · compile 10 · review 5.8
9 files written · restore: ai-spec restore 20260410-094521-c3d8
GitHub: github.com/hzhongzhong…
npm: npm install -g ai-spec-dev
站点: ai-spec.dev