oh-my-knowledge 0.23.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 (153) hide show
  1. package/README.md +63 -64
  2. package/README.zh.md +64 -63
  3. package/dist/src/analysis/report-diagnostics.d.ts +3 -2
  4. package/dist/src/analysis/report-diagnostics.d.ts.map +1 -1
  5. package/dist/src/analysis/report-diagnostics.js +107 -80
  6. package/dist/src/analysis/report-diagnostics.js.map +1 -1
  7. package/dist/src/analysis/sample-diagnostics.d.ts +4 -1
  8. package/dist/src/analysis/sample-diagnostics.d.ts.map +1 -1
  9. package/dist/src/analysis/sample-diagnostics.js +123 -29
  10. package/dist/src/analysis/sample-diagnostics.js.map +1 -1
  11. package/dist/src/authoring/evolver.d.ts +5 -0
  12. package/dist/src/authoring/evolver.d.ts.map +1 -1
  13. package/dist/src/authoring/evolver.js +20 -3
  14. package/dist/src/authoring/evolver.js.map +1 -1
  15. package/dist/src/cli/i18n-dict.d.ts +1 -1
  16. package/dist/src/cli/i18n-dict.d.ts.map +1 -1
  17. package/dist/src/cli/i18n-dict.js +28 -24
  18. package/dist/src/cli/i18n-dict.js.map +1 -1
  19. package/dist/src/cli/index.js +80 -66
  20. package/dist/src/cli/index.js.map +1 -1
  21. package/dist/src/cli/parse-run-config.d.ts.map +1 -1
  22. package/dist/src/cli/parse-run-config.js +4 -4
  23. package/dist/src/cli/parse-run-config.js.map +1 -1
  24. package/dist/src/cli/progress.d.ts +1 -0
  25. package/dist/src/cli/progress.d.ts.map +1 -1
  26. package/dist/src/cli/progress.js +8 -1
  27. package/dist/src/cli/progress.js.map +1 -1
  28. package/dist/src/eval-core/cache.d.ts +7 -5
  29. package/dist/src/eval-core/cache.d.ts.map +1 -1
  30. package/dist/src/eval-core/cache.js +11 -7
  31. package/dist/src/eval-core/cache.js.map +1 -1
  32. package/dist/src/eval-core/comparability.d.ts +11 -0
  33. package/dist/src/eval-core/comparability.d.ts.map +1 -0
  34. package/dist/src/eval-core/comparability.js +271 -0
  35. package/dist/src/eval-core/comparability.js.map +1 -0
  36. package/dist/src/eval-core/evaluation-execution.d.ts +4 -1
  37. package/dist/src/eval-core/evaluation-execution.d.ts.map +1 -1
  38. package/dist/src/eval-core/evaluation-execution.js +25 -17
  39. package/dist/src/eval-core/evaluation-execution.js.map +1 -1
  40. package/dist/src/eval-core/evaluation-job.d.ts +2 -2
  41. package/dist/src/eval-core/evaluation-job.d.ts.map +1 -1
  42. package/dist/src/eval-core/evaluation-job.js +2 -2
  43. package/dist/src/eval-core/evaluation-job.js.map +1 -1
  44. package/dist/src/eval-core/evaluation-reporting.d.ts +3 -1
  45. package/dist/src/eval-core/evaluation-reporting.d.ts.map +1 -1
  46. package/dist/src/eval-core/evaluation-reporting.js +64 -4
  47. package/dist/src/eval-core/evaluation-reporting.js.map +1 -1
  48. package/dist/src/eval-core/execution-strategy.js +2 -2
  49. package/dist/src/eval-core/execution-strategy.js.map +1 -1
  50. package/dist/src/eval-core/schema.d.ts.map +1 -1
  51. package/dist/src/eval-core/schema.js +13 -0
  52. package/dist/src/eval-core/schema.js.map +1 -1
  53. package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts +111 -0
  54. package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts.map +1 -0
  55. package/dist/src/eval-workflows/batch-evaluation-workflow.js +215 -0
  56. package/dist/src/eval-workflows/batch-evaluation-workflow.js.map +1 -0
  57. package/dist/src/eval-workflows/evaluation-pipeline.d.ts +5 -3
  58. package/dist/src/eval-workflows/evaluation-pipeline.d.ts.map +1 -1
  59. package/dist/src/eval-workflows/evaluation-pipeline.js +6 -5
  60. package/dist/src/eval-workflows/evaluation-pipeline.js.map +1 -1
  61. package/dist/src/eval-workflows/evaluation-preparation.d.ts +8 -6
  62. package/dist/src/eval-workflows/evaluation-preparation.d.ts.map +1 -1
  63. package/dist/src/eval-workflows/evaluation-preparation.js +8 -4
  64. package/dist/src/eval-workflows/evaluation-preparation.js.map +1 -1
  65. package/dist/src/eval-workflows/run-evaluation.d.ts +20 -17
  66. package/dist/src/eval-workflows/run-evaluation.d.ts.map +1 -1
  67. package/dist/src/eval-workflows/run-evaluation.js +25 -15
  68. package/dist/src/eval-workflows/run-evaluation.js.map +1 -1
  69. package/dist/src/executors/claude-cli.d.ts.map +1 -1
  70. package/dist/src/executors/claude-cli.js +11 -6
  71. package/dist/src/executors/claude-cli.js.map +1 -1
  72. package/dist/src/executors/codex-cli-trace.d.ts +10 -0
  73. package/dist/src/executors/codex-cli-trace.d.ts.map +1 -0
  74. package/dist/src/executors/codex-cli-trace.js +123 -0
  75. package/dist/src/executors/codex-cli-trace.js.map +1 -0
  76. package/dist/src/executors/codex-cli.d.ts +18 -0
  77. package/dist/src/executors/codex-cli.d.ts.map +1 -0
  78. package/dist/src/executors/codex-cli.js +254 -0
  79. package/dist/src/executors/codex-cli.js.map +1 -0
  80. package/dist/src/executors/codex-sdk.d.ts +18 -0
  81. package/dist/src/executors/codex-sdk.d.ts.map +1 -0
  82. package/dist/src/executors/codex-sdk.js +214 -0
  83. package/dist/src/executors/codex-sdk.js.map +1 -0
  84. package/dist/src/executors/gemini.d.ts.map +1 -1
  85. package/dist/src/executors/gemini.js +28 -24
  86. package/dist/src/executors/gemini.js.map +1 -1
  87. package/dist/src/executors/index.d.ts.map +1 -1
  88. package/dist/src/executors/index.js +7 -2
  89. package/dist/src/executors/index.js.map +1 -1
  90. package/dist/src/executors/runtime-fingerprint.d.ts +7 -0
  91. package/dist/src/executors/runtime-fingerprint.d.ts.map +1 -0
  92. package/dist/src/executors/runtime-fingerprint.js +277 -0
  93. package/dist/src/executors/runtime-fingerprint.js.map +1 -0
  94. package/dist/src/executors/script.d.ts.map +1 -1
  95. package/dist/src/executors/script.js +47 -55
  96. package/dist/src/executors/script.js.map +1 -1
  97. package/dist/src/executors/shared.d.ts +78 -1
  98. package/dist/src/executors/shared.d.ts.map +1 -1
  99. package/dist/src/executors/shared.js +203 -1
  100. package/dist/src/executors/shared.js.map +1 -1
  101. package/dist/src/grading/assertions.d.ts.map +1 -1
  102. package/dist/src/grading/assertions.js +18 -3
  103. package/dist/src/grading/assertions.js.map +1 -1
  104. package/dist/src/grading/index.d.ts.map +1 -1
  105. package/dist/src/grading/index.js +11 -0
  106. package/dist/src/grading/index.js.map +1 -1
  107. package/dist/src/grading/judge.d.ts.map +1 -1
  108. package/dist/src/grading/judge.js +75 -6
  109. package/dist/src/grading/judge.js.map +1 -1
  110. package/dist/src/inputs/skill-loader.d.ts +1 -1
  111. package/dist/src/inputs/skill-loader.d.ts.map +1 -1
  112. package/dist/src/inputs/skill-loader.js +1 -1
  113. package/dist/src/inputs/skill-loader.js.map +1 -1
  114. package/dist/src/renderer/html-renderer.d.ts +5 -4
  115. package/dist/src/renderer/html-renderer.d.ts.map +1 -1
  116. package/dist/src/renderer/html-renderer.js +217 -93
  117. package/dist/src/renderer/html-renderer.js.map +1 -1
  118. package/dist/src/renderer/layout.d.ts +2 -1
  119. package/dist/src/renderer/layout.d.ts.map +1 -1
  120. package/dist/src/renderer/layout.js +30 -42
  121. package/dist/src/renderer/layout.js.map +1 -1
  122. package/dist/src/renderer/summary.d.ts +3 -3
  123. package/dist/src/renderer/summary.d.ts.map +1 -1
  124. package/dist/src/renderer/summary.js +232 -42
  125. package/dist/src/renderer/summary.js.map +1 -1
  126. package/dist/src/renderer/trends.d.ts.map +1 -1
  127. package/dist/src/renderer/trends.js +5 -3
  128. package/dist/src/renderer/trends.js.map +1 -1
  129. package/dist/src/server/report-server.js +4 -4
  130. package/dist/src/server/report-server.js.map +1 -1
  131. package/dist/src/server/report-store.d.ts +7 -5
  132. package/dist/src/server/report-store.d.ts.map +1 -1
  133. package/dist/src/server/report-store.js +39 -11
  134. package/dist/src/server/report-store.js.map +1 -1
  135. package/dist/src/types/eval.d.ts +4 -2
  136. package/dist/src/types/eval.d.ts.map +1 -1
  137. package/dist/src/types/executor.d.ts +5 -0
  138. package/dist/src/types/executor.d.ts.map +1 -1
  139. package/dist/src/types/judge.d.ts +10 -0
  140. package/dist/src/types/judge.d.ts.map +1 -1
  141. package/dist/src/types/report.d.ts +120 -24
  142. package/dist/src/types/report.d.ts.map +1 -1
  143. package/dist/src/types/storage.d.ts +7 -7
  144. package/dist/src/types/storage.d.ts.map +1 -1
  145. package/package.json +2 -1
  146. package/dist/src/eval-workflows/each-evaluation-workflow.d.ts +0 -153
  147. package/dist/src/eval-workflows/each-evaluation-workflow.d.ts.map +0 -1
  148. package/dist/src/eval-workflows/each-evaluation-workflow.js +0 -178
  149. package/dist/src/eval-workflows/each-evaluation-workflow.js.map +0 -1
  150. package/dist/src/executors/openai-cli.d.ts +0 -3
  151. package/dist/src/executors/openai-cli.d.ts.map +0 -1
  152. package/dist/src/executors/openai-cli.js +0 -60
  153. 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
  /**
@@ -23,7 +24,7 @@ function verdictOneLine(level, lang, treatment, control) {
23
24
  case 'CAUTIOUS': return `${t} 比 ${c} 略好 — 但建议再仔细看,差距很小或某层未达标`;
24
25
  case 'REGRESS': return `${t} 比 ${c} 明显更差 — 不要发布`;
25
26
  case 'NOISE': return `${t} 和 ${c} 没看出明显差别 — 可以多加几条用例再试`;
26
- case 'UNDERPOWERED': return `用例数太少,看不出 ${t} 和 ${c} 的差别 — 多跑几个再看`;
27
+ case 'UNDERPOWERED': return `评测用例数太少,看不出 ${t} 和 ${c} 的差别 — 多跑几个再看`;
27
28
  case 'SOLO': return `只跑了一组,需要加对照组才能对比`;
28
29
  }
29
30
  }
@@ -62,7 +63,7 @@ export function levelTooltip(level, lang) {
62
63
  case 'REGRESS': return '实验组分数显著劣于对照组';
63
64
  case 'CAUTIOUS': return '实验组略优于对照组,但差距小或某层未达 gate';
64
65
  case 'NOISE': return '两组分数差距置信区间跨过 0,统计上分辨不出效果';
65
- case 'UNDERPOWERED': return '用例数太少,需要多加几条再看';
66
+ case 'UNDERPOWERED': return '评测用例数太少,需要多加几条再看';
66
67
  case 'SOLO': return '只跑了一组,没做对比';
67
68
  }
68
69
  }
@@ -82,7 +83,7 @@ export function levelTooltip(level, lang) {
82
83
  // 去掉旧版 headline 和 significance 的内容重复 (perPair[0].headline 是干净的
83
84
  // Δ+CI 核心, headline 拼装版只在 SOLO mode 用).
84
85
  //
85
- // computeVerdict 在 each mode + 历史脏 report 上有 NPE 风险 (顶层 summary
86
+ // computeVerdict 在历史混合批量 report 上有 NPE 风险 (顶层 summary
86
87
  // 缺 variant 数据时 evaluateLayerGates 访问 .avgFactScore 炸). 加 try/catch
87
88
  // 让 renderer 不 crash, 改为静默 skip pill — 这是 v0.21 B.4 的 scope 范围,
88
89
  // verdict.ts/layer-gates.ts 的 defensive 修复留作单独 task.
@@ -305,11 +306,11 @@ export function renderSaturationCurve(saturation, variants, lang) {
305
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>`;
306
307
  return `
307
308
  <h2 style="margin-top:24px">${lang === 'zh' ? '饱和曲线 (Saturation curve)' : 'Saturation curve'}</h2>
308
- <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>
309
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">
310
311
  ${yTicks}
311
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>
312
- <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>
313
314
  ${xTicks}
314
315
  ${seriesParts.join('\n')}
315
316
  </svg>
@@ -361,10 +362,18 @@ export function renderSummaryCards(variants, summary, lang, variance) {
361
362
  // LLM 评价层 cell
362
363
  const judgeCell = renderLayerCell(vd?.byLayer?.judge?.mean, s.avgJudgeScore);
363
364
  // Cost — only show execution cost (judge cost is tool overhead, not skill cost)
365
+ // execCostReported === false 时(如 codex executor)显示「—」并 tooltip 解释,
366
+ // 跟"真的花了 $0(全 cached / 短 prompt)"区分开。
364
367
  const execCost = s.totalExecCostUSD || 0;
368
+ const costReported = s.execCostReported !== false;
365
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';
366
373
  const costCell = hasCost
367
- ? `<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>`)
368
377
  : `<td class="summary-cell"><span style="color:var(--text-muted)">N/A</span></td>`;
369
378
  // Efficiency
370
379
  const effDetails = [];
@@ -443,10 +452,10 @@ export function renderSummaryCards(variants, summary, lang, variance) {
443
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>`;
444
453
  }).join('');
445
454
  const guideModalId = 'guide-six-dims';
446
- const guideTitle = lang === 'zh' ? '如何阅读六维对比?' : 'How to read this 6-dim comparison?';
455
+ const guideTitle = lang === 'zh' ? '如何阅读六维对比?' : 'How to read this six-dimension comparison?';
447
456
  const guideIntro = lang === 'zh'
448
457
  ? '每行是一个实验分组(Variant),六列分别衡量不同维度:'
449
- : 'Each row is a Variant. Six columns measure independent dimensions:';
458
+ : 'Each row is an experiment variant. Six columns measure independent dimensions:';
450
459
  const icon = (emoji) => `<span aria-hidden="true">${emoji}</span>`;
451
460
  // 维度分隔加粗(border-top 2px),让六维的视觉边界更明显。sub 缩进从 28 收到 22。
452
461
  const dim = 'style="padding:12px 0 4px;border-top:2px solid var(--border);color:var(--text-primary);font-weight:600"';
@@ -456,10 +465,10 @@ export function renderSummaryCards(variants, summary, lang, variance) {
456
465
  const sub = 'style="padding:2px 0 2px 22px;font-size:12px;color:var(--text-secondary);font-weight:500"';
457
466
  const subDesc = 'style="padding:2px 0;font-size:12px;color:var(--text-muted)"';
458
467
  const guideRows = lang === 'zh' ? `
459
- <tr><td ${dimFirst}>${icon('📋')} <strong>事实</strong></td><td ${dimFirstDesc}>模型的输出说得对不对(事实声明层面)。靠规则断言判:关键词是否出现、JSON 格式是否合法等,答错了直接不给分。</td></tr>
460
- <tr><td ${dim}>${icon('🛠️')} <strong>行为</strong></td><td ${dimDesc}>模型做事的过程有没有走对路。靠规则断言判:该调的工具有没有调、有没有超过轮次/成本上限。</td></tr>
461
- <tr><td ${dim}>${icon('💬')} <strong>LLM 评价</strong></td><td ${dimDesc}>请一个 LLM 当评委,让它读被测模型的输出内容,按预先写好的评分规则(英文叫 rubric)打个 1-5 分。主观但能抓到规则断言判不了的"整体好不好"——比如回答是否清晰、有没有答非所问。</td></tr>
462
- <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>
463
472
  <tr><td ${dim}>${icon('⚡')} <strong>效率</strong></td><td ${dimDesc}>一次评测平均跑多久;附带轮次数和工具调用次数。</td></tr>
464
473
  <tr><td ${dim}>${icon('🛡️')} <strong>稳定性</strong></td><td ${dimDesc}>同一份测试跑很多次,分数抖不抖。抖得越少越稳定。<strong>跑一次看不出稳定性</strong>——至少要 <code>--repeat ≥ 2</code>,不然显示"—"。</td></tr>
465
474
  <tr><td ${sub}>稳定 / 较稳 / 波动大</td><td ${subDesc}>分数波动比例 &lt;5% = 稳定 · 5~15% = 一般 · &gt;15% = 波动大</td></tr>
@@ -467,10 +476,10 @@ export function renderSummaryCards(variants, summary, lang, variance) {
467
476
  <tr><td ${sub}>CV</td><td ${subDesc}>分数抖动幅度占平均分的比例(例:CV 2% = 分数波动大约是平均分的 2%)</td></tr>
468
477
  <tr><td ${sub}>95% CI</td><td ${subDesc}>如果跑无数次求平均,真实平均分有 95% 概率落在这个范围里——范围越窄,这次测出的均值越可信</td></tr>
469
478
  ` : `
470
- <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>
471
- <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>
472
- <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>
473
- <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>
474
483
  <tr><td ${dim}>${icon('⚡')} <strong>Efficiency</strong></td><td ${dimDesc}>Average time per evaluation, plus turn counts and tool call stats.</td></tr>
475
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>
476
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>
@@ -479,7 +488,7 @@ export function renderSummaryCards(variants, summary, lang, variance) {
479
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>
480
489
  `;
481
490
  return `
482
- <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>
483
492
  <div id="${guideModalId}" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="${guideModalId}-title" onclick="if(event.target===this)closeModal('${guideModalId}')">
484
493
  <div class="modal-content">
485
494
  <div class="modal-header">
@@ -535,7 +544,7 @@ function renderJudgeAgreementBlock(variants, summary, lang) {
535
544
  <th>${t('judgeModelsLabel', lang)}</th>
536
545
  <th title="${t('pearsonDesc', lang)}">${t('pearsonLabel', lang)}</th>
537
546
  <th title="${t('madDesc', lang)}">${t('madLabel', lang)}</th>
538
- <th>${lang === 'zh' ? '用例数' : 'Samples'}</th>
547
+ <th>${lang === 'zh' ? '评测用例数' : 'Samples'}</th>
539
548
  <th>${lang === 'zh' ? 'Judge 对数' : 'Pairs'}</th>
540
549
  </tr></thead>
541
550
  <tbody>${rows}</tbody>
@@ -669,9 +678,12 @@ function getVariantMetricMean(variance, variant, key) {
669
678
  return v.mean;
670
679
  return v.byMetric?.[key]?.mean ?? null;
671
680
  }
672
- export function renderVarianceComparisons(variance, lang, layeredStatsOpen = false) {
681
+ export function renderVarianceComparisons(variance, lang, layeredStatsOpen = false, summary) {
673
682
  if (!variance || !variance.comparisons || variance.comparisons.length === 0)
674
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);
675
687
  const modalId = 'guide-variance-comparisons';
676
688
  const title = lang === 'zh' ? '方差与显著性' : 'Variance & Significance';
677
689
  const guideTitle = lang === 'zh' ? '如何阅读这张表?' : 'How to read this table?';
@@ -817,6 +829,9 @@ export function renderVarianceComparisons(variance, lang, layeredStatsOpen = fal
817
829
  // Collect available metric rows for this comparison
818
830
  const availableMetrics = [];
819
831
  for (const cfg of METRIC_CONFIGS) {
832
+ // cost 行:任一 variant 不报 cost 时跳过(数据是占位 0,显示"持平"误导)
833
+ if (cfg.key === 'cost' && !allCostReported)
834
+ continue;
820
835
  const m = pickMetricFromComparison(comp, cfg.key);
821
836
  if (m)
822
837
  availableMetrics.push({ cfg, metric: m });
@@ -851,7 +866,7 @@ export function renderVarianceComparisons(variance, lang, layeredStatsOpen = fal
851
866
  { label: '显著性', desc: 't 检验结论,基于 p<0.05 阈值。回答"差异真不真",和效应量"差多大"互补' },
852
867
  { label: 'p 值', desc: '假设真的没差异时,观察到当前差距的概率。越小越可信。0.05 只是约定阈值', sub: true },
853
868
  { label: 't 值', desc: '均值差 ÷ 估计误差,需配合 df 和效应量解读,不能单独看', sub: true },
854
- { 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 },
855
870
  ];
856
871
  const glossaryEn = [
857
872
  { label: 'Gap', desc: 'Cross-run mean winner + absolute difference (raw units)' },
@@ -934,7 +949,7 @@ function severityDot(severity) {
934
949
  };
935
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>`;
936
951
  }
937
- function renderSummaryStructured(summary) {
952
+ function renderSummaryStructured(summary, lang) {
938
953
  const markerRegex = /【([^】]+)】/g;
939
954
  const markers = [];
940
955
  let match;
@@ -949,9 +964,17 @@ function renderSummaryStructured(summary) {
949
964
  if (sections.length === 0) {
950
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>`;
951
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
+ };
952
975
  const sectionHtml = sections.map((section) => `
953
976
  <div style="display:flex;gap:12px;align-items:baseline">
954
- <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>
955
978
  <span style="color:var(--text-secondary);font-size:13px;line-height:1.7">${e(section.content)}</span>
956
979
  </div>`).join('');
957
980
  return `
@@ -959,24 +982,188 @@ function renderSummaryStructured(summary) {
959
982
  ${sectionHtml}
960
983
  </div>`;
961
984
  }
962
- 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;
963
1150
  if (!analysis)
964
1151
  return '';
965
- const { insights, suggestions } = analysis;
966
- if ((!insights || insights.length === 0) && (!suggestions || suggestions.length === 0))
1152
+ const { insights } = analysis;
1153
+ if ((!insights || insights.length === 0) && !report)
967
1154
  return '';
968
1155
  const issues = (insights || []).filter((insight) => !isConclusion(insight));
969
1156
  const issueLabel = lang === 'zh' ? '问题与建议' : 'Issues & Suggestions';
970
- const safeSuggestions = suggestions || [];
1157
+ const safeSuggestions = issues.map((issue) => localizedSuggestion(issue, lang));
971
1158
  let issuesHtml = '';
972
- if (issues.length > 0 || safeSuggestions.length > 0) {
973
- const maxRows = Math.max(issues.length, safeSuggestions.length);
1159
+ if (issues.length > 0) {
1160
+ const maxRows = issues.length;
974
1161
  const rows = [];
975
1162
  for (let i = 0; i < maxRows; i++) {
976
1163
  const issue = issues[i];
977
1164
  const suggestion = safeSuggestions[i];
978
1165
  const issueContent = issue
979
- ? `${severityDot(issue.severity)}<span>${e(issue.message)}</span>`
1166
+ ? `${severityDot(issue.severity)}<span>${e(localizedInsightMessage(issue, report, lang))}</span>`
980
1167
  : '';
981
1168
  const suggestionContent = suggestion
982
1169
  ? e(suggestion)
@@ -1000,9 +1187,12 @@ export function renderAnalysis(analysis, lang) {
1000
1187
  </div>
1001
1188
  `;
1002
1189
  }
1003
- const summaryHtml = analysis.summary
1004
- ? renderSummaryStructured(analysis.summary)
1190
+ const generatedSummary = report ? generateAnalysisSummary(report, lang) : undefined;
1191
+ const summaryHtml = generatedSummary
1192
+ ? renderSummaryStructured(generatedSummary, lang)
1005
1193
  : '';
1194
+ if (!summaryHtml && !issuesHtml)
1195
+ return '';
1006
1196
  return `
1007
1197
  <h2 data-i18n="autoAnalysis">${t('autoAnalysis', lang)}</h2>
1008
1198
  ${summaryHtml}
@@ -1155,8 +1345,8 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1155
1345
  return '';
1156
1346
  const title = lang === 'zh' ? '本次测评:测试用例 × 知识库' : 'This Evaluation: Test Set × Knowledge Base';
1157
1347
  const desc = lang === 'zh'
1158
- ? '展示本次测评用例和知识库的交互画像——用到哪些知识(使用情况)· 哪些知识想找但没找到或模型表达不确定(盲区)。'
1159
- : '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).';
1160
1350
  const readHint = lang === 'zh'
1161
1351
  ? '💡 读表:两者同时高 → 知识库内容有问题(有文件但答不出)· 同时低 → 测评用例太浅(没触到复杂场景)· 使用高 + 盲区低 → 理想但警惕用例驯化'
1162
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';
@@ -1167,7 +1357,7 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1167
1357
  ]));
1168
1358
  const signalTypeLabels = {
1169
1359
  failed_search: { zh: '搜索未命中', en: 'Search miss' },
1170
- explicit_marker: { zh: '模型标记缺口', en: 'Model-flagged gap' },
1360
+ explicit_marker: { zh: '任务执行模型标记缺口', en: 'Execution model flag' },
1171
1361
  hedging: { zh: '表达不确定', en: 'Hedging' },
1172
1362
  repeated_failure: { zh: '反复未命中', en: 'Repeated miss' },
1173
1363
  };
@@ -1261,7 +1451,7 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1261
1451
  const softShare = pct - weightedPct;
1262
1452
  const weightedHint = lang === 'zh'
1263
1453
  ? (softShare >= 10
1264
- ? `<strong>实际盲区 ${weightedPct}%</strong> · 另 ${softShare}% 为模型表达不确定(软信号,建议对照清单复核)`
1454
+ ? `<strong>实际盲区 ${weightedPct}%</strong> · 另 ${softShare}% 为任务执行模型表达不确定(软信号,建议对照清单复核)`
1265
1455
  : `<strong>实际盲区 ${weightedPct}%</strong> · 主要来自确定的搜索未命中`)
1266
1456
  : (softShare >= 10
1267
1457
  ? `<strong>real gaps ${weightedPct}%</strong> · another ${softShare}% is hedging (review list below)`
@@ -1284,7 +1474,7 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1284
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>`
1285
1475
  : '';
1286
1476
  const detailsLabel = lang === 'zh'
1287
- ? `展开 ${gap.signals.length} 条证据(按严重度上色: 红=确定 / 黄=模型自述 / 灰=犹豫)`
1477
+ ? `展开 ${gap.signals.length} 条证据(按严重度上色: 红=确定 / 黄=执行模型自述 / 灰=犹豫)`
1288
1478
  : `Show all ${gap.signals.length} evidence items (red=confirmed · yellow=self-flagged · gray=hedging)`;
1289
1479
  gapInner = `
1290
1480
  <div class="ki-col-header">
@@ -1294,7 +1484,7 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1294
1484
  <div class="ki-bar" role="progressbar" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100" aria-label="${e(barLabel)}">
1295
1485
  <div class="ki-bar-fill" style="width:${barW}%;background:${pctColor}"></div>
1296
1486
  </div>
1297
- <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>
1298
1488
  <div style="font-size:var(--fs-detail);color:var(--text-secondary);margin-bottom:8px">${weightedHint}</div>
1299
1489
  ${typeBadges ? `<div>${typeBadges}</div>` : ''}
1300
1490
  ${inventory ? `<details class="ki-details"><summary>${detailsLabel}</summary>
@@ -1343,11 +1533,11 @@ export function renderGapSection(gapReports, lang) {
1343
1533
  return '';
1344
1534
  const title = lang === 'zh' ? '本次测评的知识盲区' : 'Knowledge gaps in this evaluation';
1345
1535
  const desc = lang === 'zh'
1346
- ? '本次测评中,哪些知识想找但没找到、或模型表达不确定。数字高不一定代表知识库不全——也可能是测试用例问的领域知识库未覆盖。'
1347
- : '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.';
1348
1538
  const signalTypeLabels = {
1349
1539
  failed_search: { zh: '搜索未命中', en: 'Search miss' },
1350
- explicit_marker: { zh: '模型标记缺口', en: 'Model-flagged gap' },
1540
+ explicit_marker: { zh: '任务执行模型标记缺口', en: 'Execution model flag' },
1351
1541
  hedging: { zh: '表达不确定', en: 'Hedging' },
1352
1542
  repeated_failure: { zh: '反复未命中', en: 'Repeated miss' },
1353
1543
  };
@@ -1364,7 +1554,7 @@ export function renderGapSection(gapReports, lang) {
1364
1554
  const softSignalShare = pct - weightedPct;
1365
1555
  const weightedHint = lang === 'zh'
1366
1556
  ? (softSignalShare >= 10
1367
- ? `<strong>实际盲区 ${weightedPct}%</strong> · 另外 ${softSignalShare}% 为模型表达不确定(软信号,建议对照右侧清单复核)`
1557
+ ? `<strong>实际盲区 ${weightedPct}%</strong> · 另外 ${softSignalShare}% 为任务执行模型表达不确定(软信号,建议对照右侧清单复核)`
1368
1558
  : `<strong>实际盲区 ${weightedPct}%</strong> · 主要来自确定的搜索未命中`)
1369
1559
  : (softSignalShare >= 10
1370
1560
  ? `<strong>real gaps ${weightedPct}%</strong> · another ${softSignalShare}% is hedging (soft signals — review the list on the right)`
@@ -1410,7 +1600,7 @@ export function renderGapSection(gapReports, lang) {
1410
1600
  <div style="width:${barW}%;height:100%;background:${pctColor};border-radius:4px"></div>
1411
1601
  </div>
1412
1602
  <div style="font-size:var(--fs-detail);color:var(--text-muted);margin-bottom:4px">
1413
- ${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'}
1414
1604
  </div>
1415
1605
  <div style="font-size:var(--fs-detail);color:var(--text-secondary);margin-bottom:10px">${weightedHint}</div>
1416
1606
  ${typeBadges ? `<div style="margin-bottom:10px">${typeBadges}</div>` : ''}