oh-my-knowledge 0.28.0 → 0.30.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 (281) hide show
  1. package/README.md +49 -3
  2. package/README.zh.md +49 -3
  3. package/dist/src/analysis/failure-clusterer.js +1 -1
  4. package/dist/src/analysis/failure-clusterer.js.map +1 -1
  5. package/dist/src/analysis/gap-analyzer.d.ts +8 -1
  6. package/dist/src/analysis/gap-analyzer.d.ts.map +1 -1
  7. package/dist/src/analysis/gap-analyzer.js +65 -33
  8. package/dist/src/analysis/gap-analyzer.js.map +1 -1
  9. package/dist/src/analysis/report-diagnostics.js +2 -0
  10. package/dist/src/analysis/report-diagnostics.js.map +1 -1
  11. package/dist/src/analysis/sample-diagnostics.js +14 -14
  12. package/dist/src/analysis/sample-diagnostics.js.map +1 -1
  13. package/dist/src/authoring/evolver.d.ts +7 -1
  14. package/dist/src/authoring/evolver.d.ts.map +1 -1
  15. package/dist/src/authoring/evolver.js +8 -5
  16. package/dist/src/authoring/evolver.js.map +1 -1
  17. package/dist/src/authoring/generator.d.ts +21 -23
  18. package/dist/src/authoring/generator.d.ts.map +1 -1
  19. package/dist/src/authoring/generator.js +621 -40
  20. package/dist/src/authoring/generator.js.map +1 -1
  21. package/dist/src/authoring/sample-fixer.d.ts +45 -0
  22. package/dist/src/authoring/sample-fixer.d.ts.map +1 -0
  23. package/dist/src/authoring/sample-fixer.js +186 -0
  24. package/dist/src/authoring/sample-fixer.js.map +1 -0
  25. package/dist/src/cli/commands/evolve.d.ts.map +1 -1
  26. package/dist/src/cli/commands/evolve.js +17 -0
  27. package/dist/src/cli/commands/evolve.js.map +1 -1
  28. package/dist/src/cli/commands/observe.d.ts.map +1 -1
  29. package/dist/src/cli/commands/observe.js +170 -0
  30. package/dist/src/cli/commands/observe.js.map +1 -1
  31. package/dist/src/cli/commands/registry.d.ts.map +1 -1
  32. package/dist/src/cli/commands/registry.js +9 -1
  33. package/dist/src/cli/commands/registry.js.map +1 -1
  34. package/dist/src/cli/commands/sample.d.ts +13 -0
  35. package/dist/src/cli/commands/sample.d.ts.map +1 -1
  36. package/dist/src/cli/commands/sample.js +307 -13
  37. package/dist/src/cli/commands/sample.js.map +1 -1
  38. package/dist/src/cli/commands/studio.d.ts.map +1 -1
  39. package/dist/src/cli/commands/studio.js +8 -0
  40. package/dist/src/cli/commands/studio.js.map +1 -1
  41. package/dist/src/cli/i18n-dict.d.ts +1 -1
  42. package/dist/src/cli/i18n-dict.d.ts.map +1 -1
  43. package/dist/src/cli/i18n-dict.js +189 -17
  44. package/dist/src/cli/i18n-dict.js.map +1 -1
  45. package/dist/src/cli/parse-run-config.d.ts +14 -2
  46. package/dist/src/cli/parse-run-config.d.ts.map +1 -1
  47. package/dist/src/cli/parse-run-config.js +62 -10
  48. package/dist/src/cli/parse-run-config.js.map +1 -1
  49. package/dist/src/doctor/health/builtin-dimensions.d.ts.map +1 -1
  50. package/dist/src/doctor/health/builtin-dimensions.js +20 -23
  51. package/dist/src/doctor/health/builtin-dimensions.js.map +1 -1
  52. package/dist/src/doctor/health/composer.d.ts.map +1 -1
  53. package/dist/src/doctor/health/composer.js +7 -3
  54. package/dist/src/doctor/health/composer.js.map +1 -1
  55. package/dist/src/doctor/health/dimension-spec.d.ts +1 -0
  56. package/dist/src/doctor/health/dimension-spec.d.ts.map +1 -1
  57. package/dist/src/doctor/health/parser.d.ts.map +1 -1
  58. package/dist/src/doctor/health/parser.js +1 -0
  59. package/dist/src/doctor/health/parser.js.map +1 -1
  60. package/dist/src/doctor/health/prompt-builder.js +25 -17
  61. package/dist/src/doctor/health/prompt-builder.js.map +1 -1
  62. package/dist/src/doctor/html-renderer.d.ts.map +1 -1
  63. package/dist/src/doctor/html-renderer.js +76 -66
  64. package/dist/src/doctor/html-renderer.js.map +1 -1
  65. package/dist/src/doctor/rules.d.ts.map +1 -1
  66. package/dist/src/doctor/rules.js +32 -0
  67. package/dist/src/doctor/rules.js.map +1 -1
  68. package/dist/src/eval-core/cache.d.ts +16 -2
  69. package/dist/src/eval-core/cache.d.ts.map +1 -1
  70. package/dist/src/eval-core/cache.js +86 -15
  71. package/dist/src/eval-core/cache.js.map +1 -1
  72. package/dist/src/eval-core/evaluation-execution.d.ts +11 -1
  73. package/dist/src/eval-core/evaluation-execution.d.ts.map +1 -1
  74. package/dist/src/eval-core/evaluation-execution.js +78 -6
  75. package/dist/src/eval-core/evaluation-execution.js.map +1 -1
  76. package/dist/src/eval-core/evaluation-job.d.ts +2 -1
  77. package/dist/src/eval-core/evaluation-job.d.ts.map +1 -1
  78. package/dist/src/eval-core/evaluation-job.js +2 -1
  79. package/dist/src/eval-core/evaluation-job.js.map +1 -1
  80. package/dist/src/eval-core/evaluation-reporting.d.ts +7 -0
  81. package/dist/src/eval-core/evaluation-reporting.d.ts.map +1 -1
  82. package/dist/src/eval-core/evaluation-reporting.js +19 -2
  83. package/dist/src/eval-core/evaluation-reporting.js.map +1 -1
  84. package/dist/src/eval-core/execution-strategy.d.ts +1 -1
  85. package/dist/src/eval-core/execution-strategy.d.ts.map +1 -1
  86. package/dist/src/eval-core/execution-strategy.js +12 -1
  87. package/dist/src/eval-core/execution-strategy.js.map +1 -1
  88. package/dist/src/eval-core/mock-hook.cjs +203 -0
  89. package/dist/src/eval-core/mocks-runtime.d.ts +96 -0
  90. package/dist/src/eval-core/mocks-runtime.d.ts.map +1 -0
  91. package/dist/src/eval-core/mocks-runtime.js +323 -0
  92. package/dist/src/eval-core/mocks-runtime.js.map +1 -0
  93. package/dist/src/eval-core/schema.d.ts.map +1 -1
  94. package/dist/src/eval-core/schema.js +18 -5
  95. package/dist/src/eval-core/schema.js.map +1 -1
  96. package/dist/src/eval-core/task-planner.d.ts +9 -1
  97. package/dist/src/eval-core/task-planner.d.ts.map +1 -1
  98. package/dist/src/eval-core/task-planner.js +40 -1
  99. package/dist/src/eval-core/task-planner.js.map +1 -1
  100. package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts +5 -1
  101. package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts.map +1 -1
  102. package/dist/src/eval-workflows/batch-evaluation-workflow.js +2 -1
  103. package/dist/src/eval-workflows/batch-evaluation-workflow.js.map +1 -1
  104. package/dist/src/eval-workflows/evaluation-pipeline.d.ts +22 -1
  105. package/dist/src/eval-workflows/evaluation-pipeline.d.ts.map +1 -1
  106. package/dist/src/eval-workflows/evaluation-pipeline.js +39 -12
  107. package/dist/src/eval-workflows/evaluation-pipeline.js.map +1 -1
  108. package/dist/src/eval-workflows/evaluation-preparation.d.ts +5 -0
  109. package/dist/src/eval-workflows/evaluation-preparation.d.ts.map +1 -1
  110. package/dist/src/eval-workflows/evaluation-preparation.js +3 -1
  111. package/dist/src/eval-workflows/evaluation-preparation.js.map +1 -1
  112. package/dist/src/eval-workflows/run-evaluation.d.ts +12 -2
  113. package/dist/src/eval-workflows/run-evaluation.d.ts.map +1 -1
  114. package/dist/src/eval-workflows/run-evaluation.js +17 -5
  115. package/dist/src/eval-workflows/run-evaluation.js.map +1 -1
  116. package/dist/src/executors/claude-cli.d.ts +1 -1
  117. package/dist/src/executors/claude-cli.d.ts.map +1 -1
  118. package/dist/src/executors/claude-cli.js +57 -6
  119. package/dist/src/executors/claude-cli.js.map +1 -1
  120. package/dist/src/executors/claude-sdk-trace.d.ts.map +1 -1
  121. package/dist/src/executors/claude-sdk-trace.js +10 -4
  122. package/dist/src/executors/claude-sdk-trace.js.map +1 -1
  123. package/dist/src/executors/claude-sdk.d.ts +1 -1
  124. package/dist/src/executors/claude-sdk.d.ts.map +1 -1
  125. package/dist/src/executors/claude-sdk.js +25 -2
  126. package/dist/src/executors/claude-sdk.js.map +1 -1
  127. package/dist/src/executors/shared.d.ts +1 -1
  128. package/dist/src/executors/shared.d.ts.map +1 -1
  129. package/dist/src/executors/shared.js +6 -1
  130. package/dist/src/executors/shared.js.map +1 -1
  131. package/dist/src/grading/assertions.d.ts +5 -0
  132. package/dist/src/grading/assertions.d.ts.map +1 -1
  133. package/dist/src/grading/assertions.js +27 -1
  134. package/dist/src/grading/assertions.js.map +1 -1
  135. package/dist/src/grading/debias-validate.js +4 -4
  136. package/dist/src/grading/debias-validate.js.map +1 -1
  137. package/dist/src/grading/diagnostic.d.ts +40 -0
  138. package/dist/src/grading/diagnostic.d.ts.map +1 -0
  139. package/dist/src/grading/diagnostic.js +450 -0
  140. package/dist/src/grading/diagnostic.js.map +1 -0
  141. package/dist/src/grading/gold-cli.js +2 -2
  142. package/dist/src/grading/gold-cli.js.map +1 -1
  143. package/dist/src/grading/index.d.ts +5 -0
  144. package/dist/src/grading/index.d.ts.map +1 -1
  145. package/dist/src/grading/judge.d.ts.map +1 -1
  146. package/dist/src/grading/judge.js +54 -2
  147. package/dist/src/grading/judge.js.map +1 -1
  148. package/dist/src/grading/layered-scores.d.ts.map +1 -1
  149. package/dist/src/grading/layered-scores.js +1 -0
  150. package/dist/src/grading/layered-scores.js.map +1 -1
  151. package/dist/src/inputs/eval-config.js +19 -0
  152. package/dist/src/inputs/eval-config.js.map +1 -1
  153. package/dist/src/inputs/load-samples.d.ts +22 -2
  154. package/dist/src/inputs/load-samples.d.ts.map +1 -1
  155. package/dist/src/inputs/load-samples.js +193 -22
  156. package/dist/src/inputs/load-samples.js.map +1 -1
  157. package/dist/src/inputs/skill-loader.d.ts.map +1 -1
  158. package/dist/src/inputs/skill-loader.js +11 -3
  159. package/dist/src/inputs/skill-loader.js.map +1 -1
  160. package/dist/src/observability/experience.d.ts +260 -0
  161. package/dist/src/observability/experience.d.ts.map +1 -0
  162. package/dist/src/observability/experience.js +1233 -0
  163. package/dist/src/observability/experience.js.map +1 -0
  164. package/dist/src/observability/inbox-view-model.d.ts +27 -0
  165. package/dist/src/observability/inbox-view-model.d.ts.map +1 -0
  166. package/dist/src/observability/inbox-view-model.js +135 -0
  167. package/dist/src/observability/inbox-view-model.js.map +1 -0
  168. package/dist/src/observability/inbox.d.ts +125 -0
  169. package/dist/src/observability/inbox.d.ts.map +1 -0
  170. package/dist/src/observability/inbox.js +741 -0
  171. package/dist/src/observability/inbox.js.map +1 -0
  172. package/dist/src/observability/problem-patterns.d.ts +51 -0
  173. package/dist/src/observability/problem-patterns.d.ts.map +1 -0
  174. package/dist/src/observability/problem-patterns.js +205 -0
  175. package/dist/src/observability/problem-patterns.js.map +1 -0
  176. package/dist/src/observability/review-state.d.ts +54 -0
  177. package/dist/src/observability/review-state.d.ts.map +1 -0
  178. package/dist/src/observability/review-state.js +131 -0
  179. package/dist/src/observability/review-state.js.map +1 -0
  180. package/dist/src/observability/skill-chain-advisories.d.ts +25 -0
  181. package/dist/src/observability/skill-chain-advisories.d.ts.map +1 -0
  182. package/dist/src/observability/skill-chain-advisories.js +69 -0
  183. package/dist/src/observability/skill-chain-advisories.js.map +1 -0
  184. package/dist/src/observability/skill-chain.d.ts +61 -0
  185. package/dist/src/observability/skill-chain.d.ts.map +1 -0
  186. package/dist/src/observability/skill-chain.js +233 -0
  187. package/dist/src/observability/skill-chain.js.map +1 -0
  188. package/dist/src/observability/text-signals.d.ts +7 -0
  189. package/dist/src/observability/text-signals.d.ts.map +1 -0
  190. package/dist/src/observability/text-signals.js +22 -0
  191. package/dist/src/observability/text-signals.js.map +1 -0
  192. package/dist/src/observability/trace-adapter.d.ts +12 -64
  193. package/dist/src/observability/trace-adapter.d.ts.map +1 -1
  194. package/dist/src/observability/trace-adapter.js +9 -355
  195. package/dist/src/observability/trace-adapter.js.map +1 -1
  196. package/dist/src/observability/trace-attribution.d.ts +56 -0
  197. package/dist/src/observability/trace-attribution.d.ts.map +1 -0
  198. package/dist/src/observability/trace-attribution.js +200 -0
  199. package/dist/src/observability/trace-attribution.js.map +1 -0
  200. package/dist/src/observability/trace-segmenter.d.ts +52 -0
  201. package/dist/src/observability/trace-segmenter.d.ts.map +1 -0
  202. package/dist/src/observability/trace-segmenter.js +320 -0
  203. package/dist/src/observability/trace-segmenter.js.map +1 -0
  204. package/dist/src/observability/trace-source.d.ts +99 -0
  205. package/dist/src/observability/trace-source.d.ts.map +1 -0
  206. package/dist/src/observability/trace-source.js +492 -0
  207. package/dist/src/observability/trace-source.js.map +1 -0
  208. package/dist/src/renderer/html-renderer.d.ts.map +1 -1
  209. package/dist/src/renderer/html-renderer.js +324 -218
  210. package/dist/src/renderer/html-renderer.js.map +1 -1
  211. package/dist/src/renderer/layout.d.ts.map +1 -1
  212. package/dist/src/renderer/layout.js +155 -71
  213. package/dist/src/renderer/layout.js.map +1 -1
  214. package/dist/src/renderer/observation-inbox-renderer.d.ts +4 -0
  215. package/dist/src/renderer/observation-inbox-renderer.d.ts.map +1 -0
  216. package/dist/src/renderer/observation-inbox-renderer.js +5376 -0
  217. package/dist/src/renderer/observation-inbox-renderer.js.map +1 -0
  218. package/dist/src/renderer/skill-detail-renderer.d.ts +15 -0
  219. package/dist/src/renderer/skill-detail-renderer.d.ts.map +1 -0
  220. package/dist/src/renderer/skill-detail-renderer.js +1234 -0
  221. package/dist/src/renderer/skill-detail-renderer.js.map +1 -0
  222. package/dist/src/renderer/skill-list-renderer.d.ts +4 -0
  223. package/dist/src/renderer/skill-list-renderer.d.ts.map +1 -0
  224. package/dist/src/renderer/skill-list-renderer.js +327 -0
  225. package/dist/src/renderer/skill-list-renderer.js.map +1 -0
  226. package/dist/src/renderer/summary.d.ts +17 -0
  227. package/dist/src/renderer/summary.d.ts.map +1 -1
  228. package/dist/src/renderer/summary.js +346 -74
  229. package/dist/src/renderer/summary.js.map +1 -1
  230. package/dist/src/renderer/table.d.ts.map +1 -1
  231. package/dist/src/renderer/table.js +213 -167
  232. package/dist/src/renderer/table.js.map +1 -1
  233. package/dist/src/renderer/test-view.d.ts +66 -0
  234. package/dist/src/renderer/test-view.d.ts.map +1 -0
  235. package/dist/src/renderer/test-view.js +905 -0
  236. package/dist/src/renderer/test-view.js.map +1 -0
  237. package/dist/src/renderer/trends.d.ts.map +1 -1
  238. package/dist/src/renderer/trends.js +3 -1
  239. package/dist/src/renderer/trends.js.map +1 -1
  240. package/dist/src/server/report-server.d.ts +6 -1
  241. package/dist/src/server/report-server.d.ts.map +1 -1
  242. package/dist/src/server/report-server.js +182 -6
  243. package/dist/src/server/report-server.js.map +1 -1
  244. package/dist/src/server/report-store.d.ts.map +1 -1
  245. package/dist/src/server/report-store.js +33 -1
  246. package/dist/src/server/report-store.js.map +1 -1
  247. package/dist/src/server/skill-index.d.ts +77 -0
  248. package/dist/src/server/skill-index.d.ts.map +1 -0
  249. package/dist/src/server/skill-index.js +331 -0
  250. package/dist/src/server/skill-index.js.map +1 -0
  251. package/dist/src/server/skill-insights.d.ts +92 -0
  252. package/dist/src/server/skill-insights.d.ts.map +1 -0
  253. package/dist/src/server/skill-insights.js +676 -0
  254. package/dist/src/server/skill-insights.js.map +1 -0
  255. package/dist/src/shared/hard-rules.d.ts +40 -0
  256. package/dist/src/shared/hard-rules.d.ts.map +1 -0
  257. package/dist/src/shared/hard-rules.js +200 -0
  258. package/dist/src/shared/hard-rules.js.map +1 -0
  259. package/dist/src/shared/time.d.ts +2 -0
  260. package/dist/src/shared/time.d.ts.map +1 -0
  261. package/dist/src/shared/time.js +10 -0
  262. package/dist/src/shared/time.js.map +1 -0
  263. package/dist/src/shared/tool-search.d.ts +9 -0
  264. package/dist/src/shared/tool-search.d.ts.map +1 -0
  265. package/dist/src/shared/tool-search.js +104 -0
  266. package/dist/src/shared/tool-search.js.map +1 -0
  267. package/dist/src/types/eval.d.ts +83 -0
  268. package/dist/src/types/eval.d.ts.map +1 -1
  269. package/dist/src/types/executor.d.ts +47 -0
  270. package/dist/src/types/executor.d.ts.map +1 -1
  271. package/dist/src/types/judge.d.ts +76 -0
  272. package/dist/src/types/judge.d.ts.map +1 -1
  273. package/dist/src/types/judge.js +3 -1
  274. package/dist/src/types/judge.js.map +1 -1
  275. package/dist/src/types/report.d.ts +63 -3
  276. package/dist/src/types/report.d.ts.map +1 -1
  277. package/dist/src/util/safe-slice.d.ts +23 -0
  278. package/dist/src/util/safe-slice.d.ts.map +1 -0
  279. package/dist/src/util/safe-slice.js +33 -0
  280. package/dist/src/util/safe-slice.js.map +1 -0
  281. package/package.json +5 -4
@@ -20,11 +20,11 @@ function verdictOneLine(level, lang, treatment, control) {
20
20
  const c = control ?? (lang === 'zh' ? '对照组' : 'control');
21
21
  if (lang === 'zh') {
22
22
  switch (level) {
23
- case 'PROGRESS': return `${t} 比 ${c} 明显更好 — 可以发布`;
24
- case 'CAUTIOUS': return `${t} 比 ${c} 略好 — 但建议再仔细看,差距很小或某层未达标`;
25
- case 'REGRESS': return `${t} 比 ${c} 明显更差 — 不要发布`;
26
- case 'NOISE': return `${t} 和 ${c} 没看出明显差别 — 可以多加几条用例再试`;
27
- case 'UNDERPOWERED': return `评测用例数太少,看不出 ${t} 和 ${c} 的差别 — 多跑几个再看`;
23
+ case 'PROGRESS': return `${t} 比 ${c} 明显更好——可以发布`;
24
+ case 'CAUTIOUS': return `${t} 比 ${c} 略好——但建议再仔细看,差距很小或某层未达标`;
25
+ case 'REGRESS': return `${t} 比 ${c} 明显更差——不要发布`;
26
+ case 'NOISE': return `${t} 和 ${c} 没看出明显差别——可以多加几条用例再试`;
27
+ case 'UNDERPOWERED': return `评测用例数太少,看不出 ${t} 和 ${c} 的差别——多跑几个再看`;
28
28
  case 'SOLO': return `只跑了一组,需要加对照组才能对比`;
29
29
  }
30
30
  }
@@ -76,17 +76,38 @@ export function levelTooltip(level, lang) {
76
76
  case 'SOLO': return 'Single variant — no comparison';
77
77
  }
78
78
  }
79
- // v0.21 B.4 — Verdict pill 重写. 行业领先 status banner 三段式:
80
- // 1. Banner: 强信号(色 + 词) + 核心 metric (Δ + CI) + meta (pair / N / α)
81
- // 2. CTA: emoji + 加粗 action + 详情 (从 shipRecommendation 拆)
82
- // 3. Layers strip: fact/behavior/judge 一行带过, 不堆 bullets
83
- // 去掉旧版 headline 和 significance 的内容重复 (perPair[0].headline 是干净的
84
- // Δ+CI 核心, headline 拼装版只在 SOLO mode 用).
85
- //
86
- // computeVerdict 在历史混合批量 report 上有 NPE 风险 (顶层 summary
87
- // 缺 variant 数据时 evaluateLayerGates 访问 .avgFactScore 炸). 加 try/catch
88
- // 让 renderer 不 crash, 改为静默 skip pill — 这是 v0.21 B.4 的 scope 范围,
89
- // verdict.ts/layer-gates.ts 的 defensive 修复留作单独 task.
79
+ // Verdict hero — verdict 是报告的「答案」,应在头部抢眼可读:
80
+ // 行 1: 状态 badge (明显进步 / PROGRESS) + 自然语言句子 (含 ship action)
81
+ // 行 2: 分差 + 评测规模 — 最直观的两个数字, "差多少 / 跑了多少"
82
+ // machine-readable enum 通过 data-verdict-level 属性挂在 section 上,给 CI/工具用;
83
+ // ship-action token 写进 aria-label 给 screen reader,但不放进 badge 显示文字 ——
84
+ // 因为 verdictOneLine 的中文句子已经包含 "可以发布 / 不要发布" 这类 action,
85
+ // badge 再放一遍是重复。CI / CV 这种统计学专名挪到 variance 表 + tooltip,hero
86
+ // 不堆 jargon. 想看精确边界值的工程师下滑到下方的配对对比 / 波动检验表里看。
87
+ // computeVerdict 在历史混合批量 report 上有 NPE 风险,try/catch 让 renderer 不 crash。
88
+ const SHIP_LABEL = {
89
+ PROGRESS: { zh: '可发布', en: 'SHIP' },
90
+ CAUTIOUS: { zh: '需排查', en: 'INVESTIGATE' },
91
+ REGRESS: { zh: '勿发布', en: 'DO NOT SHIP' },
92
+ NOISE: { zh: '不下结论', en: 'NO CALL' },
93
+ UNDERPOWERED: { zh: '数据不足', en: 'INSUFFICIENT DATA' },
94
+ SOLO: { zh: '缺对照', en: 'ADD CONTROL' },
95
+ };
96
+ function computeMedianCVPercent(report) {
97
+ const variance = report.variance;
98
+ if (!variance || (variance.runs ?? 0) < 2)
99
+ return null;
100
+ const cvs = [];
101
+ for (const v of Object.values(variance.perVariant ?? {})) {
102
+ if (typeof v.stddev === 'number' && typeof v.mean === 'number' && v.mean > 0) {
103
+ cvs.push((v.stddev / v.mean) * 100);
104
+ }
105
+ }
106
+ if (cvs.length === 0)
107
+ return null;
108
+ cvs.sort((a, b) => a - b);
109
+ return cvs[Math.floor(cvs.length / 2)];
110
+ }
90
111
  export function renderVerdictPill(report, lang) {
91
112
  let result;
92
113
  try {
@@ -100,13 +121,39 @@ export function renderVerdictPill(report, lang) {
100
121
  const oneLine = verdictOneLine(level, lang, pair?.treatment, pair?.control);
101
122
  const tooltip = levelTooltip(level, lang);
102
123
  const prefix = lang === 'zh' ? '测评结论' : 'Verdict';
103
- // v0.21 B.4 — verdict 融入标题副标. 去 icon, 用 "测评结论:" 前缀引导, 让用户
104
- // 一眼读到 "label : 结论" 的语义结构. 状态色信号通过 verdictOneLine 的中文
105
- // 措辞 ("明显更好" / "明显更差" / "没看出差别") 传达, 不再依赖颜色 dot.
106
- return `<p class="page-verdict verdict-${level}" role="status" aria-label="${e(prefix)}: ${e(oneLine)}" title="${e(tooltip)}">
107
- <span class="page-verdict-label">${e(prefix)}:</span>
108
- <span class="page-verdict-text">${e(oneLine)}</span>
109
- </p>`;
124
+ // 机器可读 enum 永远是 level token; 显示给用户的文字按 lang i18n.
125
+ const levelDisplay = lang === 'zh' ? levelLabel(level, lang) : level;
126
+ const shipAria = lang === 'zh' ? SHIP_LABEL[level].zh : SHIP_LABEL[level].en;
127
+ // hero 只放「答案」: 分差是 verdict 的核心证据数字, 单独一枚 chip。
128
+ // 评测规模 (用例数 × 轮次) 走「实验配置」section 的 subtitle 那条 canonical 路径,
129
+ // 不在 hero 里重复; CV / CI 走 chip tooltip + 方法学审计 / 波动表。
130
+ const ci = report.meta?.pairComparisons?.[0]?.diffBootstrapCI;
131
+ const cvPct = computeMedianCVPercent(report);
132
+ const metrics = [];
133
+ if (ci) {
134
+ const sign = ci.estimate >= 0 ? '+' : '';
135
+ const cvSuffix = cvPct != null
136
+ ? (lang === 'zh' ? `;多轮稳定性 CV=${cvPct.toFixed(1)}% (${cvPct < 5 ? '稳' : cvPct < 15 ? '中' : '不稳'})` : `; CV=${cvPct.toFixed(1)}% (${cvPct < 5 ? 'stable' : cvPct < 15 ? 'moderate' : 'unstable'})`)
137
+ : '';
138
+ const ciTipBase = lang === 'zh'
139
+ ? `实验组与对照组综合分均值差(Δ)。bootstrap 95% 可信区间 [${ci.low}, ${ci.high}],${ci.significant ? '不含 0 = 差异显著' : '跨过 0 = 差异不显著'}${cvSuffix}`
140
+ : `Treatment minus control mean composite score (Δ). Bootstrap 95% CI [${ci.low}, ${ci.high}], ${ci.significant ? 'excludes 0 ⇒ significant' : 'spans 0 ⇒ not significant'}${cvSuffix}`;
141
+ metrics.push({
142
+ label: lang === 'zh' ? '分差' : 'Δ',
143
+ value: `${sign}${ci.estimate}`,
144
+ tip: ciTipBase,
145
+ });
146
+ }
147
+ const metricChips = metrics.map((m) => `<span class="verdict-metric"${m.tip ? ` title="${e(m.tip)}"` : ''}>` +
148
+ `<span class="verdict-metric-label">${e(m.label)}</span>` +
149
+ `<span class="verdict-metric-value">${e(m.value)}</span></span>`).join('');
150
+ return `<section class="page-verdict verdict-${level}" role="status" data-verdict-level="${e(level)}" aria-label="${e(prefix)}: ${e(levelDisplay)} · ${e(shipAria)} · ${e(oneLine)}" title="${e(tooltip)}">
151
+ <div class="page-verdict-head">
152
+ <span class="page-verdict-badge"><span class="page-verdict-badge-dot" aria-hidden="true">●</span>${e(levelDisplay)}</span>
153
+ <span class="page-verdict-text">${e(oneLine)}</span>
154
+ </div>
155
+ ${metricChips ? `<div class="page-verdict-metrics">${metricChips}</div>` : ''}
156
+ </section>`;
110
157
  }
111
158
  /**
112
159
  * Pairwise diff (treatment vs control) bootstrap CI table — populated only when
@@ -170,11 +217,11 @@ export function renderHumanAgreement(agreement, lang) {
170
217
  const interpret = lang === 'zh'
171
218
  ? (Number.isNaN(a.alpha)
172
219
  ? '评分多样性不足,α 未定义'
173
- : a.alpha >= 0.8 ? '高度一致 — 结论可放心使用'
174
- : a.alpha >= 0.667 ? '可接受 — 谨慎结论'
175
- : a.alpha >= 0.4 ? '较弱一致 — 结论需配合 CI 与人工抽检'
176
- : a.alpha >= 0 ? '偏差较大 — 排查 rubric / prompt'
177
- : '系统性反向 — 重新审视判分逻辑')
220
+ : a.alpha >= 0.8 ? '高度一致——结论可放心使用'
221
+ : a.alpha >= 0.667 ? '可接受——谨慎结论'
222
+ : a.alpha >= 0.4 ? '较弱一致——结论需配合 CI 与人工抽检'
223
+ : a.alpha >= 0 ? '偏差较大——排查 rubric / prompt'
224
+ : '系统性反向——重新审视判分逻辑')
178
225
  : (Number.isNaN(a.alpha)
179
226
  ? 'insufficient rating variance for α'
180
227
  : a.alpha >= 0.8 ? 'high agreement — conclusions trustworthy'
@@ -323,7 +370,10 @@ export function renderSummaryCards(variants, summary, lang, variance) {
323
370
  //
324
371
  // composite 合成分 (= (fact + behavior + judge) / 3) 从 v0.16 起不再在此表主视觉呈现,
325
372
  // 仅保留在 report JSON 数据层 + Variance & Significance 表顶层 flat 字段(legacy)。
326
- // 理由见 docs/terminology-spec.md 三-6 节:三层独立呈现避免合成分掩盖结构性差异。
373
+ // 综合分放在第一列, 紧贴 实验分组. 它是 fact/behavior/judge 三层的等权均值,
374
+ // 在 omk 内部承担「跨 run 排序 / bootstrap CI / verdict 比较信号」三个角色 ——
375
+ // 这里展示 + 表头 ? button 弹 modal 公开计算方式 + 局限, 让用户知情消费。
376
+ // 其余六列保持 layer-first (fact / behavior / judge / cost / efficiency / stability)。
327
377
  const headerCols = [
328
378
  { key: 'dimFact', label: t('dimFact', lang) },
329
379
  { key: 'dimBehavior', label: t('dimBehavior', lang) },
@@ -332,18 +382,23 @@ export function renderSummaryCards(variants, summary, lang, variance) {
332
382
  { key: 'dimEfficiency', label: t('dimEfficiency', lang) },
333
383
  { key: 'dimStability', label: t('dimStability', lang) },
334
384
  ];
385
+ const scoringModalId = 'guide-scoring';
386
+ // v0.30 — 拆 hero(每变体综合分一行)+ heatmap 表(去 composite,加色块)。
387
+ // composite 从表里挪出来当 hero,大字号 + delta 跟 baseline 对比;
388
+ // 维度细节继续用表,但每格按 score 浅色填充,便于扫读弱维度。
335
389
  const thead = `<tr><th data-i18n="variants">${t('variants', lang)}</th>${headerCols.map((c) => `<th data-i18n="${c.key}">${c.label}</th>`).join('')}</tr>`;
336
- // 渲染单层分数 cell(事实/行为/LLM 评价通用)。
337
- // 优先读跨 run 均值(byLayer[key].mean),fallback 到 summary 的单 run avg。
338
- // 缺数据时显示 "—" + 灰色,让读者明确看到"这一层这批用例/评委没测到",不假装有值。
390
+ // 渲染单层分数 cell(事实/行为/LLM 评价通用)— 加 heatmap 浅色背景
339
391
  function renderLayerCell(varianceMean, summaryValue, detailHtml = '') {
340
392
  const v = varianceMean ?? summaryValue;
341
393
  const hasValue = typeof v === 'number' && v > 0;
394
+ const heatClass = hasValue
395
+ ? (v >= 4 ? 'sm-heat-pass' : v >= 3 ? 'sm-heat-warn' : 'sm-heat-fail')
396
+ : '';
342
397
  const color = hasValue
343
398
  ? (v >= 4 ? 'var(--green)' : v >= 3 ? 'var(--yellow)' : 'var(--red)')
344
399
  : 'var(--text-muted)';
345
400
  const display = hasValue ? v.toFixed(2) : '—';
346
- return `<td class="summary-cell"><div class="summary-value summary-value-primary" style="color:${color}">${display}</div>${detailHtml}</td>`;
401
+ return `<td class="summary-cell ${heatClass}"><div class="summary-value summary-value-primary" style="color:${color}">${display}</div>${detailHtml}</td>`;
347
402
  }
348
403
  const rows = variants.map((v, i) => {
349
404
  const s = summary[v] || {};
@@ -451,6 +506,41 @@ export function renderSummaryCards(variants, summary, lang, variance) {
451
506
  const stabCell = `<td class="summary-cell"><div class="summary-value" style="color:${stabColor}">${stabValue}</div>${stabDetail}</td>`;
452
507
  return `<tr><td style="border-left:3px solid ${color};padding-left:12px"><strong>${e(v)}</strong></td>${factCell}${behaviorCell}${judgeCell}${costCell}${effCell}${stabCell}</tr>`;
453
508
  }).join('');
509
+ // ──────────── Hero 行:每 variant 一行,综合分大字号 + delta vs baseline ────────────
510
+ // 每变体一行,左 variant 名 + 颜色条;中 大字号 composite;右 delta(vs variants[0])
511
+ const baselineComposite = summary[variants[0]]?.avgCompositeScore;
512
+ const heroRows = variants.map((v, i) => {
513
+ const s = summary[v] || {};
514
+ const composite = s.avgCompositeScore;
515
+ const compositeHasValue = typeof composite === 'number' && composite > 0;
516
+ const compositeColor = compositeHasValue
517
+ ? (composite >= 4 ? 'var(--green)' : composite >= 3 ? 'var(--yellow)' : 'var(--red)')
518
+ : 'var(--text-muted)';
519
+ const compositeDisplay = compositeHasValue ? composite.toFixed(2) : '—';
520
+ const color = COLORS[i % COLORS.length];
521
+ let deltaHtml = '';
522
+ if (i > 0 && compositeHasValue && typeof baselineComposite === 'number' && baselineComposite > 0) {
523
+ const diff = composite - baselineComposite;
524
+ if (Math.abs(diff) >= 0.01) {
525
+ const sign = diff > 0 ? '+' : '';
526
+ const dColor = diff > 0 ? 'var(--green)' : 'var(--red)';
527
+ const arrow = diff > 0 ? '↑' : '↓';
528
+ deltaHtml = `<span class="sm-hero-delta" style="color:${dColor}">${sign}${diff.toFixed(2)} ${arrow}</span>`;
529
+ }
530
+ }
531
+ return `<div class="sm-hero" style="border-left:3px solid ${color}">
532
+ <div class="sm-hero-name">${e(v)}</div>
533
+ <div class="sm-hero-score" style="color:${compositeColor}">${compositeDisplay}<span class="sm-hero-unit">/ 5</span></div>
534
+ ${deltaHtml || '<span class="sm-hero-delta-placeholder"></span>'}
535
+ </div>`;
536
+ }).join('');
537
+ const heroBlock = `<div class="sm-hero-list">
538
+ <div class="sm-hero-h">
539
+ ${lang === 'zh' ? '综合分' : 'Composite'}
540
+ <button type="button" class="hint-btn" onclick="openModal('${scoringModalId}')" aria-label="${e(lang === 'zh' ? '综合分怎么算的?' : 'How is composite computed?')}" aria-haspopup="dialog">?</button>
541
+ </div>
542
+ ${heroRows}
543
+ </div>`;
454
544
  const guideModalId = 'guide-six-dims';
455
545
  const guideTitle = lang === 'zh' ? '如何阅读六维对比?' : 'How to read this six-dimension comparison?';
456
546
  const guideIntro = lang === 'zh'
@@ -487,6 +577,7 @@ export function renderSummaryCards(variants, summary, lang, variance) {
487
577
  <tr><td ${sub}>CV</td><td ${subDesc}>Score swing as a percentage of the mean (e.g., CV 2% = swings are about 2% of the mean)</td></tr>
488
578
  <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>
489
579
  `;
580
+ const scoringModalHtml = renderScoringModal(scoringModalId, lang);
490
581
  return `
491
582
  <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>
492
583
  <div id="${guideModalId}" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="${guideModalId}-title" onclick="if(event.target===this)closeModal('${guideModalId}')">
@@ -499,13 +590,94 @@ export function renderSummaryCards(variants, summary, lang, variance) {
499
590
  <table class="modal-table"><tbody>${guideRows}</tbody></table>
500
591
  </div>
501
592
  </div>
593
+ ${scoringModalHtml}
594
+ ${heroBlock}
502
595
  <div class="table-wrap">
503
596
  <table class="summary-table">
504
597
  <thead>${thead}</thead>
505
598
  <tbody>${rows}</tbody>
506
599
  </table>
507
600
  </div>
508
- ${renderJudgeAgreementBlock(variants, summary, lang)}`;
601
+ <style>
602
+ /* 六维对比 — Hero 区(每 variant 综合分 + delta) */
603
+ .sm-hero-list { display:flex;flex-direction:column;gap:8px;margin:14px 0 18px }
604
+ .sm-hero-h { font-size:13px;color:var(--text-muted);margin-bottom:4px;letter-spacing:0.02em }
605
+ .sm-hero { display:flex;align-items:baseline;gap:16px;padding:14px 18px;background:var(--bg-surface);border-radius:var(--radius);box-shadow:var(--shadow-sm) }
606
+ .sm-hero-name { flex:1;font-size:14.5px;font-weight:500;color:var(--text-primary) }
607
+ .sm-hero-score { font-size:28px;font-weight:600;font-variant-numeric:tabular-nums;line-height:1 }
608
+ .sm-hero-unit { font-size:13px;color:var(--text-muted);margin-left:4px;font-weight:400 }
609
+ .sm-hero-delta { font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;font-family:"SF Mono",Menlo,monospace;min-width:60px;text-align:right }
610
+ .sm-hero-delta-placeholder { display:inline-block;min-width:60px }
611
+ /* 六维 heatmap — 每格按分数浅色填充,便于远看识别弱维度 */
612
+ .sm-heat-pass { background:linear-gradient(0deg,var(--green-bg),var(--green-bg)) }
613
+ .sm-heat-warn { background:linear-gradient(0deg,var(--yellow-bg),var(--yellow-bg)) }
614
+ .sm-heat-fail { background:linear-gradient(0deg,var(--red-bg),var(--red-bg)) }
615
+ </style>`;
616
+ }
617
+ /**
618
+ * 评分说明 modal — 公开 composite 计算方式 + 局限,所有展示综合分的地方都通向同一份说明
619
+ * (六维对比表头 / 列表页 score 列 / 趋势页 chart caption)。
620
+ *
621
+ * 测量学诚实性 ≠ 藏起来怕用户误用,而是展示 + 说清楚边界。这个 modal 是简版 (~10 行),
622
+ * 完整推导 (五层评分管道架构 / ratioToScore 公式 / 多层 gate 与 composite 关系)
623
+ * 在 docs/zh/scoring.md。
624
+ */
625
+ export function renderScoringModal(id, lang) {
626
+ const title = lang === 'zh' ? '综合分怎么算的?' : 'How is composite computed?';
627
+ const intro = lang === 'zh'
628
+ ? '综合分(composite)= 等权均值({事实, 行为, LLM 评价})。1-5 制。'
629
+ : 'Composite = unweighted mean of {fact, behavior, LLM judge}. Scale 1-5.';
630
+ const sectionHead = (text) => `<tr><td colspan="2" style="padding:14px 0 6px;color:var(--text-primary);font-weight:600;font-size:13px;border-top:1px solid var(--border)">${e(text)}</td></tr>`;
631
+ const row = (label, body) => `<tr><td style="padding:6px 12px 6px 0;color:var(--text-secondary);font-size:12px;font-weight:500;white-space:nowrap;vertical-align:top;width:120px">${e(label)}</td><td style="padding:6px 0;color:var(--text-secondary);font-size:12px;line-height:1.6">${body}</td></tr>`;
632
+ const calcRows = lang === 'zh' ? [
633
+ row('事实 / 行为', '断言通过率经 <code>1 + 通过率 × 4</code> 线性映射到 1-5 制'),
634
+ row('LLM 评价', '评委模型直接给 1-5 分'),
635
+ row('缺失某层', '自动降维(参与计算的层取均值)'),
636
+ ] : [
637
+ row('Fact / Behavior', 'Assertion pass-rate mapped to 1-5 via <code>1 + ratio × 4</code>'),
638
+ row('LLM judge', 'Judge model returns 1-5 score directly'),
639
+ row('Missing layer', 'Auto-collapse (mean over present layers)'),
640
+ ];
641
+ const limitRows = lang === 'zh' ? [
642
+ row('① 等权聚合', '三层各 1/3 权重不是从需求 derive 的,无显式构造效度论证'),
643
+ row('② 量尺不一致', '事实是 binary 通过率 stretch 到 1-5;评委是真序数评分。直接均值违反量表理论(measurement scale homogeneity)'),
644
+ row('③ 缺失自动降维', '不同 variant / skill 配的层数不同时,综合分数字相同但 construct 不同,<strong>不可机械跨 variant / 跨 skill 比较</strong>'),
645
+ ] : [
646
+ row('① Equal-weight aggregation', 'The 1/3 weight per layer is ad hoc, not derived from stakeholder needs; no explicit construct validity argument'),
647
+ row('② Mixed scales added directly', 'Fact is binary pass-rate stretched to 1-5; judge is true ordinal. Direct mean violates measurement scale homogeneity'),
648
+ row('③ Auto-collapse on missing', 'When variants/skills have different layer coverage, composite numbers look same but represent different constructs — <strong>not mechanically comparable across variants/skills</strong>'),
649
+ ];
650
+ const usageRows = lang === 'zh' ? [
651
+ row('✓ 适用', '同一份 eval-samples 上 A/B 比较,看「分差 + bootstrap CI + 三层独立 gate」联合判断'),
652
+ row('✗ 不适用', '当作 absolute psychometric measure(说「这 skill 4.28/5 分」)'),
653
+ ] : [
654
+ row('✓ Use for', 'A/B comparison on same eval-samples — read "diff + bootstrap CI + per-layer gate" jointly'),
655
+ row('✗ Don\'t use as', 'Absolute psychometric measure (claiming "this skill is 4.28/5")'),
656
+ ];
657
+ const calcSection = lang === 'zh' ? '怎么算的' : 'How it\'s computed';
658
+ const limitSection = lang === 'zh' ? '局限(直白说)' : 'Limitations (frankly)';
659
+ const usageSection = lang === 'zh' ? '推荐用法' : 'Recommended usage';
660
+ const docsLink = lang === 'zh'
661
+ ? '完整推导 / 五层评分管道架构 / 多层 gate 与综合分的关系:<a href="https://github.com/lizhiyao/oh-my-knowledge/blob/main/docs/zh/scoring.md" target="_blank" rel="noopener">docs/zh/scoring.md</a>'
662
+ : 'Full derivation / five-layer scoring pipeline / multi-layer gate & composite relationship: <a href="https://github.com/lizhiyao/oh-my-knowledge/blob/main/docs/zh/scoring.md" target="_blank" rel="noopener">docs/zh/scoring.md</a>';
663
+ return `<div id="${id}" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="${id}-title" onclick="if(event.target===this)closeModal('${id}')">
664
+ <div class="modal-content">
665
+ <div class="modal-header">
666
+ <strong id="${id}-title" style="font-size:1rem">${e(title)}</strong>
667
+ <button type="button" class="modal-close" onclick="closeModal('${id}')" aria-label="${lang === 'zh' ? '关闭' : 'Close'}">✕</button>
668
+ </div>
669
+ <p style="font-size:13px;color:var(--text-secondary);margin:4px 0 12px">${e(intro)}</p>
670
+ <table class="modal-table"><tbody>
671
+ ${sectionHead(calcSection)}
672
+ ${calcRows.join('')}
673
+ ${sectionHead(limitSection)}
674
+ ${limitRows.join('')}
675
+ ${sectionHead(usageSection)}
676
+ ${usageRows.join('')}
677
+ </tbody></table>
678
+ <p style="font-size:11px;color:var(--text-muted);margin:14px 0 0;line-height:1.6">${docsLink}</p>
679
+ </div>
680
+ </div>`;
509
681
  }
510
682
  /**
511
683
  * Cross-sample inter-judge agreement table — only renders when at least one variant
@@ -513,7 +685,7 @@ export function renderSummaryCards(variants, summary, lang, variance) {
513
685
  * Pearson + MAD across the whole sample set, the metric that refutes "Claude judge
514
686
  * Claude same-modality bias".
515
687
  */
516
- function renderJudgeAgreementBlock(variants, summary, lang) {
688
+ export function renderJudgeAgreementBlock(variants, summary, lang) {
517
689
  const variantsWithEnsemble = variants.filter((v) => summary[v]?.judgeAgreement);
518
690
  if (variantsWithEnsemble.length === 0)
519
691
  return '';
@@ -551,6 +723,116 @@ function renderJudgeAgreementBlock(variants, summary, lang) {
551
723
  </table>
552
724
  </div>`;
553
725
  }
726
+ function badgeIcon(status) {
727
+ switch (status) {
728
+ case 'pass': return '✓';
729
+ case 'warn': return '⚠';
730
+ case 'fail': return '✗';
731
+ case 'skip': return '—';
732
+ }
733
+ }
734
+ function computeJudgeAgreementBadge(variants, summary, lang) {
735
+ const treatment = variants[1] ?? variants[0];
736
+ const ag = summary[treatment]?.judgeAgreement;
737
+ if (!ag || ag.pearson == null)
738
+ return null;
739
+ const p = ag.pearson;
740
+ const status = p >= 0.7 ? 'pass' : p >= 0.4 ? 'warn' : 'fail';
741
+ const verdict = status === 'pass'
742
+ ? (lang === 'zh' ? '评委一致' : 'judges agree')
743
+ : status === 'warn'
744
+ ? (lang === 'zh' ? '评委偏弱一致' : 'judges weakly agree')
745
+ : (lang === 'zh' ? '评委分歧大' : 'judges diverge');
746
+ return {
747
+ key: 'judge',
748
+ label: verdict,
749
+ status,
750
+ detail: lang === 'zh' ? `Pearson ${p}(≥0.7 一致 / 0.4-0.7 偏弱 / <0.4 分歧)` : `Pearson ${p} (≥0.7 agree / 0.4-0.7 weak / <0.4 diverge)`,
751
+ };
752
+ }
753
+ function computeSignificanceBadge(report, lang) {
754
+ const ci = report.meta?.pairComparisons?.[0]?.diffBootstrapCI;
755
+ if (!ci)
756
+ return null;
757
+ const status = ci.significant ? 'pass' : 'warn';
758
+ const label = ci.significant
759
+ ? (lang === 'zh' ? '差异显著' : 'significant')
760
+ : (lang === 'zh' ? '差异不显著' : 'not significant');
761
+ return {
762
+ key: 'sig',
763
+ label,
764
+ status,
765
+ detail: lang === 'zh' ? `bootstrap 95% CI [${ci.low}, ${ci.high}]${ci.significant ? '(不含 0)' : '(跨过 0)'}` : `Bootstrap 95% CI [${ci.low}, ${ci.high}]${ci.significant ? ' (excludes 0)' : ' (spans 0)'}`,
766
+ };
767
+ }
768
+ function computeSaturationBadge(report, variants, lang) {
769
+ const sat = report.variance?.saturation?.verdicts;
770
+ if (!sat)
771
+ return null;
772
+ const treatment = variants[1] ?? variants[0];
773
+ const v = sat[treatment];
774
+ if (!v)
775
+ return null;
776
+ const status = v.saturated
777
+ ? (v.confidence === 'high' ? 'pass' : 'warn')
778
+ : 'warn';
779
+ const label = v.saturated
780
+ ? (lang === 'zh' ? '已饱和' : 'saturated')
781
+ : (lang === 'zh' ? '未饱和' : 'not saturated');
782
+ return {
783
+ key: 'sat',
784
+ label,
785
+ status,
786
+ detail: lang === 'zh'
787
+ ? `${v.saturated ? `于 N=${v.atN ?? '?'} 饱和` : '尚未饱和'}(${v.confidence} confidence)`
788
+ : `${v.saturated ? `saturates at N=${v.atN ?? '?'}` : 'not yet saturated'} (${v.confidence} confidence)`,
789
+ };
790
+ }
791
+ function computeHumanAlignmentBadge(report, lang) {
792
+ const a = report.meta?.humanAgreement;
793
+ if (!a || a.sampleCount === 0 || Number.isNaN(a.alpha))
794
+ return null;
795
+ const status = a.alpha >= 0.667 ? 'pass' : a.alpha >= 0.4 ? 'warn' : 'fail';
796
+ const label = status === 'pass'
797
+ ? (lang === 'zh' ? '人工对齐' : 'aligns with humans')
798
+ : status === 'warn'
799
+ ? (lang === 'zh' ? '人工对齐偏弱' : 'weakly aligns')
800
+ : (lang === 'zh' ? '与人工分歧' : 'diverges from humans');
801
+ return {
802
+ key: 'human',
803
+ label,
804
+ status,
805
+ detail: lang === 'zh' ? `Krippendorff α=${a.alpha.toFixed(3)} vs ${a.goldAnnotator}` : `Krippendorff α=${a.alpha.toFixed(3)} vs ${a.goldAnnotator}`,
806
+ };
807
+ }
808
+ export function renderMethodologyAudit(report, variants, summary, lang) {
809
+ const judgeBlock = renderJudgeAgreementBlock(variants, summary, lang);
810
+ const pairwiseBlock = renderPairwiseDiff(report.meta?.pairComparisons, lang);
811
+ const humanBlock = renderHumanAgreement(report.meta?.humanAgreement, lang);
812
+ const saturationBlock = renderSaturationCurve(report.variance?.saturation, variants, lang);
813
+ if (!judgeBlock && !pairwiseBlock && !humanBlock && !saturationBlock)
814
+ return '';
815
+ const badges = [
816
+ computeJudgeAgreementBadge(variants, summary, lang),
817
+ computeSignificanceBadge(report, lang),
818
+ computeSaturationBadge(report, variants, lang),
819
+ computeHumanAlignmentBadge(report, lang),
820
+ ].filter((b) => b !== null);
821
+ // 默认折叠;只有 ✗ fail (红色 badge) 才强制展开 — summary 行已经显示
822
+ // ⚠ warn 状态, 用户能自决是否展开; 但 fail 是危险信号必须让用户看证据。
823
+ const hasFailure = badges.some((b) => b.status === 'fail');
824
+ const openAttr = hasFailure ? ' open' : '';
825
+ const summaryLabel = lang === 'zh' ? '测评可信度' : 'Reliability check';
826
+ const summaryHint = lang === 'zh' ? '点击展开看支撑证据(评委一致 / 差异显著 / 饱和度 / 人工对齐)' : 'click to expand evidence (judge agreement / significance / saturation / human alignment)';
827
+ const badgesHtml = badges.length > 0
828
+ ? `<span class="methodology-badges">${badges.map((b) => `<span class="methodology-badge methodology-badge-${b.status}" title="${e(b.detail)}">${badgeIcon(b.status)} ${e(b.label)}</span>`).join('')}</span>`
829
+ : '';
830
+ const innerSections = [judgeBlock, pairwiseBlock, humanBlock, saturationBlock].filter(Boolean).join('');
831
+ return `<details class="methodology-audit"${openAttr}>
832
+ <summary><span class="methodology-summary-label">${e(summaryLabel)}</span>${badgesHtml}<span class="methodology-summary-hint">${e(summaryHint)}</span></summary>
833
+ <div class="methodology-body">${innerSections}</div>
834
+ </details>`;
835
+ }
554
836
  function buildDiagnostic(metric, cfg, winner, lang) {
555
837
  const es = metric.effectSize;
556
838
  if (!es || es.primary === 'none') {
@@ -1203,56 +1485,39 @@ export function renderAgentOverview(variants, summary, lang) {
1203
1485
  const hasAgentData = variants.some((variant) => summary[variant]?.avgToolCalls != null && summary[variant].avgToolCalls > 0);
1204
1486
  if (!hasAgentData)
1205
1487
  return '';
1206
- const variantCards = variants.map((variant, i) => {
1488
+ // v0.30 — Agent 执行概览弱化为 ctx-row(单行 inline)样式,跟实验配置并排放在 context-strip 里。
1489
+ // 主要数字:轮次 / 工具调用数 / 成功率,加 tooltip 显示工具分布。
1490
+ const variantRows = variants.map((variant, i) => {
1207
1491
  const stats = summary[variant];
1208
1492
  if (!stats)
1209
1493
  return '';
1210
1494
  const color = COLORS[i % COLORS.length];
1211
1495
  const avgTools = stats.avgToolCalls ?? 0;
1212
- const successRate = stats.toolSuccessRate != null ? `${(stats.toolSuccessRate * 100).toFixed(0)}%` : '-';
1496
+ const successRate = stats.toolSuccessRate != null ? `${(stats.toolSuccessRate * 100).toFixed(0)}%` : '—';
1213
1497
  const successRateColor = (stats.toolSuccessRate ?? 1) >= 0.8 ? 'var(--green)' : 'var(--red)';
1214
1498
  const turns = stats.avgFullNumTurns ?? stats.avgNumTurns ?? 0;
1215
1499
  const distributionEntries = Object.entries(stats.toolDistribution || {}).sort((a, b) => b[1] - a[1]);
1216
- const maxCount = distributionEntries.length > 0 ? distributionEntries[0][1] : 0;
1217
- const distributionBars = distributionEntries.map(([tool, count]) => {
1218
- const pct = maxCount > 0 ? Math.max(8, (count / maxCount) * 100) : 0;
1219
- return `<div style="display:flex;align-items:center;gap:6px;margin:2px 0">
1220
- <span style="font-size:11px;color:var(--text-muted);width:100px;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0" title="${e(tool)}">${e(tool)}</span>
1221
- <div style="flex:1;height:8px;background:var(--bg-surface);border-radius:4px">
1222
- <div style="width:${pct}%;height:100%;background:${color};border-radius:4px"></div>
1223
- </div>
1224
- <span style="font-size:11px;color:var(--text-secondary);min-width:20px">${count}</span>
1225
- </div>`;
1226
- }).join('');
1227
- return `<div style="flex:1;min-width:240px;padding:16px;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);border-left:3px solid ${color}">
1228
- <div style="font-weight:600;margin-bottom:12px">${e(variant)}</div>
1229
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px">
1230
- <div>
1231
- <div style="font-size:11px;color:var(--text-muted)">${t('agentAvgTurns', lang)}</div>
1232
- <div style="font-size:20px;font-weight:600">${turns}</div>
1233
- </div>
1234
- <div>
1235
- <div style="font-size:11px;color:var(--text-muted)">${t('agentAvgTools', lang)}</div>
1236
- <div style="font-size:20px;font-weight:600">${avgTools}</div>
1237
- </div>
1238
- <div>
1239
- <div style="font-size:11px;color:var(--text-muted)">${t('agentToolSuccess', lang)}</div>
1240
- <div style="font-size:20px;font-weight:600;color:${successRateColor}">${successRate}</div>
1241
- </div>
1242
- </div>
1243
- ${distributionEntries.length > 0 ? `
1244
- <div style="font-size:11px;color:var(--text-muted);margin-bottom:4px">${t('agentToolDist', lang)}</div>
1245
- ${distributionBars}
1246
- ` : ''}
1500
+ const distSummary = distributionEntries.length > 0
1501
+ ? distributionEntries.slice(0, 5).map(([tool, count]) => `${tool}×${count}`).join(' · ')
1502
+ : '';
1503
+ const tooltip = distSummary ? `${t('agentToolDist', lang)}: ${distSummary}` : '';
1504
+ return `<div class="ctx-row" style="border-left:3px solid ${color}"${tooltip ? ` title="${e(tooltip)}"` : ''}>
1505
+ <span class="ctx-row-name">${e(variant)}</span>
1506
+ <span class="ctx-row-bits">
1507
+ <span>${turns} ${t('turnsPerReq', lang)}</span>
1508
+ <span class="ctx-sep">·</span>
1509
+ <span>${avgTools} ${lang === 'zh' ? '工具/次' : 'tools/req'}</span>
1510
+ <span class="ctx-sep">·</span>
1511
+ <span style="color:${successRateColor}">${successRate} OK</span>
1512
+ </span>
1247
1513
  </div>`;
1248
1514
  }).join('');
1249
1515
  return `
1250
- <section style="margin-top:24px">
1251
- <h2>${t('agentOverview', lang)}</h2>
1252
- <div style="display:flex;gap:16px;flex-wrap:wrap">
1253
- ${variantCards}
1254
- </div>
1255
- </section>
1516
+ <div class="ctx-block">
1517
+ <div class="ctx-h">${t('agentOverview', lang)}</div>
1518
+ <div class="ctx-sub">${lang === 'zh' ? '工具调用统计 · 轮次 / 调用数 / 成功率' : 'Tool call stats · turns / calls / success rate'}</div>
1519
+ <div class="ctx-rows">${variantRows}</div>
1520
+ </div>
1256
1521
  `;
1257
1522
  }
1258
1523
  export function renderCoverageSection(coverage, lang) {
@@ -1343,6 +1608,13 @@ export function renderKnowledgeInteractionSection(coverage, gapReports, lang) {
1343
1608
  const hasGap = gapReports && Object.keys(gapReports).length > 0;
1344
1609
  if (!hasCov && !hasGap)
1345
1610
  return '';
1611
+ // 所有 variant 的 coverage 都不可用 (filesTotal === 0) 且所有 gap 都是 0%(无信号)
1612
+ // 时,整个 section 没有可读信息 — 渲染只会占大段纵向空间显示「数据不可用 / 0%」
1613
+ // 干扰用户阅读流。直接跳过比强行展示「显式无信号」更尊重读者。
1614
+ const usefulCoverage = hasCov && Object.values(coverage).some((c) => c.filesTotal > 0);
1615
+ const usefulGap = hasGap && Object.values(gapReports).some((g) => g.gapRate > 0 || g.signals.length > 0);
1616
+ if (!usefulCoverage && !usefulGap)
1617
+ return '';
1346
1618
  const title = lang === 'zh' ? '本次测评:评测用例 × 知识库' : 'This Evaluation: Test Set × Knowledge Base';
1347
1619
  const desc = lang === 'zh'
1348
1620
  ? '展示本次评测用例和知识库的交互画像——用到哪些知识(使用情况)· 哪些知识想找但没找到或任务执行模型表达不确定(盲区)。'