§1 / 需求从哪来的
用 ai-spec 跑了大概两个月之后,我遇到了一个具体的问题:
我不知道这次 run 到底比上次好还是差。
同样的需求让 AI 跑两次,出来的文件大概率不一样。你说有改善,凭什么?你说有退步,什么地方变了?完全没有客观依据。
Cursor 和 Claude Code 给你 diff,你 accept 或 reject——这解决了「看到结果」的问题,但没有解决「这次质量怎么样」的问题。
跑了 30 次之后,我对这套 AI coding 工作流的认知其实就四个字:感觉还行。
感觉这东西不准。
§2 / 我的第一反应:用 LLM 当裁判
我当时的第一反应和大多数人一样——让一个更强的模型来评分。
思路很直接:跑完一次 codegen,再调一次 LLM,把生成的代码喂给它,让它输出一个质量分数。
听起来合理。但我认真试了之后,发现三个绕不过去的问题。
问题一:循环论证。
让模型 A 生成,让模型 B 评分,然后根据 B 的反馈改 A——你在用模型的「自信度」代替真实的「质量」。
具体表现:模型对自己输出的评分永远偏高,因为它不知道自己不知道什么。你问 GPT-4「这段代码好不好」,它的回答里隐含的前提是「我生成的代码我得认」。这不是评测,这是一群模型互相点头。
问题二:成本。
我每天跑 ai-spec create 大概 5-10 次。如果每次额外调一次 LLM 评分,光评分的人民币可能比代码生成本身还贵。
更关键的是:LLM 评分每次的输出不稳定。同样的代码,两次调用可能给出 8.2 和 7.6——这个 0.6 的差距,到底是代码质量变了,还是模型的随机性?根本无法区分。
问题三:评分标准不一致。
LLM 的评分取决于 prompt 里怎么描述「好代码」。你写「请从可读性角度评分」,它给你一个分数;换成「请从安全性角度评分」,又是另一个分数。你无法保证每次评分用的是同一个标准——这让跨 run 比较完全没有意义。
§3 / 换个思路:用确定性方法
我后来意识到,评分这件事其实不需要 LLM。
LLM 的核心能力是理解语义和生成文字。但「这段代码能不能编译」这件事,编译器自己就能回答,比任何模型都准。
这个认识让整个设计进入了另一个方向:不测「好不好」,测「通不通」。
于是有了 Harness Score。
Harness Score 是一个 0-10 的分数,四个维度加权:
| 维度 | 权重 | 计算方式 |
|---|---|---|
| Spec Compliance | 30% | Spec 里的验收条件 vs 实际输出文件的 diff |
| DSL Coverage | 25% | DSL(json) 里的 models/endpoints 有多少被实现了 |
| Compile/Lint | 20% | tsc --noEmit 的退出码 |
| 3-pass Review | 25% | LLM 评审(通过 / 需修改 / 不通过) |
除了 Review 那 25% 是 LLM,其他三个维度全是机械计算。
没有任何额外的 LLM 调用。不存在「调一个更强的模型来评分」这种循环论证。
§4 / 四个维度各自怎么工作的
Spec Compliance(30%):验收条件说了算
ai-spec pipeline 的第一个输出是一份 Spec,里面有一节叫「验收条件」。
比如:「登录接口要返回 refreshToken,错误时返回 401,body 里要有 error_code 字段」——这些是写进 spec 的条件。
跑完之后,拿这些验收条件和实际输出的文件做 diff。写了,分数高;没写,分数低。
这个维度的意义:防止 Spec 写得清楚但实现跑偏。 AI 有时候会把 spec 里明确写的内容漏掉,compliance 维度能把这个问题放大。
DSL Coverage(25%):契约说了什么,代码实现了多少
DSL(json) 是 ai-spec pipeline 的第二个输出——它把需求翻译成机器可读的结构化契约,包含 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 分成三轮:
- 第一轮:架构层——代码结构是否符合项目规范
- 第二轮:实现层——函数逻辑、边界情况、异常处理
- 第三轮:影响层——复杂度、引入的依赖风险、是否破坏现有功能
三轮的结果映射:通过 = 10分,需修改 = 5分,不通过 = 0分,加权均分。
§5 / 为什么这个分数能跨 run 比较
分数本身没有意义,比较才有意义。
Harness 的设计关键在这里:每次 run 的分数绑定了 prompt hash。
Harness Score 8.4 / 10
compliance 8.6 · coverage 9.2 · compile 10 · review 7.1
prompt_hash: a7f2d3e1
run_id: 20260408-143022
prompt hash 是根据这次需求的文字生成的。只要需求文字一样,hash 就一样,分数就可以横比。
同一个 prompt 跑了三次:
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。如果 compliance 维度一直低,你就知道是 spec 的验收条件写得不够细。
每一个维度都在告诉你一个具体的方向。
§6 / 温度计,不是裁判
Harness Score 的定位是温度计,不是裁判。
温度计不判断对错。它只需要读数准确。它告诉你「这次是 8.4,上次是 7.6」——你根据这个信号决定要不要多 review 一次,而不是让它替你做决定。
裁判和温度计的区别:
| 裁判 | 温度计 | |
|---|---|---|
| 判断「好不好」 | ✅ | ❌ |
| 读数「几分」 | 主观 | 客观 |
| 成本 | 高(每次调用 LLM) | 低(编译器跑一下) |
| 跨 run 可比 | ❌(标准不一致) | ✅(同一把尺子) |
你仍然要 review。但 review 的切入点从「全看一遍」变成「看分数低的地方」。
§7 / 这个分数能做什么、不能做什么
它能量化的:
- 这次 run 和 spec 的吻合度
- 跨 run 的质量趋势
- 不同 provider 对同一类任务的适配度差异
- 代码能不能编译
它不能量化的:
- Runtime 正确性(业务逻辑 bug Harness 看不出来的)
- 代码可读性和可维护性(这部分还是靠人审)
- 架构是否合理(Harness 只看你写没写,不看你写得好不好)
Harness 的价值是把「我这次跑完觉得还行」变成一个可记录的量化数字。
§8 / Fix-history:Harness 的另一个维度
光有分数不够。分数告诉你「哪里坏了」,但没有解决「下次怎么不坏」。
这个问题我用一个叫 Fix-history 的机制解决。
每次 ai-spec 的 import 验证器发现一个错误的 import——比如 AI 生成了一个从一个不存在的文件导入的语句——会自动修复,然后把这个错误记进一个 ledger:
// .ai-spec-fix-history.json
{
"pattern": "import { Task } from '@/apis/task/type'",
"file_not_exist": "src/apis/task/type.ts",
"fix": "created: src/apis/task/type.ts",
"count": 3,
"last_seen": "2026-04-08"
}
下次跑 codegen 之前,这个 ledger 会被加载,重复出现的 pattern 会被注入到 prompt 里:
❌ Do NOT: import { Task } from '@/apis/task/type'
Reason: file did not exist (seen 3x)
Previously fixed by creating: src/apis/task/type.ts
这个过程零 AI 成本——不是调一次 LLM 来学习,是字符串匹配加文本注入。
跑得越久,AI 犯同样错误的几率就越低。这就是 ai-spec 的「体温记忆」。
§9 / 现在的状态
Harness 已经集成在 ai-spec 的 pipeline 里,不需要额外配置。跑完任何 ai-spec create 命令都会自动输出分数。
$ ai-spec create "给订单模块加退款功能"
✔ Spec generated .ai-spec/specs/order-refund.md
✔ DSL valid Models: 3 Endpoints: 7
✔ 8 files written
✔ 3 errors fixed in 1 cycle
✔ 3-pass review passed
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