oh-my-knowledge 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +63 -63
  2. package/README.zh.md +64 -62
  3. package/dist/src/analysis/report-diagnostics.d.ts +28 -3
  4. package/dist/src/analysis/report-diagnostics.d.ts.map +1 -1
  5. package/dist/src/analysis/report-diagnostics.js +201 -81
  6. package/dist/src/analysis/report-diagnostics.js.map +1 -1
  7. package/dist/src/analysis/sample-diagnostics.d.ts +9 -2
  8. package/dist/src/analysis/sample-diagnostics.d.ts.map +1 -1
  9. package/dist/src/analysis/sample-diagnostics.js +224 -25
  10. package/dist/src/analysis/sample-diagnostics.js.map +1 -1
  11. package/dist/src/authoring/evolver.d.ts +7 -2
  12. package/dist/src/authoring/evolver.d.ts.map +1 -1
  13. package/dist/src/authoring/evolver.js +43 -9
  14. package/dist/src/authoring/evolver.js.map +1 -1
  15. package/dist/src/authoring/generator.d.ts +24 -0
  16. package/dist/src/authoring/generator.d.ts.map +1 -1
  17. package/dist/src/authoring/generator.js +67 -5
  18. package/dist/src/authoring/generator.js.map +1 -1
  19. package/dist/src/cli/coverage-renderer.d.ts +15 -0
  20. package/dist/src/cli/coverage-renderer.d.ts.map +1 -0
  21. package/dist/src/cli/coverage-renderer.js +74 -0
  22. package/dist/src/cli/coverage-renderer.js.map +1 -0
  23. package/dist/src/cli/i18n-dict.d.ts +1 -1
  24. package/dist/src/cli/i18n-dict.d.ts.map +1 -1
  25. package/dist/src/cli/i18n-dict.js +62 -40
  26. package/dist/src/cli/i18n-dict.js.map +1 -1
  27. package/dist/src/cli/index.d.ts +3 -0
  28. package/dist/src/cli/index.d.ts.map +1 -0
  29. package/dist/src/{cli.js → cli/index.js} +123 -384
  30. package/dist/src/cli/index.js.map +1 -0
  31. package/dist/src/cli/parse-run-config.d.ts +56 -0
  32. package/dist/src/cli/parse-run-config.d.ts.map +1 -0
  33. package/dist/src/cli/parse-run-config.js +195 -0
  34. package/dist/src/cli/parse-run-config.js.map +1 -0
  35. package/dist/src/cli/progress.d.ts +25 -0
  36. package/dist/src/cli/progress.d.ts.map +1 -0
  37. package/dist/src/cli/progress.js +62 -0
  38. package/dist/src/cli/progress.js.map +1 -0
  39. package/dist/src/cli/update-check.d.ts +3 -0
  40. package/dist/src/cli/update-check.d.ts.map +1 -0
  41. package/dist/src/cli/update-check.js +37 -0
  42. package/dist/src/cli/update-check.js.map +1 -0
  43. package/dist/src/eval-core/cache.d.ts +7 -5
  44. package/dist/src/eval-core/cache.d.ts.map +1 -1
  45. package/dist/src/eval-core/cache.js +11 -7
  46. package/dist/src/eval-core/cache.js.map +1 -1
  47. package/dist/src/eval-core/comparability.d.ts +11 -0
  48. package/dist/src/eval-core/comparability.d.ts.map +1 -0
  49. package/dist/src/eval-core/comparability.js +271 -0
  50. package/dist/src/eval-core/comparability.js.map +1 -0
  51. package/dist/src/eval-core/dependency-checker.d.ts +1 -1
  52. package/dist/src/eval-core/dependency-checker.js +1 -1
  53. package/dist/src/eval-core/evaluation-execution.d.ts +5 -2
  54. package/dist/src/eval-core/evaluation-execution.d.ts.map +1 -1
  55. package/dist/src/eval-core/evaluation-execution.js +29 -21
  56. package/dist/src/eval-core/evaluation-execution.js.map +1 -1
  57. package/dist/src/eval-core/evaluation-job.d.ts +2 -2
  58. package/dist/src/eval-core/evaluation-job.d.ts.map +1 -1
  59. package/dist/src/eval-core/evaluation-job.js +2 -2
  60. package/dist/src/eval-core/evaluation-job.js.map +1 -1
  61. package/dist/src/eval-core/evaluation-reporting.d.ts +3 -1
  62. package/dist/src/eval-core/evaluation-reporting.d.ts.map +1 -1
  63. package/dist/src/eval-core/evaluation-reporting.js +65 -5
  64. package/dist/src/eval-core/evaluation-reporting.js.map +1 -1
  65. package/dist/src/eval-core/execution-strategy.js +6 -6
  66. package/dist/src/eval-core/execution-strategy.js.map +1 -1
  67. package/dist/src/eval-core/schema.d.ts.map +1 -1
  68. package/dist/src/eval-core/schema.js +14 -1
  69. package/dist/src/eval-core/schema.js.map +1 -1
  70. package/dist/src/eval-core/verdict.d.ts +3 -3
  71. package/dist/src/eval-core/verdict.js +3 -3
  72. package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts +111 -0
  73. package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts.map +1 -0
  74. package/dist/src/eval-workflows/batch-evaluation-workflow.js +215 -0
  75. package/dist/src/eval-workflows/batch-evaluation-workflow.js.map +1 -0
  76. package/dist/src/eval-workflows/evaluation-pipeline.d.ts +7 -5
  77. package/dist/src/eval-workflows/evaluation-pipeline.d.ts.map +1 -1
  78. package/dist/src/eval-workflows/evaluation-pipeline.js +13 -8
  79. package/dist/src/eval-workflows/evaluation-pipeline.js.map +1 -1
  80. package/dist/src/eval-workflows/evaluation-preparation.d.ts +10 -8
  81. package/dist/src/eval-workflows/evaluation-preparation.d.ts.map +1 -1
  82. package/dist/src/eval-workflows/evaluation-preparation.js +8 -4
  83. package/dist/src/eval-workflows/evaluation-preparation.js.map +1 -1
  84. package/dist/src/eval-workflows/run-evaluation.d.ts +23 -20
  85. package/dist/src/eval-workflows/run-evaluation.d.ts.map +1 -1
  86. package/dist/src/eval-workflows/run-evaluation.js +34 -25
  87. package/dist/src/eval-workflows/run-evaluation.js.map +1 -1
  88. package/dist/src/executors/claude-cli.d.ts.map +1 -1
  89. package/dist/src/executors/claude-cli.js +12 -7
  90. package/dist/src/executors/claude-cli.js.map +1 -1
  91. package/dist/src/executors/claude-sdk.d.ts +1 -1
  92. package/dist/src/executors/claude-sdk.js +1 -1
  93. package/dist/src/executors/codex-cli-trace.d.ts +10 -0
  94. package/dist/src/executors/codex-cli-trace.d.ts.map +1 -0
  95. package/dist/src/executors/codex-cli-trace.js +123 -0
  96. package/dist/src/executors/codex-cli-trace.js.map +1 -0
  97. package/dist/src/executors/codex-cli.d.ts +18 -0
  98. package/dist/src/executors/codex-cli.d.ts.map +1 -0
  99. package/dist/src/executors/codex-cli.js +254 -0
  100. package/dist/src/executors/codex-cli.js.map +1 -0
  101. package/dist/src/executors/codex-sdk.d.ts +18 -0
  102. package/dist/src/executors/codex-sdk.d.ts.map +1 -0
  103. package/dist/src/executors/codex-sdk.js +214 -0
  104. package/dist/src/executors/codex-sdk.js.map +1 -0
  105. package/dist/src/executors/gemini.d.ts.map +1 -1
  106. package/dist/src/executors/gemini.js +28 -24
  107. package/dist/src/executors/gemini.js.map +1 -1
  108. package/dist/src/executors/index.d.ts.map +1 -1
  109. package/dist/src/executors/index.js +7 -2
  110. package/dist/src/executors/index.js.map +1 -1
  111. package/dist/src/executors/runtime-fingerprint.d.ts +7 -0
  112. package/dist/src/executors/runtime-fingerprint.d.ts.map +1 -0
  113. package/dist/src/executors/runtime-fingerprint.js +277 -0
  114. package/dist/src/executors/runtime-fingerprint.js.map +1 -0
  115. package/dist/src/executors/script.d.ts.map +1 -1
  116. package/dist/src/executors/script.js +48 -56
  117. package/dist/src/executors/script.js.map +1 -1
  118. package/dist/src/executors/shared.d.ts +78 -1
  119. package/dist/src/executors/shared.d.ts.map +1 -1
  120. package/dist/src/executors/shared.js +203 -1
  121. package/dist/src/executors/shared.js.map +1 -1
  122. package/dist/src/grading/assertions.d.ts.map +1 -1
  123. package/dist/src/grading/assertions.js +21 -6
  124. package/dist/src/grading/assertions.js.map +1 -1
  125. package/dist/src/grading/gold-dataset.d.ts +1 -1
  126. package/dist/src/grading/gold-dataset.js +1 -1
  127. package/dist/src/grading/index.d.ts.map +1 -1
  128. package/dist/src/grading/index.js +11 -0
  129. package/dist/src/grading/index.js.map +1 -1
  130. package/dist/src/grading/judge.d.ts.map +1 -1
  131. package/dist/src/grading/judge.js +75 -6
  132. package/dist/src/grading/judge.js.map +1 -1
  133. package/dist/src/inputs/eval-config.js +2 -2
  134. package/dist/src/inputs/eval-config.js.map +1 -1
  135. package/dist/src/inputs/load-samples.d.ts.map +1 -1
  136. package/dist/src/inputs/load-samples.js +30 -4
  137. package/dist/src/inputs/load-samples.js.map +1 -1
  138. package/dist/src/inputs/skill-loader.d.ts +2 -2
  139. package/dist/src/inputs/skill-loader.d.ts.map +1 -1
  140. package/dist/src/inputs/skill-loader.js +2 -2
  141. package/dist/src/inputs/skill-loader.js.map +1 -1
  142. package/dist/src/renderer/html-renderer.d.ts +5 -4
  143. package/dist/src/renderer/html-renderer.d.ts.map +1 -1
  144. package/dist/src/renderer/html-renderer.js +217 -93
  145. package/dist/src/renderer/html-renderer.js.map +1 -1
  146. package/dist/src/renderer/layout.d.ts +2 -1
  147. package/dist/src/renderer/layout.d.ts.map +1 -1
  148. package/dist/src/renderer/layout.js +30 -42
  149. package/dist/src/renderer/layout.js.map +1 -1
  150. package/dist/src/renderer/summary.d.ts +3 -3
  151. package/dist/src/renderer/summary.d.ts.map +1 -1
  152. package/dist/src/renderer/summary.js +232 -55
  153. package/dist/src/renderer/summary.js.map +1 -1
  154. package/dist/src/renderer/trends.d.ts.map +1 -1
  155. package/dist/src/renderer/trends.js +5 -3
  156. package/dist/src/renderer/trends.js.map +1 -1
  157. package/dist/src/server/report-server.js +4 -4
  158. package/dist/src/server/report-server.js.map +1 -1
  159. package/dist/src/server/report-store.d.ts +7 -5
  160. package/dist/src/server/report-store.d.ts.map +1 -1
  161. package/dist/src/server/report-store.js +39 -11
  162. package/dist/src/server/report-store.js.map +1 -1
  163. package/dist/src/types/eval.d.ts +27 -5
  164. package/dist/src/types/eval.d.ts.map +1 -1
  165. package/dist/src/types/executor.d.ts +5 -0
  166. package/dist/src/types/executor.d.ts.map +1 -1
  167. package/dist/src/types/judge.d.ts +10 -0
  168. package/dist/src/types/judge.d.ts.map +1 -1
  169. package/dist/src/types/report.d.ts +151 -29
  170. package/dist/src/types/report.d.ts.map +1 -1
  171. package/dist/src/types/storage.d.ts +7 -7
  172. package/dist/src/types/storage.d.ts.map +1 -1
  173. package/package.json +14 -5
  174. package/dist/src/cli.d.ts +0 -3
  175. package/dist/src/cli.d.ts.map +0 -1
  176. package/dist/src/cli.js.map +0 -1
  177. package/dist/src/eval-workflows/each-evaluation-workflow.d.ts +0 -153
  178. package/dist/src/eval-workflows/each-evaluation-workflow.d.ts.map +0 -1
  179. package/dist/src/eval-workflows/each-evaluation-workflow.js +0 -178
  180. package/dist/src/eval-workflows/each-evaluation-workflow.js.map +0 -1
  181. package/dist/src/executors/openai-cli.d.ts +0 -3
  182. package/dist/src/executors/openai-cli.d.ts.map +0 -1
  183. package/dist/src/executors/openai-cli.js +0 -60
  184. package/dist/src/executors/openai-cli.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { e, fmtNum, fmtCost, fmtDuration, COLORS, t } from './layout.js';
2
+ import { generateAnalysisSummary } from '../analysis/report-diagnostics.js';
2
3
  import { pValueCategory } from '../eval-core/statistics.js';
3
4
  import { computeVerdict } from '../eval-core/verdict.js';
4
5
  /**
@@ -10,19 +11,6 @@ import { computeVerdict } from '../eval-core/verdict.js';
10
11
  * UNDERPOWERED / NOISE / CAUTIOUS — none of which are "ship" but none of which
11
12
  * are "regress" either. SOLO is grey because it's an info pill, not a verdict.
12
13
  */
13
- // v0.21 B.4 — verdict level → status icon. PROGRESS/REGRESS 用实心点强信号,
14
- // CAUTIOUS 三角(警示),NOISE 空心圆(有信号但无效果),UNDERPOWERED 部分填充
15
- // (不够数据),SOLO 描边圆(单变体非对比).
16
- function levelIcon(level) {
17
- switch (level) {
18
- case 'PROGRESS': return '●';
19
- case 'REGRESS': return '●';
20
- case 'CAUTIOUS': return '▲';
21
- case 'NOISE': return '◌';
22
- case 'UNDERPOWERED': return '◔';
23
- case 'SOLO': return '○';
24
- }
25
- }
26
14
  // v0.21 B.4 — verdict 一句话总结. 把 level 标签 + Δ 数字 + action 推荐 + 原因
27
15
  // 压成一个完整的中文/英文句子, 用 variant 名直接说"X 比 Y 怎么样, 怎么办".
28
16
  // 用户读完一句话就懂结论, 不需要先学 jargon (Δ / CI / NOISE / SHIP).
@@ -36,7 +24,7 @@ function verdictOneLine(level, lang, treatment, control) {
36
24
  case 'CAUTIOUS': return `${t} 比 ${c} 略好 — 但建议再仔细看,差距很小或某层未达标`;
37
25
  case 'REGRESS': return `${t} 比 ${c} 明显更差 — 不要发布`;
38
26
  case 'NOISE': return `${t} 和 ${c} 没看出明显差别 — 可以多加几条用例再试`;
39
- case 'UNDERPOWERED': return `用例数太少,看不出 ${t} 和 ${c} 的差别 — 多跑几个再看`;
27
+ case 'UNDERPOWERED': return `评测用例数太少,看不出 ${t} 和 ${c} 的差别 — 多跑几个再看`;
40
28
  case 'SOLO': return `只跑了一组,需要加对照组才能对比`;
41
29
  }
42
30
  }
@@ -75,7 +63,7 @@ export function levelTooltip(level, lang) {
75
63
  case 'REGRESS': return '实验组分数显著劣于对照组';
76
64
  case 'CAUTIOUS': return '实验组略优于对照组,但差距小或某层未达 gate';
77
65
  case 'NOISE': return '两组分数差距置信区间跨过 0,统计上分辨不出效果';
78
- case 'UNDERPOWERED': return '用例数太少,需要多加几条再看';
66
+ case 'UNDERPOWERED': return '评测用例数太少,需要多加几条再看';
79
67
  case 'SOLO': return '只跑了一组,没做对比';
80
68
  }
81
69
  }
@@ -95,7 +83,7 @@ export function levelTooltip(level, lang) {
95
83
  // 去掉旧版 headline 和 significance 的内容重复 (perPair[0].headline 是干净的
96
84
  // Δ+CI 核心, headline 拼装版只在 SOLO mode 用).
97
85
  //
98
- // computeVerdict 在 each mode + 历史脏 report 上有 NPE 风险 (顶层 summary
86
+ // computeVerdict 在历史混合批量 report 上有 NPE 风险 (顶层 summary
99
87
  // 缺 variant 数据时 evaluateLayerGates 访问 .avgFactScore 炸). 加 try/catch
100
88
  // 让 renderer 不 crash, 改为静默 skip pill — 这是 v0.21 B.4 的 scope 范围,
101
89
  // verdict.ts/layer-gates.ts 的 defensive 修复留作单独 task.
@@ -318,11 +306,11 @@ export function renderSaturationCurve(saturation, variants, lang) {
318
306
  : `<p style="font-size:13px;color:var(--text-muted);margin:8px 0 0">${lang === 'zh' ? '提示:repeat ≥ 5 时才会输出饱和判定。当前数据只够画曲线,不足以下结论。' : 'Note: saturation verdict needs repeat ≥ 5. Current data plots the curve only.'}</p>`;
319
307
  return `
320
308
  <h2 style="margin-top:24px">${lang === 'zh' ? '饱和曲线 (Saturation curve)' : 'Saturation curve'}</h2>
321
- <p style="font-size:13px;color:var(--text-secondary);margin:4px 0 12px">${lang === 'zh' ? '随累积用例数 N 增长的均值与 95% CI。CI 宽度衰减放缓即饱和——再多用例对结论无实质收益。' : 'Mean and 95% CI as cumulative N grows. When CI shrink rate flattens, the evidence has saturated — more samples buy little.'}</p>
309
+ <p style="font-size:13px;color:var(--text-secondary);margin:4px 0 12px">${lang === 'zh' ? '随累积评测用例数 N 增长的均值与 95% CI。CI 宽度衰减放缓即饱和——再多评测用例对结论无实质收益。' : 'Mean and 95% CI as cumulative N grows. When CI shrink rate flattens, the evidence has saturated — more samples buy little.'}</p>
322
310
  <svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:${width}px;height:auto;display:block">
323
311
  ${yTicks}
324
312
  <text x="${padL - 36}" y="${padT + plotH / 2}" font-size="11" text-anchor="middle" fill="var(--text-muted)" transform="rotate(-90 ${padL - 36} ${padT + plotH / 2})">${lang === 'zh' ? '均值' : 'mean'}</text>
325
- <text x="${padL + plotW / 2}" y="${height - 4}" font-size="11" text-anchor="middle" fill="var(--text-muted)">${lang === 'zh' ? '累积用例数 N' : 'cumulative N'}</text>
313
+ <text x="${padL + plotW / 2}" y="${height - 4}" font-size="11" text-anchor="middle" fill="var(--text-muted)">${lang === 'zh' ? '累积评测用例数 N' : 'cumulative N'}</text>
326
314
  ${xTicks}
327
315
  ${seriesParts.join('\n')}
328
316
  </svg>
@@ -374,10 +362,18 @@ export function renderSummaryCards(variants, summary, lang, variance) {
374
362
  // LLM 评价层 cell
375
363
  const judgeCell = renderLayerCell(vd?.byLayer?.judge?.mean, s.avgJudgeScore);
376
364
  // Cost — only show execution cost (judge cost is tool overhead, not skill cost)
365
+ // execCostReported === false 时(如 codex executor)显示「—」并 tooltip 解释,
366
+ // 跟"真的花了 $0(全 cached / 短 prompt)"区分开。
377
367
  const execCost = s.totalExecCostUSD || 0;
368
+ const costReported = s.execCostReported !== false;
378
369
  const hasCost = execCost > 0 || (s.avgTotalTokens || 0) > 0;
370
+ const costUnreportedTooltip = lang === 'zh'
371
+ ? 'executor 不报 USD 成本(如 codex CLI),无法估算'
372
+ : 'executor does not report USD cost (e.g. codex CLI); not measurable';
379
373
  const costCell = hasCost
380
- ? `<td class="summary-cell"><div class="summary-value">${fmtCost(execCost)}</div><div class="summary-detail">${fmtNum(s.avgTotalTokens)} tokens/${t('tokPerReq', lang).replace('tokens/', '')}</div></td>`
374
+ ? (costReported
375
+ ? `<td class="summary-cell"><div class="summary-value">${fmtCost(execCost)}</div><div class="summary-detail">${fmtNum(s.avgTotalTokens)} tokens/${t('tokPerReq', lang).replace('tokens/', '')}</div></td>`
376
+ : `<td class="summary-cell" title="${e(costUnreportedTooltip)}"><div class="summary-value" style="color:var(--text-muted)">${fmtCost(0, false)}</div><div class="summary-detail">${fmtNum(s.avgTotalTokens)} tokens/${t('tokPerReq', lang).replace('tokens/', '')}</div></td>`)
381
377
  : `<td class="summary-cell"><span style="color:var(--text-muted)">N/A</span></td>`;
382
378
  // Efficiency
383
379
  const effDetails = [];
@@ -456,10 +452,10 @@ export function renderSummaryCards(variants, summary, lang, variance) {
456
452
  return `<tr><td style="border-left:3px solid ${color};padding-left:12px"><strong>${e(v)}</strong></td>${factCell}${behaviorCell}${judgeCell}${costCell}${effCell}${stabCell}</tr>`;
457
453
  }).join('');
458
454
  const guideModalId = 'guide-six-dims';
459
- const guideTitle = lang === 'zh' ? '如何阅读六维对比?' : 'How to read this 6-dim comparison?';
455
+ const guideTitle = lang === 'zh' ? '如何阅读六维对比?' : 'How to read this six-dimension comparison?';
460
456
  const guideIntro = lang === 'zh'
461
457
  ? '每行是一个实验分组(Variant),六列分别衡量不同维度:'
462
- : 'Each row is a Variant. Six columns measure independent dimensions:';
458
+ : 'Each row is an experiment variant. Six columns measure independent dimensions:';
463
459
  const icon = (emoji) => `<span aria-hidden="true">${emoji}</span>`;
464
460
  // 维度分隔加粗(border-top 2px),让六维的视觉边界更明显。sub 缩进从 28 收到 22。
465
461
  const dim = 'style="padding:12px 0 4px;border-top:2px solid var(--border);color:var(--text-primary);font-weight:600"';
@@ -469,10 +465,10 @@ export function renderSummaryCards(variants, summary, lang, variance) {
469
465
  const sub = 'style="padding:2px 0 2px 22px;font-size:12px;color:var(--text-secondary);font-weight:500"';
470
466
  const subDesc = 'style="padding:2px 0;font-size:12px;color:var(--text-muted)"';
471
467
  const guideRows = lang === 'zh' ? `
472
- <tr><td ${dimFirst}>${icon('📋')} <strong>事实</strong></td><td ${dimFirstDesc}>模型的输出说得对不对(事实声明层面)。靠规则断言判:关键词是否出现、JSON 格式是否合法等,答错了直接不给分。</td></tr>
473
- <tr><td ${dim}>${icon('🛠️')} <strong>行为</strong></td><td ${dimDesc}>模型做事的过程有没有走对路。靠规则断言判:该调的工具有没有调、有没有超过轮次/成本上限。</td></tr>
474
- <tr><td ${dim}>${icon('💬')} <strong>LLM 评价</strong></td><td ${dimDesc}>请一个 LLM 当评委,让它读被测模型的输出内容,按预先写好的评分规则(英文叫 rubric)打个 1-5 分。主观但能抓到规则断言判不了的"整体好不好"——比如回答是否清晰、有没有答非所问。</td></tr>
475
- <tr><td ${dim}>${icon('💰')} <strong>成本</strong></td><td ${dimDesc}>跑这次评测花了多少 API 调用费(只算执行成本,评委那个 LLM 的钱不算进来)。</td></tr>
468
+ <tr><td ${dimFirst}>${icon('📋')} <strong>事实</strong></td><td ${dimFirstDesc}>任务执行模型的输出说得对不对(事实声明层面)。靠规则断言判:关键词是否出现、JSON 格式是否合法等,答错了直接不给分。</td></tr>
469
+ <tr><td ${dim}>${icon('🛠️')} <strong>行为</strong></td><td ${dimDesc}>任务执行模型做事的过程有没有走对路。靠规则断言判:该调的工具有没有调、有没有超过轮次/成本上限。</td></tr>
470
+ <tr><td ${dim}>${icon('💬')} <strong>LLM 评价</strong></td><td ${dimDesc}>请一个 LLM 当评委,让它读任务执行模型的输出内容,按预先写好的评分规则(英文叫 rubric)打个 1-5 分。主观但能抓到规则断言判不了的"整体好不好"——比如回答是否清晰、有没有答非所问。</td></tr>
471
+ <tr><td ${dim}>${icon('💰')} <strong>执行成本</strong></td><td ${dimDesc}>任务执行模型跑这次评测花了多少 API 调用费。评委模型成本单独作为评测开销记录,不算进 skill 自身成本。</td></tr>
476
472
  <tr><td ${dim}>${icon('⚡')} <strong>效率</strong></td><td ${dimDesc}>一次评测平均跑多久;附带轮次数和工具调用次数。</td></tr>
477
473
  <tr><td ${dim}>${icon('🛡️')} <strong>稳定性</strong></td><td ${dimDesc}>同一份测试跑很多次,分数抖不抖。抖得越少越稳定。<strong>跑一次看不出稳定性</strong>——至少要 <code>--repeat ≥ 2</code>,不然显示"—"。</td></tr>
478
474
  <tr><td ${sub}>稳定 / 较稳 / 波动大</td><td ${subDesc}>分数波动比例 &lt;5% = 稳定 · 5~15% = 一般 · &gt;15% = 波动大</td></tr>
@@ -480,10 +476,10 @@ export function renderSummaryCards(variants, summary, lang, variance) {
480
476
  <tr><td ${sub}>CV</td><td ${subDesc}>分数抖动幅度占平均分的比例(例:CV 2% = 分数波动大约是平均分的 2%)</td></tr>
481
477
  <tr><td ${sub}>95% CI</td><td ${subDesc}>如果跑无数次求平均,真实平均分有 95% 概率落在这个范围里——范围越窄,这次测出的均值越可信</td></tr>
482
478
  ` : `
483
- <tr><td ${dimFirst}>${icon('📋')} <strong>Fact</strong></td><td ${dimFirstDesc}>Whether the model's output is factually correct. Checked by rule-based assertions — keyword matches, JSON schema validity, etc. Wrong = zero.</td></tr>
484
- <tr><td ${dim}>${icon('🛠️')} <strong>Behavior</strong></td><td ${dimDesc}>Whether the model followed the right process. Checked by rule-based assertions — did it call the expected tools, stay within turn/cost limits.</td></tr>
485
- <tr><td ${dim}>${icon('💬')} <strong>LLM judge</strong></td><td ${dimDesc}>A separate LLM acts as judge: it reads the tested model's output and scores it 1-5 against a predefined rubric. Subjective, but catches "overall feel" that rule-based assertions miss — e.g., whether the answer is clear, whether it's on-topic.</td></tr>
486
- <tr><td ${dim}>${icon('💰')} <strong>Cost</strong></td><td ${dimDesc}>API cost of this run (execution only — the judge LLM's cost isn't included here).</td></tr>
479
+ <tr><td ${dimFirst}>${icon('📋')} <strong>Fact</strong></td><td ${dimFirstDesc}>Whether the task execution model's output is factually correct. Checked by rule-based assertions — keyword matches, JSON schema validity, etc. Wrong = zero.</td></tr>
480
+ <tr><td ${dim}>${icon('🛠️')} <strong>Behavior</strong></td><td ${dimDesc}>Whether the task execution model followed the right process. Checked by rule-based assertions — did it call the expected tools, stay within turn/cost limits.</td></tr>
481
+ <tr><td ${dim}>${icon('💬')} <strong>LLM judge</strong></td><td ${dimDesc}>A separate LLM acts as judge: it reads the task execution model's output and scores it 1-5 against a predefined rubric. Subjective, but catches "overall feel" that rule-based assertions miss — e.g., whether the answer is clear, whether it's on-topic.</td></tr>
482
+ <tr><td ${dim}>${icon('💰')} <strong>Exec cost</strong></td><td ${dimDesc}>API cost for the task execution model. Judge model cost is tracked as evaluation overhead, not skill cost.</td></tr>
487
483
  <tr><td ${dim}>${icon('⚡')} <strong>Efficiency</strong></td><td ${dimDesc}>Average time per evaluation, plus turn counts and tool call stats.</td></tr>
488
484
  <tr><td ${dim}>${icon('🛡️')} <strong>Stability</strong></td><td ${dimDesc}>How much the score swings when you repeat the same test. Less swing = more stable. <strong>You can't measure stability from a single run</strong> — need <code>--repeat ≥ 2</code>, otherwise shows "—".</td></tr>
489
485
  <tr><td ${sub}>Stable / Moderate / Variable</td><td ${subDesc}>Score swing as % of mean: &lt;5% = Stable · 5~15% = Moderate · &gt;15% = Variable</td></tr>
@@ -492,7 +488,7 @@ export function renderSummaryCards(variants, summary, lang, variance) {
492
488
  <tr><td ${sub}>95% CI</td><td ${subDesc}>If you ran infinitely many times, the true mean has a 95% chance of falling in this range — narrower = you can trust the measured mean more</td></tr>
493
489
  `;
494
490
  return `
495
- <h2 style="display:flex;align-items:center;gap:4px">${lang === 'zh' ? '六维对比' : '6-Dim Comparison'} <button type="button" class="hint-btn" onclick="openModal('${guideModalId}')" aria-label="${e(guideTitle)}" aria-haspopup="dialog">?</button></h2>
491
+ <h2 style="display:flex;align-items:center;gap:4px">${lang === 'zh' ? '六维对比' : 'Six-Dimension Comparison'} <button type="button" class="hint-btn" onclick="openModal('${guideModalId}')" aria-label="${e(guideTitle)}" aria-haspopup="dialog">?</button></h2>
496
492
  <div id="${guideModalId}" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="${guideModalId}-title" onclick="if(event.target===this)closeModal('${guideModalId}')">
497
493
  <div class="modal-content">
498
494
  <div class="modal-header">
@@ -548,7 +544,7 @@ function renderJudgeAgreementBlock(variants, summary, lang) {
548
544
  <th>${t('judgeModelsLabel', lang)}</th>
549
545
  <th title="${t('pearsonDesc', lang)}">${t('pearsonLabel', lang)}</th>
550
546
  <th title="${t('madDesc', lang)}">${t('madLabel', lang)}</th>
551
- <th>${lang === 'zh' ? '用例数' : 'Samples'}</th>
547
+ <th>${lang === 'zh' ? '评测用例数' : 'Samples'}</th>
552
548
  <th>${lang === 'zh' ? 'Judge 对数' : 'Pairs'}</th>
553
549
  </tr></thead>
554
550
  <tbody>${rows}</tbody>
@@ -682,9 +678,12 @@ function getVariantMetricMean(variance, variant, key) {
682
678
  return v.mean;
683
679
  return v.byMetric?.[key]?.mean ?? null;
684
680
  }
685
- export function renderVarianceComparisons(variance, lang, layeredStatsOpen = false) {
681
+ export function renderVarianceComparisons(variance, lang, layeredStatsOpen = false, summary) {
686
682
  if (!variance || !variance.comparisons || variance.comparisons.length === 0)
687
683
  return '';
684
+ // 任一 variant 的 exec 或 judge cost 未报告 → cost 行跳过(显示「—」会跟"持平"难区分,
685
+ // 索性直接不渲染该指标行)。判 cost 列前看 summary,缺 summary 当全 reported 走旧路径。
686
+ const allCostReported = !summary || Object.values(summary).every((v) => v.execCostReported !== false && v.judgeCostReported !== false);
688
687
  const modalId = 'guide-variance-comparisons';
689
688
  const title = lang === 'zh' ? '方差与显著性' : 'Variance & Significance';
690
689
  const guideTitle = lang === 'zh' ? '如何阅读这张表?' : 'How to read this table?';
@@ -830,6 +829,9 @@ export function renderVarianceComparisons(variance, lang, layeredStatsOpen = fal
830
829
  // Collect available metric rows for this comparison
831
830
  const availableMetrics = [];
832
831
  for (const cfg of METRIC_CONFIGS) {
832
+ // cost 行:任一 variant 不报 cost 时跳过(数据是占位 0,显示"持平"误导)
833
+ if (cfg.key === 'cost' && !allCostReported)
834
+ continue;
833
835
  const m = pickMetricFromComparison(comp, cfg.key);
834
836
  if (m)
835
837
  availableMetrics.push({ cfg, metric: m });
@@ -864,7 +866,7 @@ export function renderVarianceComparisons(variance, lang, layeredStatsOpen = fal
864
866
  { label: '显著性', desc: 't 检验结论,基于 p<0.05 阈值。回答"差异真不真",和效应量"差多大"互补' },
865
867
  { label: 'p 值', desc: '假设真的没差异时,观察到当前差距的概率。越小越可信。0.05 只是约定阈值', sub: true },
866
868
  { label: 't 值', desc: '均值差 ÷ 估计误差,需配合 df 和效应量解读,不能单独看', sub: true },
867
- { label: 'df 自由度', desc: '≈"有效用例数"。--repeat 3 时通常 2~4;想达到 20+ 需 --repeat 10+', sub: true },
869
+ { label: 'df 自由度', desc: '≈"有效评测用例数"。--repeat 3 时通常 2~4;想达到 20+ 需 --repeat 10+', sub: true },
868
870
  ];
869
871
  const glossaryEn = [
870
872
  { label: 'Gap', desc: 'Cross-run mean winner + absolute difference (raw units)' },
@@ -947,7 +949,7 @@ function severityDot(severity) {
947
949
  };
948
950
  return `<span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:${color[severity] || 'var(--text-muted)'};margin-right:8px;flex-shrink:0;margin-top:6px"></span>`;
949
951
  }
950
- function renderSummaryStructured(summary) {
952
+ function renderSummaryStructured(summary, lang) {
951
953
  const markerRegex = /【([^】]+)】/g;
952
954
  const markers = [];
953
955
  let match;
@@ -962,9 +964,17 @@ function renderSummaryStructured(summary) {
962
964
  if (sections.length === 0) {
963
965
  return `<div style="padding:14px 18px;font-size:13px;line-height:1.8;color:var(--text-secondary);background:var(--bg-surface);border-radius:var(--radius);border:1px solid var(--border)">${e(summary)}</div>`;
964
966
  }
967
+ const labelMap = {
968
+ 结论: { zh: '结论', en: 'Conclusion' },
969
+ Conclusion: { zh: '结论', en: 'Conclusion' },
970
+ '关键差异': { zh: '关键差异', en: 'Key differences' },
971
+ 'Key differences': { zh: '关键差异', en: 'Key differences' },
972
+ 综合洞察: { zh: '综合洞察', en: 'Synthesis' },
973
+ Synthesis: { zh: '综合洞察', en: 'Synthesis' },
974
+ };
965
975
  const sectionHtml = sections.map((section) => `
966
976
  <div style="display:flex;gap:12px;align-items:baseline">
967
- <span style="flex-shrink:0;font-size:11px;font-weight:600;color:var(--text-muted);letter-spacing:0.03em;min-width:56px">${e(section.label)}</span>
977
+ <span style="flex-shrink:0;font-size:11px;font-weight:600;color:var(--text-muted);letter-spacing:0.03em;min-width:56px">${e(labelMap[section.label]?.[lang] || section.label)}</span>
968
978
  <span style="color:var(--text-secondary);font-size:13px;line-height:1.7">${e(section.content)}</span>
969
979
  </div>`).join('');
970
980
  return `
@@ -972,24 +982,188 @@ function renderSummaryStructured(summary) {
972
982
  ${sectionHtml}
973
983
  </div>`;
974
984
  }
975
- export function renderAnalysis(analysis, lang) {
985
+ function asRecord(value) {
986
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
987
+ }
988
+ function asArray(value) {
989
+ return Array.isArray(value) ? value : [];
990
+ }
991
+ function num(value) {
992
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
993
+ }
994
+ function localizedInsightMessage(insight, report, lang) {
995
+ const details = asRecord(insight.details);
996
+ const detailItems = asArray(insight.details);
997
+ const totalSamples = report?.results?.length || detailItems.length || num(details.total) || 0;
998
+ const type = insight.type;
999
+ switch (type) {
1000
+ case 'low_discrimination_all_passed': {
1001
+ const count = detailItems.length;
1002
+ return lang === 'zh'
1003
+ ? `${count} 个断言所有变体均通过,区分度低`
1004
+ : `${count} assertions passed for every variant, so they have low discrimination power`;
1005
+ }
1006
+ case 'low_discrimination_all_failed': {
1007
+ const count = detailItems.length;
1008
+ return lang === 'zh'
1009
+ ? `${count} 个断言所有变体均失败,断言可能过严或配置有误`
1010
+ : `${count} assertions failed for every variant; they may be too strict or misconfigured`;
1011
+ }
1012
+ case 'uniform_scores': {
1013
+ const count = detailItems.length;
1014
+ return lang === 'zh'
1015
+ ? `${count}/${totalSamples} 个评测用例在变体间分差 < 0.5,区分度较低`
1016
+ : `${count}/${totalSamples} samples differ by less than 0.5 across variants, so they have low discrimination power`;
1017
+ }
1018
+ case 'all_pass':
1019
+ return lang === 'zh'
1020
+ ? '所有断言在所有变体上全部通过,断言可能过于宽松'
1021
+ : 'All assertions passed for all variants; the assertions may be too loose';
1022
+ case 'all_fail':
1023
+ return lang === 'zh'
1024
+ ? '所有断言在所有变体上全部失败,请检查断言配置'
1025
+ : 'All assertions failed for all variants; check the assertion configuration';
1026
+ case 'suggest_repeat': {
1027
+ const variant = String(details.variant || 'variant');
1028
+ const min = num(details.min);
1029
+ const max = num(details.max);
1030
+ const range = min != null && max != null ? `${min}~${max}` : '';
1031
+ return lang === 'zh'
1032
+ ? `${variant} 的分数跨度较大${range ? `(${range})` : ''},建议使用 --repeat 多轮评测`
1033
+ : `${variant} has a wide score range${range ? ` (${range})` : ''}; run with --repeat to measure variance`;
1034
+ }
1035
+ case 'low_tool_success_rate': {
1036
+ const variant = String(details.variant || 'variant');
1037
+ const rate = num(details.toolSuccessRate);
1038
+ const pct = rate != null ? `${(rate * 100).toFixed(0)}%` : 'low';
1039
+ return lang === 'zh'
1040
+ ? `${variant} 的工具调用成功率仅 ${pct},可能存在工具选择或参数问题`
1041
+ : `${variant} has only ${pct} tool-call success, which may indicate tool-selection or parameter issues`;
1042
+ }
1043
+ case 'tool_permission_error': {
1044
+ const count = detailItems.length;
1045
+ return lang === 'zh'
1046
+ ? `检测到 ${count} 次工具权限错误,实验结论可能被环境问题污染`
1047
+ : `${count} tool permission errors were detected; the conclusion may be contaminated by environment issues`;
1048
+ }
1049
+ case 'trace_integrity_gap': {
1050
+ const count = detailItems.length;
1051
+ return lang === 'zh'
1052
+ ? `${count} 个 variant 的 trace 覆盖率低于 75%,报告可能不足以解释 agent 行为差异`
1053
+ : `${count} variants have trace coverage below 75%, so the report may not fully explain agent behavior differences`;
1054
+ }
1055
+ case 'agent_assertion_discrimination_low': {
1056
+ const total = num(details.total) || 0;
1057
+ const discriminative = num(details.discriminative) || 0;
1058
+ const pct = total > 0 ? Math.round((discriminative / total) * 100) : 0;
1059
+ return lang === 'zh'
1060
+ ? `agent 断言区分度偏低,只有 ${pct}% 的断言真正拉开了变体差异`
1061
+ : `Agent assertion discrimination is low; only ${pct}% of assertions separate variants`;
1062
+ }
1063
+ case 'agent_assertion_discrimination_ok': {
1064
+ const total = num(details.total) || 0;
1065
+ const discriminative = num(details.discriminative) || 0;
1066
+ const pct = total > 0 ? Math.round((discriminative / total) * 100) : 0;
1067
+ return lang === 'zh'
1068
+ ? `agent 断言区分度达标,${pct}% 的断言能区分变体差异`
1069
+ : `Agent assertion discrimination is healthy; ${pct}% of assertions separate variants`;
1070
+ }
1071
+ case 'efficiency_gap': {
1072
+ const variant = String(details.variant || 'variant');
1073
+ const baseline = String(details.baseline || 'baseline');
1074
+ return lang === 'zh'
1075
+ ? `${variant} 与 ${baseline} 在效率维度存在明显差异`
1076
+ : `${variant} differs materially from ${baseline} on efficiency`;
1077
+ }
1078
+ case 'tool_count_gap': {
1079
+ const variant = String(details.variant || 'variant');
1080
+ const baseline = String(details.baseline || 'baseline');
1081
+ const baseTools = num(details.baseTools);
1082
+ const otherTools = num(details.otherTools);
1083
+ const delta = baseTools != null && otherTools != null ? Math.abs(otherTools - baseTools).toFixed(1) : '';
1084
+ return lang === 'zh'
1085
+ ? `${variant} 与 ${baseline} 的工具调用次数差异明显${delta ? `(差 ${delta} 次)` : ''}`
1086
+ : `${variant} differs materially from ${baseline} in tool-call count${delta ? ` (${delta} calls apart)` : ''}`;
1087
+ }
1088
+ case 'high_cost_sample': {
1089
+ const count = detailItems.length;
1090
+ return lang === 'zh'
1091
+ ? `${count} 个评测用例成本显著高于平均值`
1092
+ : `${count} samples cost materially more than average`;
1093
+ }
1094
+ default:
1095
+ return lang === 'zh'
1096
+ ? `结构化诊断:${type}`
1097
+ : `Structured diagnostic: ${type}`;
1098
+ }
1099
+ }
1100
+ function localizedSuggestion(insight, lang) {
1101
+ switch (insight.type) {
1102
+ case 'low_discrimination_all_passed':
1103
+ return lang === 'zh'
1104
+ ? '把全通过断言替换为能检测 skill 独有细节的断言,例如特定参数名、配置值或流程要求'
1105
+ : 'Replace always-passing assertions with checks for artifact-specific details, such as parameter names, config values, or workflow requirements';
1106
+ case 'low_discrimination_all_failed':
1107
+ return lang === 'zh'
1108
+ ? '检查断言条件是否正确,或降低匹配要求,避免 broken 用例污染结论'
1109
+ : 'Check whether the assertion condition is correct, or relax the matcher to avoid broken samples contaminating the conclusion';
1110
+ case 'uniform_scores':
1111
+ return lang === 'zh'
1112
+ ? '增加更有挑战性的评测用例,或把 rubric 写得更能区分优劣'
1113
+ : 'Add more challenging samples, or make the rubric more discriminative';
1114
+ case 'all_pass':
1115
+ return lang === 'zh'
1116
+ ? '增加更严格的断言来区分不同变体的质量'
1117
+ : 'Add stricter assertions that can distinguish variant quality';
1118
+ case 'all_fail':
1119
+ return lang === 'zh'
1120
+ ? '优先检查评测配置和断言目标,确认用例不是整体不可达'
1121
+ : 'Check the evaluation config and assertion target first; make sure the sample is reachable';
1122
+ case 'suggest_repeat':
1123
+ return lang === 'zh'
1124
+ ? '使用 --repeat 3 或更多轮次获取方差、置信区间和显著性检验'
1125
+ : 'Run with --repeat 3 or more to obtain variance, confidence intervals, and significance tests';
1126
+ case 'low_tool_success_rate':
1127
+ return lang === 'zh'
1128
+ ? '检查失败工具调用,必要时在 skill 中补充工具选择和参数约束'
1129
+ : 'Inspect failed tool calls and, if needed, add tool-selection and parameter guidance to the artifact';
1130
+ case 'tool_permission_error':
1131
+ return lang === 'zh'
1132
+ ? '先解决工具权限或工作目录问题,再解读分数差异'
1133
+ : 'Fix tool permission or working-directory issues before interpreting score differences';
1134
+ case 'trace_integrity_gap':
1135
+ return lang === 'zh'
1136
+ ? '补齐 turns、toolCalls、timing 和完整输出采集,确保报告能解释行为差异'
1137
+ : 'Capture turns, tool calls, timing, and full output so the report can explain behavior differences';
1138
+ case 'agent_assertion_discrimination_low':
1139
+ return lang === 'zh'
1140
+ ? '重写 agent 断言时优先约束工具路径、关键文件读取和 turns 上限'
1141
+ : 'When rewriting agent assertions, prioritize tool path, key file reads, and turn-limit constraints';
1142
+ default:
1143
+ return lang === 'zh'
1144
+ ? '查看结构化 details 字段定位原因'
1145
+ : 'Inspect the structured details field for the underlying evidence';
1146
+ }
1147
+ }
1148
+ export function renderAnalysis(report, lang) {
1149
+ const analysis = report?.analysis;
976
1150
  if (!analysis)
977
1151
  return '';
978
- const { insights, suggestions } = analysis;
979
- if ((!insights || insights.length === 0) && (!suggestions || suggestions.length === 0))
1152
+ const { insights } = analysis;
1153
+ if ((!insights || insights.length === 0) && !report)
980
1154
  return '';
981
1155
  const issues = (insights || []).filter((insight) => !isConclusion(insight));
982
1156
  const issueLabel = lang === 'zh' ? '问题与建议' : 'Issues & Suggestions';
983
- const safeSuggestions = suggestions || [];
1157
+ const safeSuggestions = issues.map((issue) => localizedSuggestion(issue, lang));
984
1158
  let issuesHtml = '';
985
- if (issues.length > 0 || safeSuggestions.length > 0) {
986
- const maxRows = Math.max(issues.length, safeSuggestions.length);
1159
+ if (issues.length > 0) {
1160
+ const maxRows = issues.length;
987
1161
  const rows = [];
988
1162
  for (let i = 0; i < maxRows; i++) {
989
1163
  const issue = issues[i];
990
1164
  const suggestion = safeSuggestions[i];
991
1165
  const issueContent = issue
992
- ? `${severityDot(issue.severity)}<span>${e(issue.message)}</span>`
1166
+ ? `${severityDot(issue.severity)}<span>${e(localizedInsightMessage(issue, report, lang))}</span>`
993
1167
  : '';
994
1168
  const suggestionContent = suggestion
995
1169
  ? e(suggestion)
@@ -1013,9 +1187,12 @@ export function renderAnalysis(analysis, lang) {
1013
1187
  </div>
1014
1188
  `;
1015
1189
  }
1016
- const summaryHtml = analysis.summary
1017
- ? renderSummaryStructured(analysis.summary)
1190
+ const generatedSummary = report ? generateAnalysisSummary(report, lang) : undefined;
1191
+ const summaryHtml = generatedSummary
1192
+ ? renderSummaryStructured(generatedSummary, lang)
1018
1193
  : '';
1194
+ if (!summaryHtml && !issuesHtml)
1195
+ return '';
1019
1196
  return `
1020
1197
  <h2 data-i18n="autoAnalysis">${t('autoAnalysis', lang)}</h2>
1021
1198
  ${summaryHtml}
@@ -1168,8 +1345,8 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1168
1345
  return '';
1169
1346
  const title = lang === 'zh' ? '本次测评:测试用例 × 知识库' : 'This Evaluation: Test Set × Knowledge Base';
1170
1347
  const desc = lang === 'zh'
1171
- ? '展示本次测评用例和知识库的交互画像——用到哪些知识(使用情况)· 哪些知识想找但没找到或模型表达不确定(盲区)。'
1172
- : 'How this test set interacts with the KB — which knowledge was exercised (usage) · which was missed or flagged as uncertain (gaps).';
1348
+ ? '展示本次测评用例和知识库的交互画像——用到哪些知识(使用情况)· 哪些知识想找但没找到或任务执行模型表达不确定(盲区)。'
1349
+ : 'How this test set interacts with the KB — which knowledge was exercised (usage) · which knowledge the task execution model missed or flagged as uncertain (gaps).';
1173
1350
  const readHint = lang === 'zh'
1174
1351
  ? '💡 读表:两者同时高 → 知识库内容有问题(有文件但答不出)· 同时低 → 测评用例太浅(没触到复杂场景)· 使用高 + 盲区低 → 理想但警惕用例驯化'
1175
1352
  : '💡 Read together: both high → KB content issues (files exist but can\'t answer) · both low → test set too shallow · high use + low gap → ideal, but beware sample-set overfitting';
@@ -1180,7 +1357,7 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1180
1357
  ]));
1181
1358
  const signalTypeLabels = {
1182
1359
  failed_search: { zh: '搜索未命中', en: 'Search miss' },
1183
- explicit_marker: { zh: '模型标记缺口', en: 'Model-flagged gap' },
1360
+ explicit_marker: { zh: '任务执行模型标记缺口', en: 'Execution model flag' },
1184
1361
  hedging: { zh: '表达不确定', en: 'Hedging' },
1185
1362
  repeated_failure: { zh: '反复未命中', en: 'Repeated miss' },
1186
1363
  };
@@ -1274,7 +1451,7 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1274
1451
  const softShare = pct - weightedPct;
1275
1452
  const weightedHint = lang === 'zh'
1276
1453
  ? (softShare >= 10
1277
- ? `<strong>实际盲区 ${weightedPct}%</strong> · 另 ${softShare}% 为模型表达不确定(软信号,建议对照清单复核)`
1454
+ ? `<strong>实际盲区 ${weightedPct}%</strong> · 另 ${softShare}% 为任务执行模型表达不确定(软信号,建议对照清单复核)`
1278
1455
  : `<strong>实际盲区 ${weightedPct}%</strong> · 主要来自确定的搜索未命中`)
1279
1456
  : (softShare >= 10
1280
1457
  ? `<strong>real gaps ${weightedPct}%</strong> · another ${softShare}% is hedging (review list below)`
@@ -1297,7 +1474,7 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1297
1474
  ? `<div style="font-size:var(--fs-micro);color:var(--text-muted);margin-top:6px">${lang === 'zh' ? `另 ${gap.signals.length - INVENTORY_CAP} 条未展示` : `+${gap.signals.length - INVENTORY_CAP} more not shown`}</div>`
1298
1475
  : '';
1299
1476
  const detailsLabel = lang === 'zh'
1300
- ? `展开 ${gap.signals.length} 条证据(按严重度上色: 红=确定 / 黄=模型自述 / 灰=犹豫)`
1477
+ ? `展开 ${gap.signals.length} 条证据(按严重度上色: 红=确定 / 黄=执行模型自述 / 灰=犹豫)`
1301
1478
  : `Show all ${gap.signals.length} evidence items (red=confirmed · yellow=self-flagged · gray=hedging)`;
1302
1479
  gapInner = `
1303
1480
  <div class="ki-col-header">
@@ -1307,7 +1484,7 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1307
1484
  <div class="ki-bar" role="progressbar" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100" aria-label="${e(barLabel)}">
1308
1485
  <div class="ki-bar-fill" style="width:${barW}%;background:${pctColor}"></div>
1309
1486
  </div>
1310
- <div style="font-size:11px;color:var(--text-muted);margin-bottom:4px">${gap.samplesWithGap}/${gap.sampleCount} ${lang === 'zh' ? '个用例出现搜索未命中或表达不确定' : 'samples with search miss / hedging'}</div>
1487
+ <div style="font-size:11px;color:var(--text-muted);margin-bottom:4px">${gap.samplesWithGap}/${gap.sampleCount} ${lang === 'zh' ? '个评测用例出现搜索未命中或表达不确定' : 'samples with search miss / hedging'}</div>
1311
1488
  <div style="font-size:var(--fs-detail);color:var(--text-secondary);margin-bottom:8px">${weightedHint}</div>
1312
1489
  ${typeBadges ? `<div>${typeBadges}</div>` : ''}
1313
1490
  ${inventory ? `<details class="ki-details"><summary>${detailsLabel}</summary>
@@ -1356,11 +1533,11 @@ export function renderGapSection(gapReports, lang) {
1356
1533
  return '';
1357
1534
  const title = lang === 'zh' ? '本次测评的知识盲区' : 'Knowledge gaps in this evaluation';
1358
1535
  const desc = lang === 'zh'
1359
- ? '本次测评中,哪些知识想找但没找到、或模型表达不确定。数字高不一定代表知识库不全——也可能是测试用例问的领域知识库未覆盖。'
1360
- : 'Which knowledge the model tried to find but missed, or expressed uncertainty about. High numbers do not necessarily mean the KB is incomplete — the test set may be asking about areas the KB never covered.';
1536
+ ? '本次测评中,哪些知识想找但没找到、或任务执行模型表达不确定。数字高不一定代表知识库不全——也可能是测试用例问的领域知识库未覆盖。'
1537
+ : 'Which knowledge the task execution model tried to find but missed, or expressed uncertainty about. High numbers do not necessarily mean the KB is incomplete — the test set may be asking about areas the KB never covered.';
1361
1538
  const signalTypeLabels = {
1362
1539
  failed_search: { zh: '搜索未命中', en: 'Search miss' },
1363
- explicit_marker: { zh: '模型标记缺口', en: 'Model-flagged gap' },
1540
+ explicit_marker: { zh: '任务执行模型标记缺口', en: 'Execution model flag' },
1364
1541
  hedging: { zh: '表达不确定', en: 'Hedging' },
1365
1542
  repeated_failure: { zh: '反复未命中', en: 'Repeated miss' },
1366
1543
  };
@@ -1377,7 +1554,7 @@ export function renderGapSection(gapReports, lang) {
1377
1554
  const softSignalShare = pct - weightedPct;
1378
1555
  const weightedHint = lang === 'zh'
1379
1556
  ? (softSignalShare >= 10
1380
- ? `<strong>实际盲区 ${weightedPct}%</strong> · 另外 ${softSignalShare}% 为模型表达不确定(软信号,建议对照右侧清单复核)`
1557
+ ? `<strong>实际盲区 ${weightedPct}%</strong> · 另外 ${softSignalShare}% 为任务执行模型表达不确定(软信号,建议对照右侧清单复核)`
1381
1558
  : `<strong>实际盲区 ${weightedPct}%</strong> · 主要来自确定的搜索未命中`)
1382
1559
  : (softSignalShare >= 10
1383
1560
  ? `<strong>real gaps ${weightedPct}%</strong> · another ${softSignalShare}% is hedging (soft signals — review the list on the right)`
@@ -1423,7 +1600,7 @@ export function renderGapSection(gapReports, lang) {
1423
1600
  <div style="width:${barW}%;height:100%;background:${pctColor};border-radius:4px"></div>
1424
1601
  </div>
1425
1602
  <div style="font-size:var(--fs-detail);color:var(--text-muted);margin-bottom:4px">
1426
- ${report.samplesWithGap} / ${report.sampleCount} ${lang === 'zh' ? '个用例出现搜索未命中或表达不确定' : 'samples with search miss or hedging'}
1603
+ ${report.samplesWithGap} / ${report.sampleCount} ${lang === 'zh' ? '个评测用例出现搜索未命中或表达不确定' : 'samples with search miss or hedging'}
1427
1604
  </div>
1428
1605
  <div style="font-size:var(--fs-detail);color:var(--text-secondary);margin-bottom:10px">${weightedHint}</div>
1429
1606
  ${typeBadges ? `<div style="margin-bottom:10px">${typeBadges}</div>` : ''}