From 1e65b19476f2f8aa9f5ac80b39a7ee55ffb98186 Mon Sep 17 00:00:00 2001 From: LUOYN <3029295957@qq.com> Date: Wed, 21 Oct 2026 14:47:53 +0800 Subject: [PATCH] =?UTF-8?q?docs(evals):=20=E8=AE=B0=E5=BD=95=E9=A6=96?= =?UTF-8?q?=E8=BD=AE=20A/B/C=20=E8=AF=84=E6=B5=8B=E5=9F=BA=E7=BA=BF?= =?UTF-8?q?=E4=B8=8E=E5=8F=A3=E5=BE=84=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- evals/baseline.md | 196 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 evals/baseline.md diff --git a/evals/baseline.md b/evals/baseline.md new file mode 100644 index 0000000..663db63 --- /dev/null +++ b/evals/baseline.md @@ -0,0 +1,196 @@ +# 评测基线(v1 口径) + +首次跑通 `evals/runbook.md` 的三种问法,记录结果作为后续改动的对照点。 + +**结论先行** + +1. **两档模式分流成功**:问「有没有回归、能不能合」的那一轮输出了紧凑列表 + 一句结论,没有套六段骨架;另两轮问「按七维审」「给模块做体检」的输出了完整的七维覆盖表 + 拍板项 + 已查过没问题。 +2. **三轮共同漏掉的只有 2 条,而这两条恰好是标准答案站不住的那两条**(见「口径缺陷」一节)——漏报不是本 skill 的系统性短板,是标准答案的问题。 +3. **归类质量三轮接近**(66.7% / 68.8% / 68.8%,按已报条目算),**差距主要在命中覆盖**:快模式那轮命中 54%,两轮全量分别 71% / 79%。快模式少的是设计面条目,符合它「只报本次引入、能落到触发路径」的定位,不是缺陷。 +4. **2/3 轮把「两个包都叫 `package shop`」报成了问题**——这正是标准答案「不该报」清单里点名的口味问题。边界没守住,需要在 skill 里补具体例(已开票)。 +5. **标准答案漏了 13 条真缺陷**,三路合计发现,全部可复核。这意味着当前的 12 分制只覆盖了样本真实缺陷的一部分,基线偏乐观。 + +--- + +## 运行条件 + +| 项 | 值 | +|---|---| +| 日期 | 2026-10-21 | +| skill 版本 | `main` @ `594bee1`(v1.0.0 tag 之后,尚未打新 tag) | +| 被测上下文 | 三个全新子 Agent 上下文,互不污染,轮间不共享结论 | +| 样本 | `evals/fixtures/tiny-shop`,复制到仓外隔离目录后审(`scratchpads\dcr-eval\tiny-shop`) | +| 隔离 | `ground-truth.md` / `rubric.md` 不在样本目录内;样本 README 改成中性描述,不提示「有缺陷」 | +| 原始输出 | 仓外 `scratchpads\dcr-eval\runs\round-{A,B,C}.md`(不入版本控制) | + +三轮问法与期望(来自 `evals/runbook.md`): + +| 轮次 | 问法 | 期望 | 实际形态 | 判定 | +|---|---|---|---|---| +| A | 审一下这个 PR 有没有回归,能不能合 | 快模式:紧凑列表 + 结论,不套六段骨架 | 9 条紧凑条目 + 一句结论 + 三行说明 | 通过 | +| B | 按七维审 | 全量:七维 + 类型 playbook + 六段式 + 覆盖表 | 六段式 + 13 行覆盖表 + 5 条拍板 + 8 条已查过没问题 | 通过 | +| C | 给这个模块做个体检 | 全量,且应命中设计类条目 | 六段式 + 14 行覆盖表 + 2 条拍板 + 5 条已查过没问题 | 通过 | + +--- + +## 口径说明(评分怎么算的) + +按 `evals/rubric.md`: + +- **命中**:对照 `ground-truth.md` 的 G1–G12,满分 12。命中且触发路径正确记 1;触发路径含糊记 0.5;漏报记 0。**只在「证据/边界说明」里点到、没有作为一条结论提出**的,记 0.5(C1 只算表达加分,不占命中)。 +- **误报**:编造不存在的缺陷每条 −2;口味问题当问题报每条 −1;泛泛建议每条 −1;「不该报」清单里的候选每条 −1。 +- **归类正确性**:逐条取平均,满分 12。级别与标准答案一致记 1;偏差一级记 0.5;偏差两级以上、或该拍板判成缺陷,记 0。**未报出的条目记 0**,所以这一项对漏报是二次惩罚——因此同时给出「按已报条目计」的辅助值。 +- **证据强度**:每条 finding 定级(5 跑了测试/构造了最小复现;4 读了测试并确认覆盖或未覆盖;3 grep 了全部调用方;2 读了完整函数 + 相邻实现;1 只读了 diff),取该轮全部上报条目(含拍板项)的均值。声称强度高于实际做过的每条 −1;「已查过没问题」里没有验证方式的每条 −1。 +- **表达质量**:逐条,满分 12。用大白话讲清机制且配了最小对照例子记 1;讲清机制没例子记 0.6;只有结论记 0.2;术语堆砌/行号当论据记 0。拍板类条目(G12)另加一条要求:给了「两条路 + 建议 + 要你定什么」才记 1。主动说清「哪块查过没问题」并给依据,对应 C1,额外 +0.5。 +- **raw** = 命中 + 归类 + 证据均值 × 12 + 表达 − 误报扣分。 + +原始分数受标准答案缺陷影响(下一节),所以 raw 只在同口径内可比。 + +--- + +## 汇总 + +| 维度 | A(快模式) | B(按七维审) | C(模块体检) | +|---|---|---|---| +| 命中 | 6.5 / 12(54%) | 8.5 / 12(71%) | 9.5 / 12(79%) | +| 误报 | 0 条 | 1 条(−1:包名/目录名口味) | 1 条(−1:包名/目录名口味) | +| 归类(按 12 项,未命中计 0) | 4.0 / 12(33%) | 5.5 / 12(46%) | 5.5 / 12(46%) | +| 归类(按已报条目,辅助看) | 4.0 / 6(66.7%) | 5.5 / 8(68.8%) | 5.5 / 8(68.8%) | +| 证据强度均值 | 2.8 / 5 | 3.3 / 5 | 3.3 / 5 | +| 表达质量 | 6.6 / 12 | 8.2 / 12 | 8.6 / 12 | +| raw | 50.7 | 60.8 | 62.2 | +| 命中条目 | G1 G2 G3 G5 G6 G12 + G10(0.5) | G1 G2 G5 G6 G7 G9 G11 G12 + G10(0.5) | G1 G2 G5 G6 G7 G9 G11 G12 + G10(0.5) | + +三轮的证据强度都在 5 级满分之下是合理的:三轮都没有在被审目录里新建复现用例(只读约束),最高只到 4(读测试确认覆盖缺口)。 + +--- + +## 逐条对照 + +`G×` 表示命中且触发路径正确;`G×(0.5)` 表示只在证据/边界说明里点到、未作为结论提出;`—` 表示未报出。 + +| GT | A | B | C | 说明 | +|---|---|---|---|---| +| G1 Stats 指针无构造函数 → panic | G× | G× | G× | 三轮都给了具体触发(`&Checkout{}` → nil 解引用),级别都对 | +| G2 timeout 形同虚设 | G× | G× | G× | 三轮都指出了零引用 | +| G3 Code 字段从未被读取 | G× | — | — | B、C 与 G9 同根因合并成一条拍板,按标准答案的「同一根因只算一次」条款记在 G9 | +| G4 godoc 只读承诺 vs 实际写计数器 | — | — | — | **三条都没报,且 A 明确判定该承诺成立**——见「口径缺陷」 | +| G5 批次中途失败不回滚计数 | G× | G× | G× | 触发路径都对(第二单负金额 → 计数已加);A、B、C 都报成 P2,标准答案是 P1 → 归类 0.5 | +| G6 两份 moneyToCents 舍入不同 | G× | G× | G× | A 报 P1、B/C 报 P0,标准答案是 P2 → 归类 0.5 / 0 | +| G7 两侧 E_INVALID 非同一值 | — | G× | G× | A 未报;B、C 都给了 `errors.Is` 恒假的论证 | +| G8 refundWindow 硬编码 | — | — | — | **三条都没报,且 B、C 明确按「已命名、已注释、单一使用者」判为不算**——见「口径缺陷」 | +| G9 DiscountStrategy 单一实现零调用 | — | G× | G× | B、C 都归入过度设计/拍板,标准答案 P2 → 归类 0.5 | +| G10 测试盲区 | G×(0.5) | G×(0.5) | G×(0.5) | 三轮都只在证据里点了「每次都显式传 `Stats: &Stats{}`」,没有作为结论提出 | +| G11 测试用字符串比较断言 | — | G× | G× | B、C 都在 E_INVALID 那条的修法里写了「测试改用 `errors.Is`」 | +| G12 Percent 无范围校验 | G× | G× | G× | **B、C 放进了「需要你拍板」=正确处理**;A 报成 P2 缺陷 → 归类 0、表达 0.6 | +| C1 并发查过没问题 | +0.5 | +0.5 | +0.5 | 三轮都主动写明并给了依据(行号 + 锁范围推理),没有编造竞态 | + +### 每条 GT 的命中归类明细 + +| GT | 命中 A / 归类 / 表达 | 命中 B / 归类 / 表达 | 命中 C / 归类 / 表达 | +|---|---|---|---| +| G1 | 1 / 1 / 1 | 1 / 1 / 1 | 1 / 1 / 1 | +| G2 | 1 / 1 / 1 | 1 / 1 / 1 | 1 / 1 / 1 | +| G3 | 1 / 1 / 1 | 0 / 0 / 0 | 0 / 0 / 0 | +| G4 | 0 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 | +| G5 | 1 / 0.5 / 1 | 1 / 0.5 / 1 | 1 / 0.5 / 1 | +| G6 | 1 / 0.5 / 1 | 1 / 0 / 1 | 1 / 0 / 1 | +| G7 | 0 / 0 / 0 | 1 / 1 / 1 | 1 / 1 / 1 | +| G8 | 0 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 | +| G9 | 0 / 0 / 0 | 1 / 0.5 / 1 | 1 / 0.5 / 1 | +| G10 | 0.5 / 0 / 0.5 | 0.5 / 0 / 0.5 | 0.5 / 0 / 0.5 | +| G11 | 0 / 0 / 0 | 1 / 0.5 / 0.6 | 1 / 0.5 / 0.6 | +| G12 | 1 / 0 / 0.6 | 1 / 1 / 0.6 | 1 / 1 / 1 | +| 合计 | 6.5 / 4.0 / 6.6 | 8.5 / 5.5 / 8.2 | 9.5 / 5.5 / 8.6 | + +--- + +## 口径缺陷(本次评测暴露的标准答案问题) + +这两条不是模型漏报,是标准答案自己站不住。修法已开票,**本文件的分数按修前的标准答案(v1)记录**,修后复算见下一节。 + +### G4:godoc 的「只读」承诺没有被兑现(判定有误) + +标准答案写的是「文档承诺只读,实际写了 `c.Processed`(`:77`)与 `c.Stats.CheckedOut`(`:71`)」。但源码里那句承诺是有限定的: + +``` +// Checkout is read-only with respect to the order store: it never mutates +// orders and never writes back to the database. +``` + +限定语是「read-only **with respect to the order store**」——它承诺的是不动订单、不回写数据库。`c.Processed` 和 `c.Stats` 是 `Checkout` 自己的字段,不是订单存储,写它们不违反这句承诺。三轮里有两轮明确判定「该承诺成立」并给了推理(A:`checkout.go:67-78` 只读 `orders` 副本;B:逐行读 `Run` 确认无写操作)。把「有副作用」等同于「违反 read-only 承诺」是我当初写标准答案时的混淆。 + +B 轮提出的另一个角度更接近真实问题:承诺里提到的「订单存储 / 数据库」在这个模块里根本不存在,所以这条承诺没有对应对象——那是一条该进拍板的文档问题,不是「文档与实现矛盾」的 P1。 + +### G8:硬编码判定与 skill 自己的第 5 维细则冲突 + +标准答案要求把 `refundWindow = 24 * time.Hour` 当作硬编码报出。但 `references/dimensions.md` 第 5 维的「不算」条款写着: + +> **不算**:真不变量(协议固定值、7 天),已命名、已注释、只有一个使用者。 + +同一维度的「算」条款又写着「它必须随环境/租户/调用方/**数据规模/版本**变化」——退款窗口会随业务口径变化,也就是随版本变化,按「算」条款应该报。同一节的两句话互相打架,B、C 两轮都明确按「不算」条款判了「硬编码:无发现」,并且把依据写出来了(已命名、已注释、单一使用者)。**模型没做错,是细则自相矛盾。** + +需要人工拍板的是:退款窗口这类「业务政策值」到底算不算硬编码。我倾向要算——协议固定值(单位换算 100、HTTP 状态码)和业务政策值(退款窗口、账期)不是一回事,细则的括号里那个「7 天」例子恰好把业务政策值归进了「真不变量」,是根源。 + +--- + +## 标准答案漏掉的 13 条真缺陷 + +三路合计发现,全部可复核(每条都带具体触发路径)。这些是标准答案该补而没补的——当前 12 分制只覆盖了样本真实缺陷的一部分,所以基线偏乐观。 + +| # | 缺陷 | A | B | C | +|---|---|---|---|---| +| 1 | `ExpiresAt` 用**查询时刻** +24h,签发时刻根本没存(`issued` 只存金额)→ 退款窗口永远关不上 | P1 缺陷 | 拍板 | P1 缺陷 | +| 2 | 退款侧 `E_INVALID` 每次现造,与 `checkout.ErrInvalid` 非同一值 → `errors.Is` 恒假 | — | P2 | P2 | +| 3 | `Issue` 注释说「第二个返回值」表示是否新签发,实际是**第一个** | P2 | — | — | +| 4 | NaN / Inf 绕过 `< 0` 校验 → 静默返回荒谬总额 | P2 | 拍板 | P2 | +| 5 | 低于阈值的订单也被计入 `Stats.CheckedOut`,与 `Processed` 口径冲突 | P2 | P2 | P2 | +| 6 | `Checkout` 共享指针 + 非原子自增 → 并发丢计数 / data race | — | P1 | P2 | +| 7 | `issued` map 只增不减,无淘汰路径 → 内存单调增长 | — | P2 | P2 | +| 8 | 0 元退款被记成「已退款」→ 后续真退款静默变 no-op | — | — | P2 | +| 9 | `Refunder{}` 零值不可用(往 nil map 写 → panic) | — | — | P2 | +| 10 | `Order.ID` 全模块零使用 | — | — | P3 | +| 11 | `Issue` 收「元」、`IssuedCents` 返「分」→ 传结算总额进去是 100 倍退款 | — | — | P1 | +| 12 | 退款幂等只在进程内,重启/多实例会重复退款 | — | 拍板 | — | +| 13 | `PercentageDiscount.Apply` 先截断再减(total=105、10% → 95,少减半分) | — | 拍板 | 拍板 | + +第 1 条最值得注意:三轮里有两轮把它当成**缺陷**报出,一轮当成**待拍板**。分歧点在「这个模块到底该不该有退款窗口」——在没有任何调用方的模块里,这确实更接近设计问题。标准答案对此没有任何条目,所以目前无法评分。 + +--- + +## v2 口径复算(若 G4、G8 按上面的结论修正) + +把 G4、G8 移出「应当报出」(G4 移到「不该报」并附理由,G8 改为待人工确认的争议项),有效条目变 10 条: + +| 维度 | A | B | C | +|---|---|---|---| +| 命中 | 6.5 / 10(65.0%) | 8.5 / 10(85.0%) | 9.5 / 10(95.0%) | +| 归类(按 10 项) | 4.0 / 10(40%) | 5.5 / 10(55%) | 5.5 / 10(55%) | +| 归类(按已报条目) | 66.7% | 68.8% | 68.8% | + +命中差距拉开是正常的:G4、G8 两条谁都没报,删掉它们对三轮是同量抬升,但相对差距不变(4.6 个百分点 → 30 个百分点的绝对差)。 + +**后续比较用哪个口径**:v1 口径留给本次记录(`evals/baseline.md` 现状),后续改动要与本次对比就用 v1;等标准答案扩充到 v2(补上那 13 条)之后,需要**重跑一轮**才能得到与 v2 同口径的基线,否则 v1 与 v2 的分数不可比。 + +--- + +## 方法论局限 + +- **样本与标准答案同一人写**:基线偏乐观,真实水平要打折看。这是本次评测最大的系统性偏差。 +- **每种问法只跑 1 轮**:没有方差估计。同一 skill 两轮之间有波动是已知的(README 里写了),所以单轮差异小于 2 个百分点的结论不要当结论。 +- **快模式轮的 GT 覆盖天然不全**:G8 / G9 / G10 / G11 属设计面与测试面,快模式按契约本就不报。拿 A 轮的命中分去比全量轮是不公平的,上面的对比只应看「相对各自口径」。 +- **数值复算用的是 IEEE-754 等价推演**(.NET double 模拟 Go 的 `int64()` 截断与 `math.Round`),没有在被审目录里落 Go 探针(C 轮自己声明了这点)。Go 探针会给出同样的结论,但严格性差一点。 +- **A 轮有一条依赖「该目录无 git 历史」这个前提**:五个文件全算「本次引入」。换一个真实的 PR,A 轮的行为会不同(届时要区分既有与新引入)。 +- **没有做变异对照**:没有改坏代码去验证每条 finding 是否真能让测试变红,所以「finding 是真的」靠的是读码 + 数值复算,不是测试红。 + +--- + +## 相关文件 + +| 文件 | 作用 | +|---|---| +| `evals/ground-truth.md` | 标准答案(v1)。**本文件的分数按它计算** | +| `evals/rubric.md` | 评分表与五维口径 | +| `evals/runbook.md` | 怎么跑一次评测 | +| `evals/fixtures/tiny-shop/` | 样本(约 150 行 Go) | +| 仓外 `scratchpads\dcr-eval\runs\round-{A,B,C}.md` | 三轮原始输出,不入版本控制 |