niceeval 0.5.4 → 0.6.1

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 (337) hide show
  1. package/INDEX.md +55 -0
  2. package/dist/agents/types.d.ts +321 -0
  3. package/dist/context/types.d.ts +335 -0
  4. package/dist/i18n/core.d.ts +11 -0
  5. package/dist/i18n/en.d.ts +181 -0
  6. package/dist/i18n/index.d.ts +5 -0
  7. package/dist/i18n/zh-CN.d.ts +183 -0
  8. package/dist/o11y/otlp/parse.d.ts +3 -0
  9. package/dist/o11y/otlp/receiver.d.ts +21 -0
  10. package/dist/o11y/otlp/turn-otel.d.ts +43 -0
  11. package/dist/o11y/types.d.ts +190 -0
  12. package/dist/report/aggregate.d.ts +57 -0
  13. package/dist/report/aggregate.js +212 -0
  14. package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
  15. package/dist/report/built-ins/experiment-comparison.js +13 -0
  16. package/dist/report/built-ins/index.d.ts +1 -0
  17. package/dist/report/built-ins/index.js +2 -0
  18. package/dist/report/components.d.ts +180 -0
  19. package/dist/report/components.js +132 -0
  20. package/dist/report/compute.d.ts +106 -0
  21. package/dist/report/compute.js +600 -0
  22. package/dist/report/flag.d.ts +6 -0
  23. package/dist/report/flag.js +8 -0
  24. package/dist/report/format.d.ts +26 -0
  25. package/dist/report/format.js +109 -0
  26. package/dist/report/index.d.ts +19 -0
  27. package/dist/report/index.js +30 -0
  28. package/dist/report/load.d.ts +12 -0
  29. package/dist/report/load.js +56 -0
  30. package/dist/report/locale.d.ts +94 -0
  31. package/dist/report/locale.js +183 -0
  32. package/dist/report/metrics.d.ts +21 -0
  33. package/dist/report/metrics.js +115 -0
  34. package/dist/report/primitives.d.ts +64 -0
  35. package/dist/report/primitives.js +113 -0
  36. package/dist/report/react/AttemptList.d.ts +23 -0
  37. package/dist/report/react/AttemptList.js +19 -0
  38. package/dist/report/react/DeltaTable.d.ts +8 -0
  39. package/dist/report/react/DeltaTable.js +24 -0
  40. package/dist/report/react/EvalList.d.ts +10 -0
  41. package/dist/report/react/EvalList.js +0 -0
  42. package/dist/report/react/ExperimentList.d.ts +11 -0
  43. package/dist/report/react/ExperimentList.js +67 -0
  44. package/dist/report/react/GroupSummary.d.ts +8 -0
  45. package/dist/report/react/GroupSummary.js +8 -0
  46. package/dist/report/react/MetricBars.d.ts +10 -0
  47. package/dist/report/react/MetricBars.js +48 -0
  48. package/dist/report/react/MetricLine.d.ts +9 -0
  49. package/dist/report/react/MetricLine.js +68 -0
  50. package/dist/report/react/MetricMatrix.d.ts +10 -0
  51. package/dist/report/react/MetricMatrix.js +23 -0
  52. package/dist/report/react/MetricScatter.d.ts +9 -0
  53. package/dist/report/react/MetricScatter.js +96 -0
  54. package/dist/report/react/MetricTable.d.ts +11 -0
  55. package/dist/report/react/MetricTable.js +26 -0
  56. package/dist/report/react/RunOverview.d.ts +8 -0
  57. package/dist/report/react/RunOverview.js +12 -0
  58. package/dist/report/react/Scoreboard.d.ts +8 -0
  59. package/dist/report/react/Scoreboard.js +26 -0
  60. package/dist/report/react/cell.d.ts +9 -0
  61. package/dist/report/react/cell.js +9 -0
  62. package/dist/report/react/chart-math.d.ts +14 -0
  63. package/dist/report/react/chart-math.js +68 -0
  64. package/dist/report/react/colors.d.ts +14 -0
  65. package/dist/report/react/colors.js +45 -0
  66. package/dist/report/react/fixtures.d.ts +19 -0
  67. package/dist/report/react/fixtures.js +398 -0
  68. package/dist/report/react/format.d.ts +3 -0
  69. package/dist/report/react/format.js +7 -0
  70. package/dist/report/react/index.d.ts +6 -0
  71. package/dist/report/react/index.js +14 -0
  72. package/dist/report/report.d.ts +24 -0
  73. package/dist/report/report.js +49 -0
  74. package/dist/report/text/faces.d.ts +29 -0
  75. package/dist/report/text/faces.js +541 -0
  76. package/dist/report/text/layout.d.ts +75 -0
  77. package/dist/report/text/layout.js +173 -0
  78. package/dist/report/text/plot.d.ts +37 -0
  79. package/dist/report/text/plot.js +81 -0
  80. package/dist/report/text/table.d.ts +8 -0
  81. package/dist/report/text/table.js +86 -0
  82. package/dist/report/tree.d.ts +99 -0
  83. package/dist/report/tree.js +228 -0
  84. package/dist/report/types.d.ts +408 -0
  85. package/dist/report/types.js +4 -0
  86. package/dist/report/web.d.ts +15 -0
  87. package/dist/report/web.js +37 -0
  88. package/dist/results/locator.d.ts +75 -0
  89. package/dist/results/locator.js +0 -0
  90. package/dist/results/select.d.ts +45 -0
  91. package/dist/results/select.js +230 -0
  92. package/dist/results/source-hash.d.ts +9 -0
  93. package/dist/results/types.d.ts +204 -0
  94. package/dist/runner/eval-source.d.ts +22 -0
  95. package/dist/runner/fingerprint.d.ts +23 -0
  96. package/dist/runner/types.d.ts +332 -0
  97. package/dist/sandbox/docker-stream.d.ts +24 -0
  98. package/dist/sandbox/docker.d.ts +84 -0
  99. package/dist/sandbox/e2b.d.ts +31 -0
  100. package/dist/sandbox/errors.d.ts +4 -0
  101. package/dist/sandbox/local-files.d.ts +6 -0
  102. package/dist/sandbox/paths.d.ts +4 -0
  103. package/dist/sandbox/registry.d.ts +13 -0
  104. package/dist/sandbox/resolve.d.ts +40 -0
  105. package/dist/sandbox/retry.d.ts +10 -0
  106. package/dist/sandbox/shell.d.ts +11 -0
  107. package/dist/sandbox/source-files.d.ts +13 -0
  108. package/dist/sandbox/types.d.ts +203 -0
  109. package/dist/sandbox/vercel.d.ts +37 -0
  110. package/dist/scoring/types.d.ts +70 -0
  111. package/dist/shared/aggregate.d.ts +29 -0
  112. package/dist/shared/aggregate.js +55 -0
  113. package/dist/shared/types.d.ts +27 -0
  114. package/dist/shared/verdict.d.ts +27 -0
  115. package/dist/shared/verdict.js +34 -0
  116. package/dist/tty-line.d.ts +6 -0
  117. package/dist/types.d.ts +7 -0
  118. package/dist/util.d.ts +22 -0
  119. package/docs-site/zh/concepts/adapter.mdx +7 -7
  120. package/docs-site/zh/concepts/assert.mdx +19 -18
  121. package/docs-site/zh/concepts/evals.mdx +12 -11
  122. package/docs-site/zh/concepts/experiment.mdx +2 -2
  123. package/docs-site/zh/concepts/judge.mdx +9 -9
  124. package/docs-site/zh/concepts/overview.mdx +3 -3
  125. package/docs-site/zh/concepts/tier.mdx +6 -6
  126. package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
  127. package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
  128. package/docs-site/zh/guides/agent-feedback-loop.mdx +237 -78
  129. package/docs-site/zh/guides/connect-otel.mdx +3 -3
  130. package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
  131. package/docs-site/zh/guides/custom-reports.mdx +196 -36
  132. package/docs-site/zh/guides/experiments.mdx +7 -1
  133. package/docs-site/zh/guides/fixtures.mdx +4 -2
  134. package/docs-site/zh/guides/official-adapters.mdx +31 -9
  135. package/docs-site/zh/guides/publish-report.mdx +4 -4
  136. package/docs-site/zh/guides/report-components.mdx +194 -55
  137. package/docs-site/zh/guides/reporters.mdx +2 -2
  138. package/docs-site/zh/guides/results-data.mdx +61 -34
  139. package/docs-site/zh/guides/runner.mdx +2 -2
  140. package/docs-site/zh/guides/sandbox-agent.mdx +3 -2
  141. package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +12 -12
  142. package/docs-site/zh/guides/viewing-results.mdx +200 -79
  143. package/docs-site/zh/guides/write-experiment.mdx +9 -1
  144. package/docs-site/zh/guides/write-send.mdx +4 -3
  145. package/docs-site/zh/index.mdx +4 -3
  146. package/docs-site/zh/introduction.mdx +2 -1
  147. package/docs-site/zh/quickstart.mdx +5 -3
  148. package/docs-site/zh/reference/builtin-agents.mdx +38 -13
  149. package/docs-site/zh/reference/capabilities.mdx +1 -1
  150. package/docs-site/zh/reference/cli.mdx +36 -13
  151. package/docs-site/zh/reference/define-agent.mdx +15 -13
  152. package/docs-site/zh/reference/define-config.mdx +1 -1
  153. package/docs-site/zh/reference/define-eval.mdx +3 -3
  154. package/docs-site/zh/reference/events.mdx +9 -3
  155. package/docs-site/zh/reference/expect.mdx +13 -8
  156. package/package.json +10 -6
  157. package/src/agents/bub.ts +129 -42
  158. package/src/agents/claude-code.test.ts +152 -0
  159. package/src/agents/claude-code.ts +127 -13
  160. package/src/agents/codex.test.ts +192 -0
  161. package/src/agents/codex.ts +151 -12
  162. package/src/agents/index.ts +9 -3
  163. package/src/agents/manifest.ts +41 -0
  164. package/src/agents/plugin-config.test.ts +94 -0
  165. package/src/agents/shared.ts +33 -2
  166. package/src/agents/skills.test.ts +214 -0
  167. package/src/agents/skills.ts +304 -0
  168. package/src/agents/streaming.ts +5 -5
  169. package/src/agents/types.ts +59 -3
  170. package/src/cli.ts +74 -47
  171. package/src/context/context.test.ts +20 -2
  172. package/src/context/control-flow.ts +1 -1
  173. package/src/context/session.test.ts +92 -1
  174. package/src/context/session.ts +23 -1
  175. package/src/context/types.ts +4 -4
  176. package/src/define.test.ts +8 -8
  177. package/src/define.ts +11 -11
  178. package/src/expect/index.ts +5 -5
  179. package/src/i18n/en.ts +33 -15
  180. package/src/i18n/zh-CN.ts +37 -21
  181. package/src/index.ts +1 -1
  182. package/src/o11y/execution-tree.test.ts +452 -0
  183. package/src/o11y/execution-tree.ts +367 -0
  184. package/src/o11y/otlp/receiver.ts +1 -1
  185. package/src/o11y/otlp/select.ts +7 -3
  186. package/src/o11y/parsers/claude-code.test.ts +175 -0
  187. package/src/o11y/parsers/claude-code.ts +32 -0
  188. package/src/o11y/types.ts +6 -1
  189. package/src/report/aggregate.ts +26 -20
  190. package/src/report/built-in-user-parity.test.tsx +640 -0
  191. package/src/report/built-ins/experiment-comparison.tsx +19 -0
  192. package/src/report/built-ins/index.ts +2 -0
  193. package/src/report/components.tsx +225 -50
  194. package/src/report/compute.ts +335 -54
  195. package/src/report/dual-render.test.tsx +1129 -0
  196. package/src/report/flag.ts +1 -1
  197. package/src/report/format.ts +35 -2
  198. package/src/report/index.ts +47 -14
  199. package/src/report/load.ts +1 -1
  200. package/src/report/locale.ts +229 -0
  201. package/src/report/metrics.ts +39 -15
  202. package/src/report/primitives.tsx +100 -2
  203. package/src/report/react/AttemptList.tsx +113 -0
  204. package/src/report/react/DeltaTable.tsx +9 -6
  205. package/src/report/react/EvalList.tsx +0 -0
  206. package/src/report/react/ExperimentList.tsx +220 -0
  207. package/src/report/react/GroupSummary.tsx +66 -0
  208. package/src/report/react/MetricBars.tsx +24 -13
  209. package/src/report/react/MetricLine.tsx +21 -21
  210. package/src/report/react/MetricMatrix.tsx +8 -4
  211. package/src/report/react/MetricScatter.tsx +126 -96
  212. package/src/report/react/MetricTable.tsx +115 -15
  213. package/src/report/react/RunOverview.tsx +48 -26
  214. package/src/report/react/Scoreboard.tsx +15 -7
  215. package/src/report/react/cell.tsx +18 -9
  216. package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
  217. package/src/report/react/colors.ts +24 -15
  218. package/src/report/react/enhance.js +168 -0
  219. package/src/report/react/fixtures.ts +169 -34
  220. package/src/report/react/format.ts +1 -1
  221. package/src/report/react/index.tsx +21 -6
  222. package/src/report/react/render.test.tsx +180 -31
  223. package/src/report/react/styles.css +432 -213
  224. package/src/report/report.test.ts +398 -94
  225. package/src/report/report.ts +29 -11
  226. package/src/report/text/faces.ts +344 -96
  227. package/src/report/text/layout.ts +76 -11
  228. package/src/report/text/table.ts +98 -0
  229. package/src/report/tree.ts +103 -20
  230. package/src/report/types.ts +184 -25
  231. package/src/report/web.ts +46 -19
  232. package/src/results/annotated-source.test.ts +146 -0
  233. package/src/results/annotated-source.ts +128 -0
  234. package/src/results/attempt-evidence.test.ts +158 -0
  235. package/src/results/attempt-evidence.ts +158 -0
  236. package/src/results/attempt-source.ts +48 -0
  237. package/src/results/copy.ts +154 -139
  238. package/src/results/format.ts +54 -47
  239. package/src/results/host-equivalence.test.ts +527 -0
  240. package/src/results/index.ts +56 -14
  241. package/src/results/locator.test.ts +198 -0
  242. package/src/results/locator.ts +0 -0
  243. package/src/results/open.ts +392 -175
  244. package/src/results/results.test.ts +943 -494
  245. package/src/results/select.ts +129 -23
  246. package/src/results/skipped-notice.ts +0 -0
  247. package/src/results/source-hash.ts +28 -0
  248. package/src/results/types.ts +98 -62
  249. package/src/results/writer.ts +304 -176
  250. package/src/runner/attempt.test.ts +170 -0
  251. package/src/runner/attempt.ts +48 -13
  252. package/src/runner/discover.test.ts +65 -0
  253. package/src/runner/discover.ts +6 -2
  254. package/src/runner/eval-source.test.ts +121 -0
  255. package/src/runner/eval-source.ts +45 -0
  256. package/src/runner/fingerprint.ts +54 -1
  257. package/src/runner/remote-sandbox.ts +1 -1
  258. package/src/runner/report.test.ts +6 -8
  259. package/src/runner/report.ts +5 -4
  260. package/src/runner/reporters/artifacts.ts +27 -41
  261. package/src/runner/reporters/braintrust.test.ts +3 -3
  262. package/src/runner/reporters/braintrust.ts +1 -1
  263. package/src/runner/reporters/console.ts +6 -6
  264. package/src/runner/reporters/json.ts +4 -4
  265. package/src/runner/reporters/live.test.ts +56 -0
  266. package/src/runner/reporters/live.ts +36 -10
  267. package/src/runner/reporters/quiet.test.ts +66 -0
  268. package/src/runner/reporters/quiet.ts +49 -0
  269. package/src/runner/reporters/shared.ts +5 -5
  270. package/src/runner/reporters/table.ts +17 -17
  271. package/src/runner/run.ts +61 -102
  272. package/src/runner/types.ts +43 -32
  273. package/src/sandbox/checkpoint.ts +1 -1
  274. package/src/sandbox/docker.ts +15 -2
  275. package/src/sandbox/e2b.ts +11 -5
  276. package/src/sandbox/errors.ts +13 -0
  277. package/src/sandbox/index.ts +3 -3
  278. package/src/sandbox/local-files.ts +1 -1
  279. package/src/sandbox/registry.ts +6 -5
  280. package/src/sandbox/resolve.ts +38 -23
  281. package/src/sandbox/retry.test.ts +68 -0
  282. package/src/sandbox/retry.ts +44 -0
  283. package/src/sandbox/types.ts +24 -24
  284. package/src/sandbox/vercel.ts +17 -3
  285. package/src/scoring/collector.ts +3 -3
  286. package/src/scoring/judge.ts +2 -2
  287. package/src/scoring/scoped.ts +13 -1
  288. package/src/scoring/types.ts +7 -7
  289. package/src/scoring/verdict.ts +4 -4
  290. package/src/shared/aggregate.ts +23 -3
  291. package/src/shared/types.ts +2 -2
  292. package/src/shared/{outcome.ts → verdict.ts} +15 -15
  293. package/src/show/compose.ts +12 -132
  294. package/src/show/index.ts +119 -95
  295. package/src/show/render.ts +462 -159
  296. package/src/show/show.test.ts +471 -253
  297. package/src/source-loc.ts +1 -1
  298. package/src/tty-line.ts +32 -0
  299. package/src/types.ts +2 -2
  300. package/src/util.test.ts +21 -1
  301. package/src/util.ts +5 -1
  302. package/src/view/app/App.tsx +33 -198
  303. package/src/view/app/components/AttemptModal.tsx +6 -6
  304. package/src/view/app/components/CodeView.tsx +2 -2
  305. package/src/view/app/components/CopyControls.tsx +32 -36
  306. package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
  307. package/src/view/app/components/ui/badge.tsx +1 -1
  308. package/src/view/app/components/ui/dialog.tsx +2 -2
  309. package/src/view/app/i18n.ts +40 -31
  310. package/src/view/app/lib/artifact-url.ts +1 -1
  311. package/src/view/app/lib/attempt-route.test.ts +37 -47
  312. package/src/view/app/lib/attempt-route.ts +26 -26
  313. package/src/view/app/lib/rows.ts +9 -112
  314. package/src/view/app/lib/verdict.ts +25 -0
  315. package/src/view/app/main.tsx +9 -12
  316. package/src/view/app/pages/RunsPage.tsx +4 -4
  317. package/src/view/app/pages/TracesPage.tsx +3 -3
  318. package/src/view/app/shared.ts +0 -1
  319. package/src/view/app/types.ts +6 -51
  320. package/src/view/artifact-serving.test.ts +136 -0
  321. package/src/view/client-dist/app.css +1 -1
  322. package/src/view/client-dist/app.js +21 -21
  323. package/src/view/data.test.ts +214 -143
  324. package/src/view/data.ts +166 -120
  325. package/src/view/index.ts +23 -11
  326. package/src/view/server.ts +52 -17
  327. package/src/view/shared/types.ts +22 -31
  328. package/src/view/styles.css +117 -327
  329. package/src/view/view-report.test.ts +146 -98
  330. package/src/report/default-report.tsx +0 -222
  331. package/src/report/dual-face.test.tsx +0 -527
  332. package/src/report/react/CaseList.tsx +0 -70
  333. package/src/view/app/components/CostScoreChart.tsx +0 -147
  334. package/src/view/app/components/ExperimentTable.tsx +0 -284
  335. package/src/view/app/components/GroupSelector.tsx +0 -61
  336. package/src/view/app/components/primitives.tsx +0 -43
  337. package/src/view/app/lib/outcome.ts +0 -82
@@ -1,50 +1,60 @@
1
1
  ---
2
2
  title: "用报告积木搭自己的口径"
3
3
  sidebarTitle: "自定义报告"
4
- description: "一份报告就是一个报告文件:官方宿主打开结果、注入数据,Row / Col 负责排版,双面组件同时渲染网页与终端——niceeval show 与 niceeval view 默认渲染的默认报告,就是内置的那份报告文件。"
4
+ description: "一份报告就是一个报告文件:官方宿主打开结果、注入数据,双面组件让同一份自定义报告同时用于 niceeval show 与 niceeval view"
5
5
  ---
6
6
 
7
7
  [查看结果](/zh/guides/viewing-results)讲「用」:官方两扇门 `niceeval show`(终端)和 `niceeval view`(网页)怎么看。本页讲「写」:官方摆法不够时,怎么写一份自己的报告——考试成绩单、代码行数榜、质量 × 成本 frontier。
8
8
 
9
9
  一份报告就是一个报告文件。你不用打开结果目录、不用写渲染代码、不用起自己的应用:`niceeval show` / `niceeval view` 本体就是宿主——替你打开结果、把数据注入进来,你用官方组件和 `Row` / `Col` 摆版面,写完把文件路径递给 `--report`,终端和网页两扇门就都认它——官方的证据室深链、`--run` 历史切换、静态导出,自定义报告全部原样享有。
10
10
 
11
- ## 官方默认报告也是一份报告文件
11
+ ## view 的默认报告也是一份报告定义
12
12
 
13
- `niceeval view` 和 `niceeval show` 不传 `--report` 时渲染的默认报告不是私有实现,而是内置的一份报告文件:`niceeval view` 就等于 `niceeval view --report <内置默认报告>`,`show` 同理。我们自己用的零件和给你的完全一样,没有私有数据通道,默认报告上的每个数字你都能用同样几行代码原样重算出来。
13
+ `niceeval view` 不传 `--report` 时渲染的默认报告不是私有实现,而是 `niceeval/report` 公开导出的一个值,也可显式传给 `niceeval show --report`:
14
+
15
+ ```ts
16
+ import { CostPassRateComparison } from "niceeval/report";
17
+ ```
18
+
19
+ `niceeval view` 缺省选择 `CostPassRateComparison`:一张成本 × 通过率散点图加一张实验明细表,没有私有数据通道。裸 `niceeval show` 使用更适合终端的 Attempt 索引;显式自定义报告(包括这份 definition)仍可由两个宿主按同一双面协议渲染。
14
20
 
15
21
  | | 网页(人看) | 终端(agent 和你看) |
16
22
  | --- | --- | --- |
17
- | 官方摆法 | `niceeval view` | `niceeval show` |
23
+ | 官方默认 | 分析报告 | Attempt 索引 |
18
24
  | 自定义摆法 | `niceeval view --report reports/exam.tsx` | `niceeval show --report reports/exam.tsx` |
19
25
 
20
26
  四个格子是同一个宿主。自定义只有三个层次,逐层深入:
21
27
 
22
28
  1. **换摆法**:用官方组件和 `Row` / `Col` 重摆版面——大多数「要自己的口径」到这层就够了。
23
- 2. **换口径**:`defineMetric` 定义自己的指标,`flag()` 把 experiment 声明的变量当维度和轴。
24
- 3. **换形态**:官方组件表达不了的展示,用 `defineComponent` 写自己的组件——网页怎么渲染、终端字符怎么排,两个面你都说了算。
29
+ 2. **换口径**:`defineMetric` 定义自己的指标,自定义维度和 `flag()` 定义自己的分组。
30
+ 3. **换形态**:官方组件摆不出的展示——是表就用 `<Table>` 自定义列,不是表就用 `defineComponent` 写自己的组件,网页怎么渲染、终端字符怎么排,两个面你都说了算。
25
31
 
26
32
  ## 一份报告 = 一个报告文件
27
33
 
28
34
  先交代唯一的前置:报告文件是 `.tsx`,写它的项目要装 `react`(写自定义组件的 web 面还要 `@types/react`),tsconfig 里 `compilerOptions.jsx` 设为 `"react-jsx"`。裸跑 `niceeval show` / `niceeval view` 不需要这些——只有自己写报告文件才需要。
29
35
 
30
- 报告基座是 `defineReport`:宿主打开结果目录(含 `--run` 指定历史 run)、按官方口径挑好快照,注入给你的函数;你只负责折数据和摆积木:
36
+ 报告基座是 `defineReport`:宿主打开结果目录(含 `--run` 指定历史 run)、按官方口径挑好结果快照,注入给你的函数;你只负责折数据和摆积木:
31
37
 
32
38
  ```tsx
33
39
  // reports/exam.tsx —— 一份定义,两扇门共用
34
40
  import {
35
41
  defineReport, Col, Section,
36
- DefaultReport, Scoreboard,
42
+ MetricScatter, ExperimentList, EvalList, AttemptList, Scoreboard,
43
+ costUSD, passRate,
37
44
  } from "niceeval/report";
38
45
 
39
- export default defineReport(async ({ selection }) => (
40
- <Col>
41
- <DefaultReport />
42
- {/* 官方水位原样铺进来,不要就删这行 */}
43
- <Section title="考试成绩单">
44
- <Scoreboard data={await Scoreboard.data(selection, { rows: "agent", subjects: "evalGroup" })} />
45
- </Section>
46
- </Col>
47
- ));
46
+ export default defineReport(async ({ selection }) => {
47
+ const experiments = await ExperimentList.data(selection);
48
+ return (
49
+ <Col>
50
+ <MetricScatter selection={selection} points="experiment" series="agent" x={costUSD} y={passRate} />
51
+ <ExperimentList items={experiments} />
52
+ <Section title="考试成绩单">
53
+ <Scoreboard data={await Scoreboard.data(selection, { rows: "agent", subjects: "evalGroup" })} />
54
+ </Section>
55
+ </Col>
56
+ );
57
+ });
48
58
  ```
49
59
 
50
60
  ```bash
@@ -52,15 +62,17 @@ niceeval show --report reports/exam.tsx # 终端:同一棵树走文本面
52
62
  niceeval view --report reports/exam.tsx # 网页:同一棵树走网页面,attempt 深链直达证据室
53
63
  ```
54
64
 
55
- 注入的上下文只有两样,全部是[结果数据 API](/zh/guides/results-data) 的原语,宿主没有私有通道:`results` 就是 `openResults(".niceeval")` 的返回(实验、历次快照、attempt 级 `diff()`、`trace()` 都在上面);`selection` 就是 `results.latest()` 的返回选集——宿主按「每个实验取最新」挑好的现刻水位,与默认报告同口径,快照在 `selection.snapshots`,挑选警告在 `selection.warnings`。组件的 `data` 函数直接吃选集,警告随行——`RunOverview` 会把残缺快照的警告如实显示,不用你另外接线;默认挑法不合口径时,拿 `results` 自己挑,手工挑的 `Snapshot[]` 数组同样能喂给每个 `data` 函数。
65
+ 注入的上下文只有两样,全部是[结果数据 API](/zh/guides/results-data) 的原语,宿主没有私有通道:`results` 就是 `openResults(".niceeval")` 的返回(实验、历次快照、attempt 级 `diff()`、`trace()` 都在上面);`selection` 是宿主替你挑好的那份结果。挑选规则是:对每个实验、每道 eval,取该实验历史运行里最新的那次判定——只按前缀重跑了一部分 eval,其余 eval 的判定从更早的运行补齐,不会因为一次局部重跑就整体退回某一份残缺快照。默认报告吃的就是这份 `selection`,快照在 `selection.snapshots`,挑选提醒在 `selection.warnings`。默认挑法不合口径时,拿 `results` 自己挑,手工挑的 `Snapshot[]` 数组同样能喂给每个组件。
56
66
 
57
- 命令行的范围先作用在挑选上,报告拿到的就是范围内的水位:位置参数的 eval id 前缀收窄选集覆盖的 eval(覆盖警告的分母同样收窄到范围内),`--run` 把结果根换成指定目录,`--experiment` 让选集只留该实验。`--history` `--report` 互斥——趋势在报告里用 `exp.snapshots` 自己摆;证据切面(`--transcript` / `--trace` / `--diff`)只看证据,不渲染报告。
67
+ 报告文件里有两种数据形态。实体列表的 `.data(selection)` 返回普通数组:`ExperimentListItem[]`、`EvalListItem[]`、`AttemptListItem[]`。报告作者用 JavaScript `.filter()` / `.slice()` 决定展示哪些实体,再把数组作为 `items` 传给列表;组件不藏另一套过滤 DSL。指标图形和汇总组件收算好的 `data`;其中 `MetricScatter` 也提供 `selection` 简写,由宿主在渲染前计算。计算后的组件只渲染传入数据,不碰结果目录。
58
68
 
59
- `<DefaultReport />` props、纯声明:官方水位只读瘦身条目(不碰 diff 这类重工件),宿主总是替你备好,摆不摆在你。它渲染的是**宿主注入的那份选集**的官方水位——跟随命令行范围,但不跟随你在报告里自己另挑的口径;这正是它的意义:当官方锚点,和你的口径并排对照。要「官方摆法 + 自选口径」,用它脚下的同一批组件自己摆——它没有任何私有零件。
69
+ 挑选提醒(覆盖不全、快照过期、有没跑完的运行)不用你在报告里专门摆一个组件来显示:不管你的报告摆了哪些组件,`niceeval show` `niceeval view` 都会把 `selection.warnings` 显示在报告正文上方,不会因为你没摆某一块就静默丢掉。
60
70
 
61
- 页面里的每个组件都是**双面**的:网页面是 React 渲染,终端面是字符渲染,两面吃同一份算好的数据。你在报告文件里只见到组件本身,从不碰某一面——走哪扇门由宿主决定。官方组件覆盖常见摆法——榜单 `MetricTable`、逐题格子 `MetricMatrix`、分组条形 `MetricBars`、成绩单 `Scoreboard`、质量 × 成本 frontier `MetricScatter`、趋势线 `MetricLine`、成对对比 `DeltaTable`、失败清单 `CaseList`——每个组件回答什么问题、怎么调用、终端字符输出长什么样,逐个见[报告组件](/zh/guides/report-components)。它们在宿主里自动接上证据室:网页面的格子深链到 attempt 弹窗,终端面在条目下印出 `niceeval show <eval id>` 下钻命令。
71
+ 命令行的范围先作用在挑选上,报告拿到的就是收窄到这个范围后的 `selection`:位置参数的 eval id 前缀收窄 Selection 覆盖的 eval(覆盖提醒的分母同样收窄到范围内),`--run` 把结果根换成指定目录,`--experiment` Selection 只留该实验。`--history` 与 `--report` 互斥——趋势在报告里用 `exp.snapshots` 自己摆;证据切面(`--eval` / `--execution` / `--diff`)只看证据,不渲染报告。
62
72
 
63
- 默认报告没有特权:`<DefaultReport />` 只是不传 `--report` 时渲染的那份内置报告,本身也是一个普通组件。「官方水位 + 自己的口径」像上面那样把它摆进自己的树;不要官方水位就不摆——组合发生在你的 tsx 里,没有 tab、没有注册表、没有配置。
73
+ 页面里的每个组件都是**双面**的:网页面是 React 渲染,终端面是字符渲染,两面吃同一份算好的数据。实体列表按 experiment → Eval → Attempt 展示事实;指标表、矩阵、条形图、成绩单、散点图、趋势图和差异表展示聚合值。完整清单见[报告组件](/zh/guides/report-components)。网页面的实体、格子和点深链到 Attempt 详情,终端面印出对应的 `niceeval show <eval id>` 下钻命令。
74
+
75
+ 默认报告没有特权:它就是上面导出的那个值 `CostPassRateComparison`,正文是一张 `MetricScatter` 加一份 `ExperimentList`。你的报告和它同级;需要相同摆法时 import 同样的公开组件,不需要就不摆。
64
76
 
65
77
  ## 排版:Row 与 Col 在两个面都成立
66
78
 
@@ -70,6 +82,7 @@ niceeval view --report reports/exam.tsx # 网页:同一棵树走网页面
70
82
  - **`<Row>`**:并排——网页是横向排布,终端是字符分栏;终端宽度不够时自动降级为纵向,不硬挤。
71
83
  - **`<Section title="…">`**:带标题的块,网页是标题层级,终端是标题行加缩进。
72
84
  - **`<Text>`**:说明文字,网页是段落,终端是折行文本。
85
+ - **`<Table>`**:自定义列的表——网页是 `<table>`,终端是按显示宽度对齐的字符表,中文列不撕歪。
73
86
 
74
87
  ```tsx
75
88
  <Row>
@@ -88,9 +101,53 @@ codex 78.0/100 40/50 38/50 │ codex 80% $0.51
88
101
 
89
102
  页面树里只放双面组件和排版原语,不放裸 HTML 标签——终端面没法渲染一个 `<div>`。要自由内容,说明文字用 `<Text>`,更自由的走下面的自定义组件。
90
103
 
104
+ ## 按组摘要:GroupSummary
105
+
106
+ 想把实验按目录前缀分组、每组顶部摆一块紧凑统计(通过率、experiment/eval 数、failed/errored、总成本、最后运行时间),用官方组件 `GroupSummary`。它的计算函数 `GroupSummary.data` 只吃一份已经收窄好的 Selection——不管你怎么分组,把对应组的 Selection 传进去,就是同一套折叠口径(eval 级折叠计票、null-safe 总成本、组内最后运行时间的最大值),不是另一套近似公式。
107
+
108
+ 常见的分法是按 experiment id 的目录前缀(`compare/bub-low` 属于组 `compare`,顶层实验不属于任何组);用 `Selection.filter` 收窄出每组的 Selection,就能一组摆一块:
109
+
110
+ ```tsx
111
+ // reports/groups.tsx —— 按 experiment id 前缀分组,每组顶部摆一块统计
112
+ import { defineReport, Col, Section, GroupSummary } from "niceeval/report";
113
+ import type { Snapshot } from "niceeval/report";
114
+
115
+ // experiment id 的目录前缀当组名:顶层实验(id 无 "/")不属于任何组。
116
+ function groupOf(snapshot: Snapshot): string | undefined {
117
+ const parts = snapshot.experimentId.split("/");
118
+ return parts.length > 1 ? parts.slice(0, -1).join("/") : undefined;
119
+ }
120
+
121
+ export default defineReport(async ({ selection }) => {
122
+ const groups = [...new Set(selection.snapshots.map(groupOf))];
123
+ return (
124
+ <Col>
125
+ {await Promise.all(
126
+ groups.map(async (key) => {
127
+ const scoped = selection.filter((s) => groupOf(s) === key);
128
+ const summary = <GroupSummary data={await GroupSummary.data(scoped)} />;
129
+ return key === undefined ? summary : (
130
+ <Section key={key} title={key}>{summary}</Section>
131
+ );
132
+ }),
133
+ )}
134
+ </Col>
135
+ );
136
+ });
137
+ ```
138
+
139
+ ```text
140
+ $ niceeval show --report reports/groups.tsx
141
+ compare
142
+ Pass rate 60% · 2 experiments · 6 evals · failed 1 · errored 1 · $1.50
143
+ latest 2026-07-01T11:30:00Z
144
+ ```
145
+
146
+ `GroupSummary` 之后想接着摆散点图或实验列表,把同一份 `scoped` Selection 传给散点图,再用 `await ExperimentList.data(scoped)` 得到可自行过滤的 experiment 数组。想自由换行维度和指标列,再用下面的通用 `MetricTable`。
147
+
91
148
  ## 换口径:自定义指标
92
149
 
93
- 默认报告里那张逐实验指标表的口径拆开就是 `MetricTable.data(selection, { rows: "experiment", columns: [passRate, costUSD, durationMs] })`——每个组件的数据算法挂在它自己身上,组件名后打个点就找到;换口径只是把 columns 换成自己的指标:
150
+ 实验、Eval、Attempt 的固定诊断字段由三个实体列表承接。下面的例子是另一种需求:用通用 `MetricTable` 自由换指标口径——每个组件的数据算法都挂在自己身上,换口径只需换 columns
94
151
 
95
152
  ```tsx
96
153
  // reports/golf.tsx —— code-golf:谁写出能用的代码,谁写得短
@@ -105,7 +162,7 @@ const codeLines = defineMetric({
105
162
  label: "代码行数",
106
163
  better: "lower",
107
164
  unit: "lines",
108
- where: (a) => a.result.outcome === "passed",
165
+ where: (a) => a.result.verdict === "passed",
109
166
  async value(attempt) {
110
167
  const diff = await attempt.diff();
111
168
  if (!diff) return null;
@@ -136,7 +193,41 @@ codex 80% 355 lines $0.51
136
193
 
137
194
  两个面继承同一套诚实契约:排序方向随指标的 `better`,覆盖不全的格子带 `12/15` 角标(15 个 attempt 里 12 个测得了该指标),缺数据渲染成 `—` 而不是 0。指标只定义一次,两个面共用——人在网页上看到的数字,就是 agent 在 stdout 里读到的数字,判断口径永远一致。给 agent 的指引也只多一行:跑 `niceeval show --report reports/golf.tsx`,读 stdout。
138
195
 
139
- ## 变量来自配置,不来自命名
196
+ `label` 可以是一份文案,也可以按语言给:`label: { en: "Code lines", "zh-CN": "代码行数" }`——查看器界面切语言时,按语言给的 label 跟着切;只给一份就两种语言都用它。指标算出来的数字本身不分语言。
197
+
198
+ 内置指标里 `passRate` / `costUSD` / `durationMs` / `tokens` 只读 attempt 自带的判定、用量这些字段,任何一份结果目录都算得出。`turns`(agent 的总轮次)不一样,它读 `attempt.o11y()`——这份数据发布时不一定随行,`copySnapshots` 要显式把 `"o11y"` 加进 `artifacts` 才带得上(见[结果数据 API](/zh/guides/results-data)的「发布」一节),漏了就渲染成 `—`,不是 0。自己写的指标只要读了 `o11y()` / `diff()` 这类 artifact(就像上面 `codeLines` 读 `attempt.diff()`),发布前都要过一遍同样的检查。
199
+
200
+ ## 换分组:三种维度
201
+
202
+ 维度决定分组——表的行、矩阵的行列、散点的点。每个维度槽收三种值:
203
+
204
+ - **内置维度**:`"agent"`、`"model"`、`"experiment"`、`"eval"`、`"evalGroup"`、`"snapshot"`,结果里现成的身份字段。
205
+ - **自定义维度**:`{ name, of }`,一个纯函数吃 attempt、吐组名。定义只住在报告文件里,不用改任何 experiment 文件。
206
+ - **`flag()`**:引用 experiment 用 `flags` 声明的变量。
207
+
208
+ 选哪种看变量住在哪:分组能从结果已有数据**算**出来的,用自定义维度——报告只管「怎么摆」,不反过来要求 experiment 为一种摆法改配置;变量是 experiment 本身的**配置**——scaling 档位、要当数值轴用、要在 experiment 定义处一眼看到的——才声明成 `flags`,报告用 `flag()` 引用。数值轴(`MetricLine` 的 `x`)只收 `flag()`:刻度必须来自声明的数值,函数派生的组名当不了刻度。
209
+
210
+ ### 自定义维度:从已有数据派生分组
211
+
212
+ 比如把不同模型折成厂商,按厂商比通过率——分组从 `result.model` 算出来,experiment 文件一个都不用碰:
213
+
214
+ ```tsx
215
+ import type { Dimension } from "niceeval/report";
216
+
217
+ const vendor: Dimension = {
218
+ name: "vendor",
219
+ of: (a) => (a.result.model?.startsWith("gpt-") ? "OpenAI" : "Anthropic"),
220
+ };
221
+
222
+ <MetricTable data={await MetricTable.data(selection, {
223
+ rows: vendor,
224
+ columns: [passRate, costUSD],
225
+ })} />
226
+ ```
227
+
228
+ `of` 能读 attempt 的全部已有数据:`evalId`、`experimentId`、`result` 里的判定与 experiment 元数据。它只能派生、不能补造:如果两组 experiment 的差别只体现在文件命名里(`bub-baseline.ts` / `bub-mempal.ts`),`of` 就只能去解析这个名字——命名约定一改,分组静默散掉。这种时候变量该搬回配置,往下看。
229
+
230
+ ### `flag()`:变量来自配置,不来自命名
140
231
 
141
232
  画「并行 agent 数 × 模拟延迟 × 得分」这类 scaling 趋势时,图上的变量不该编码进 experiment id(`ultra-16agents-300ms`),再在报告里解析字符串抠出来——那是约定不是配置,改个命名整张图就散。变量的家是 experiment 文件的 `flags`:一文件一个档位,把变量声明成数据:
142
233
 
@@ -161,15 +252,80 @@ export default defineExperiment({
161
252
  })} />
162
253
  ```
163
254
 
164
- `flag()` 用在维度槽(`series` / `rows` / `columns` / `points`)就按声明值分组,用在轴槽(`x`)就要求数值并驱动刻度;内置维度(`agent`、`model`、`experiment`、`evalGroup`……)覆盖不了的变量都走它。没有声明该 flag 的 experiment 不猜:分组时如实归入「未配置」一组,作轴时该点不画、注脚报数。flags 随快照落盘,所以历史 run 也能按当时声明的变量重摆——报告读的是配置的事实,不是命名的巧合。
255
+ `flag()` 用在维度槽(`series` / `rows` / `columns` / `points`)就按声明值分组,用在轴槽(`x`)就要求数值并驱动刻度。没有声明该 flag 的 experiment 不猜:分组时如实归入「未配置」一组,作轴时该点不画、注脚报数。flags 随快照落盘,所以历史 run 也能按当时声明的变量重摆——报告读的是配置的事实,不是命名的巧合。
256
+
257
+ ## 换形态:表格用 Table,其余自己画
165
258
 
166
- ## 换形态:自定义组件,一个定义两个面
259
+ 官方组件摆不出来的展示分两种,各走一条路:是一张表,用排版原语 `<Table>`;不是表(通过率条形、预算燃尽、项目自己的徽章),用 `defineComponent` 写自己的双面组件。
167
260
 
168
- 官方组件表达不了的展示——通过率条形、预算燃尽、项目自己的徽章——用 `defineComponent` 声明两个渲染面,和 `defineExperiment` / `defineMetric` / `defineReport` 同一个家族:
261
+ ### 一张表:`<Table>`
262
+
263
+ 列是你定的,格子是你算好的显示值,`<Table>` 负责把网页和终端两个面都排整齐:
264
+
265
+ ```tsx
266
+ // reports/cost-board.tsx
267
+ import { defineReport, Col, Table, MetricTable, costUSD, passRate } from "niceeval/report";
268
+
269
+ export default defineReport(async ({ selection }) => {
270
+ const board = await MetricTable.data(selection, {
271
+ rows: "agent",
272
+ columns: [passRate, costUSD],
273
+ });
274
+ return (
275
+ <Col>
276
+ <Table
277
+ columns={[
278
+ { key: "agent", header: "Agent" },
279
+ { key: "pass", header: "通过率", align: "right" },
280
+ { key: "cost", header: "成本", align: "right" },
281
+ ]}
282
+ rows={board.rows.map((r) => ({
283
+ key: r.key,
284
+ cells: {
285
+ agent: r.key,
286
+ pass: r.cells[passRate.name].display,
287
+ // 缺数据交 null,组件渲染成 —;不要自己填 0
288
+ cost: r.cells[costUSD.name].value === null ? null : r.cells[costUSD.name].display,
289
+ },
290
+ }))}
291
+ />
292
+ </Col>
293
+ );
294
+ });
295
+ ```
296
+
297
+ ```text
298
+ $ niceeval show --report reports/cost-board.tsx
299
+ Agent 通过率 成本
300
+ bub 87% $0.42
301
+ codex 80% $0.51
302
+ 克劳德 — —
303
+ ```
304
+
305
+ 列宽按**终端显示宽度**算:一个汉字占 2 列,所以中文 agent 名、中文题目名都不会把表撕歪。`align: "right"` 让数字列右对齐。格子给 `null` 就渲染 `—`,不补 0。行上带 `locator` 就多出一列 attempt,网页面点进证据室、终端面把 locator 交给 `niceeval show`。完整字段见[报告组件](/zh/guides/report-components)的「表格」一节。
306
+
307
+ ### 不是表:`defineComponent` 加文本排版函数
308
+
309
+ `defineComponent` 声明两个渲染面,和 `defineExperiment` / `defineMetric` / `defineReport` 同一个家族。终端面要自己排字符,用 `niceeval/report` 导出的这组函数——官方组件排的就是这几把尺子:
310
+
311
+ | 函数 | 干什么 |
312
+ | --- | --- |
313
+ | `stringWidth(text)` | 显示宽度:CJK 和全角字符记 2 列,其余记 1 列 |
314
+ | `padEnd(text, width)` | 按显示宽度在右侧补齐(左对齐) |
315
+ | `padStart(text, width)` | 按显示宽度在左侧补齐(右对齐,数字列用) |
316
+ | `wrapText(text, width)` | 按显示宽度折行,返回若干行 |
317
+ | `indent(block, prefix)` | 每行加缩进 |
318
+ | `bar(ratio, width)` | 字符条:`█` 填充、`░` 补齐到 `width` |
319
+ | `columns(blocks, widths, separator?)` | 多块并排 |
320
+
321
+ **不要用 `String.prototype.padEnd` / `padStart` 对齐终端输出。** 它们数的是 UTF-16 码元,不是显示列宽:一个汉字占 2 列,却只算 1 个码元。用它们补齐,中文一进来列就错位,而中文 agent 名和中文题目名恰恰是最常见的情形。列宽也要随内容和 `ctx.width` 算,不要硬编码一个数字。
169
322
 
170
323
  ```tsx
171
324
  // reports/passbars.tsx
172
- import { defineReport, defineComponent, Col, Style, MetricTable, passRate } from "niceeval/report";
325
+ import {
326
+ defineReport, defineComponent, Col, Style, MetricTable,
327
+ bar, padEnd, stringWidth, passRate,
328
+ } from "niceeval/report";
173
329
 
174
330
  interface BarRow { key: string; ratio: number | null; display: string }
175
331
 
@@ -188,9 +344,13 @@ const PassBars = defineComponent<{ rows: BarRow[] }>({
188
344
  );
189
345
  },
190
346
  text({ rows }, { width }) {
191
- const bar = (n: number) => "█".repeat(Math.round(n * 10)).padEnd(10, "░");
347
+ const label = Math.max(...rows.map((r) => stringWidth(r.key))); // 列宽随内容
348
+ const barWidth = Math.min(20, width - label - 8); // 也随可用列宽
192
349
  return rows
193
- .map((r) => `${r.key.padEnd(8)} ${r.ratio === null ? "—".padEnd(10) : bar(r.ratio)} ${r.display}`)
350
+ .map((r) => {
351
+ const chart = r.ratio === null ? padEnd("—", barWidth) : bar(r.ratio, barWidth);
352
+ return `${padEnd(r.key, label)} ${chart} ${r.display}`;
353
+ })
194
354
  .join("\n");
195
355
  },
196
356
  });
@@ -213,9 +373,9 @@ export default defineReport(async ({ selection }) => {
213
373
 
214
374
  ```text
215
375
  $ niceeval show --report reports/passbars.tsx
216
- bub █████████░ 87%
217
- codex ████████░░ 80%
218
- claude
376
+ bub █████████████████░░░ 87%
377
+ codex ████████████████░░░░ 80%
378
+ 克劳德
219
379
  ```
220
380
 
221
381
  组件的契约:
@@ -234,10 +394,10 @@ claude — —
234
394
  niceeval view --report reports/exam.tsx --out site
235
395
  ```
236
396
 
237
- 产物是纯静态文件:你的报告页是首页,证据室(transcript、trace、代码视图)在同一站内,报告里的每个数字点进去就是证据。组件不 hydrate,产物不需要任何客户端 JS,交给任何静态托管即可。CI 上没有 `.niceeval/` 时,先用 `copySnapshots` 把快照瘦身进仓库再导出,流程见[查看结果](/zh/guides/viewing-results#导出与静态托管)。
397
+ 产物是纯静态文件:你的报告页是首页,证据室(transcript、trace、代码视图)在同一站内,报告里的每个数字点进去就是证据。组件不 hydrate;页面内联一小段官方脚本,提供表头排序、行过滤、图表悬停这些浏览操作,浏览器禁用 JS 时页面仍完整可读。交给任何静态托管即可。CI 上没有 `.niceeval/` 时,先用 `copySnapshots` 把快照瘦身进仓库再导出,流程见[查看结果](/zh/guides/viewing-results#导出与静态托管)。
238
398
 
239
399
  双面组件的网页面就是普通 React 组件,`data` 函数就是普通 TS 函数——想把某一块指标表嵌进已有的内部面板,import 组件喂数据就行。那是零件的复用,不是另一套报告系统:报告的家在官方宿主。算数据与渲染分离部署时(CI 落 JSON、另一个应用 fetch),两侧锁同一个 niceeval 版本是硬要求——组件数据不带版本戳,兼容性跟随包版本。
240
400
 
241
401
  ## 界线:内置命令不长配置
242
402
 
243
- 报告槽只有一个,`--report` 收显式文件路径——没有 `reports/` 目录自动发现、没有插件注册表、没有配置文件。自定义指标和自定义组件都住在你的报告文件里,随文件一起递入,宿主不为它们长任何注册面。默认报告只是报告槽的出厂填充,你的报告和它是同级实现。
403
+ 一次只渲染一份报告,`--report` 收显式文件路径——没有 `reports/` 目录自动发现、没有插件注册表、没有配置文件。自定义指标和自定义组件都住在你的报告文件里,随文件一起递入,宿主不为它们长任何注册面。不传 `--report` 时渲染的就是内置的 `CostPassRateComparison`,你的报告和它是同级实现。
@@ -37,6 +37,12 @@ npx niceeval exp compare-models # 把同组各 model 并排出报告
37
37
 
38
38
  这样每个配置独立成文件:可命名、可 diff、可单独 review,"这一组就是对照"在文件结构上就讲清楚了。
39
39
 
40
+ 某一格结果反常、需要单独复现时,用该配置的完整 id(`组/文件名`)精确只跑这一格,不用先跑完整组:
41
+
42
+ ```bash
43
+ npx niceeval exp compare-models/gpt-5.4
44
+ ```
45
+
40
46
  `defineExperiment` 的字段怎么写、`flags` 怎么透传到 adapter,见[写实验](/zh/guides/write-experiment)。
41
47
 
42
48
  ## 适合比较什么
@@ -45,7 +51,7 @@ npx niceeval exp compare-models # 把同组各 model 并排出报告
45
51
  - 不同模型(Tier 1 接入即可:应用接口暴露模型选择,`model` 经 `ctx.model` 透传)。
46
52
  - 不同 prompts 或 feature flags(要求 Tier 3 接入:变体在应用内部,需要应用把它暴露成 experiment 可选的 flag,经 `flags` → `ctx.flags` 透传)。
47
53
  - 不同 sandbox 后端。
48
- - 不同运行环境条件(比如装不装某个记忆工具的二进制、有没有预置状态):环境差异写在 `sandbox` spec 的 `.setup()` / `.teardown()` 钩子里,一个变体一个 experiment 文件,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-backends#环境钩子)。
54
+ - 不同运行环境条件(比如装不装某个记忆工具的二进制、有没有预置状态):环境差异写在 `sandbox` spec 的 `.setup()` / `.teardown()` 钩子里,一个变体一个 experiment 文件,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-providers#环境钩子)。
49
55
  - 同一任务的 pass@N。
50
56
 
51
57
  Tier 1 / Tier 2 / Tier 3 的定义见 [Tier](/zh/concepts/tier)。
@@ -6,7 +6,7 @@ description: "用 .eval.ts 给 coding agent 准备隔离 workspace、发送真
6
6
 
7
7
  评估 coding agent 时,单纯检查一段回复不够。你通常要给它一个真实项目,让它读写文件、运行命令、提交改动,然后检查产物是否正确。[NiceEval](https://niceeval.com/) 的当前推荐写法是:用普通 `.eval.ts` 文件显式准备 sandbox workspace、发送任务并断言结果。
8
8
 
9
- 可运行参考见 [`examples/zh/coding-agent-skill`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/coding-agent-skill)
9
+ 可运行参考见 [coding-agent-skill](https://github.com/CorrectRoadH/coding-agent-skill)(独立仓库)。
10
10
 
11
11
  ## 推荐目录结构
12
12
 
@@ -117,8 +117,10 @@ t.check(result.stdout.trim(), includes(/ok/));
117
117
  也可以运行项目自己的 test/lint/build 脚本:
118
118
 
119
119
  ```ts
120
+ import { commandSucceeded } from "niceeval/expect";
121
+
120
122
  const result = await t.sandbox.runCommand("npm", ["test"]);
121
- t.check(result.exitCode, includes(0));
123
+ t.check(result, commandSucceeded());
122
124
  ```
123
125
 
124
126
  ## 用 experiment 做对照组
@@ -14,7 +14,8 @@ description: "NiceEval 内置的 Sandbox 和非 Sandbox 适配器分别是什么
14
14
 
15
15
  - **鉴权**:`ANTHROPIC_API_KEY`(工厂参数 `apiKey` 可覆盖),可选 `ANTHROPIC_BASE_URL`(工厂参数 `baseUrl`)。
16
16
  - **装 MCP server**:`mcpServers` 配置项,`setup` 阶段写进沙箱里用户级的 `~/.claude.json`(顶层 `mcpServers` 字段)。
17
- - **装 Skill**:`skills: ["org/repo"]`(GitHub 仓库),`setup` 阶段跑 `npx skills add <org/repo> -y -a claude-code`(`-y -a` 避免无 tty 的沙箱里卡在交互式的 agent 选择框),结果写进沙箱工作区的 `skills-lock.json`,claude CLI 启动时自动读取(原生 `Skill` 工具,可用 `t.calledTool("Skill", { input: { skill } })` 断言,不是 `t.loadedSkill()`——那是给 `load_skill` 这个不同协议约定的语法糖)。
17
+ - **装 Skill**:`skills: SkillSpec[]`——本地 Skill(`{ kind: "local", path }`,从项目根读文件或目录)或 Repo Skill(`{ kind: "repo", source, ref, skills }`,可钉 commit/tag、可只启用多 Skill 仓库里的一部分)。装进沙箱的 project `.claude/skills/<name>/`,claude CLI 原生发现(原生 `Skill` 工具调用被 adapter 归一为 `skill.loaded` 事件,不重复记成工具调用;用 `t.loadedSkill()` 断言,不是 `t.calledTool("Skill", ...)`)。
18
+ - **装原生 Plugin**:`plugins: ClaudeCodePluginSpec[]`,每一项声明 Marketplace 连接(`name` / `source` / 可选 `ref`)和其中的 Plugin 名。这个类型只属于 claude-code,传不进 codex。
18
19
  - **tracing**:claude CLI 的 beta 原生遥测(`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA`),span 只有结构和计时,细节见 [OTel 接入](/zh/guides/connect-otel)。
19
20
 
20
21
  ```ts
@@ -27,7 +28,16 @@ export default defineExperiment({
27
28
  mcpServers: [
28
29
  { name: "browser", command: "npx", args: ["-y", "@anthropic/mcp-browser"] },
29
30
  ],
30
- skills: ["Effect-TS/skills"],
31
+ skills: [
32
+ { kind: "repo", source: "Effect-TS/skills", ref: "8f3c1a2", skills: ["effect"] },
33
+ { kind: "local", path: "skills/repository-guide.md" },
34
+ ],
35
+ plugins: [
36
+ {
37
+ marketplace: { name: "acme", source: "acme/claude-code-plugins", ref: "v1.3.0" },
38
+ name: "safe-shell",
39
+ },
40
+ ],
31
41
  }),
32
42
  model: "claude-sonnet-4-6",
33
43
  sandbox: dockerSandbox(),
@@ -43,7 +53,8 @@ export default defineExperiment({
43
53
  是复数 `mcp_servers`:单数 `[mcp_server.x]` 会被 codex CLI 静默忽略,MCP 压根挂不上,且不会报错——自查用 `codex mcp list`。
44
54
  </Warning>
45
55
 
46
- - **装 Skill**:`skills: ["org/repo"]`,和 claude-code 一样走 `npx skills add <org/repo> -y -a codex`。codex 没有 claude-code 那种原生 Skill 工具——装的技能落在 skills 包的通用目录(`.agents/skills/<name>`),codex CLI 不会主动去读,要在 prompt 里明说"检查仓库里有没有相关的 skill/guide 文件",agent 才会用 shell 命令读进上下文;断言"用没用到"只能看它是否真的执行过读那个文件的命令,没有工具调用可以直接认。
56
+ - **装 Skill**:`skills: SkillSpec[]`,与 claude-code 同一个类型。装进 `.agents/skills/<name>/`,并把发现指引写进 AGENTS.md——codex 没有 claude-code 那种原生 Skill 工具,只把文件装进去它不会主动去读;断言"用没用到"看它是否真的执行过读那个文件的 shell 命令,没有工具调用可以直接认。
57
+ - **装原生 Plugin**:`plugins: CodexPluginSpec[]`,声明 Marketplace 连接(`name` / `source` / 可选 `ref`)和其中的 Plugin 名。这个类型只属于 codex,传不进 claude-code。
47
58
  - **tracing**:内置,通过 `config.toml` 的 `[otel.trace_exporter.otlp-http]` 段配置,协议 `http/json`。
48
59
 
49
60
  ```ts
@@ -56,7 +67,13 @@ export default defineExperiment({
56
67
  mcpServers: [
57
68
  { name: "browser", command: "npx", args: ["-y", "@anthropic/mcp-browser"] },
58
69
  ],
59
- skills: ["Effect-TS/skills"],
70
+ skills: [{ kind: "repo", source: "Effect-TS/skills", ref: "8f3c1a2", skills: ["effect"] }],
71
+ plugins: [
72
+ {
73
+ marketplace: { name: "acme", source: "acme/codex-plugins", ref: "8f3c1a2" },
74
+ name: "repo-map",
75
+ },
76
+ ],
60
77
  }),
61
78
  model: "gpt-5.4",
62
79
  sandbox: dockerSandbox(),
@@ -66,8 +83,9 @@ export default defineExperiment({
66
83
  ### bub
67
84
 
68
85
  - **鉴权**:`BUB_API_KEY` + `BUB_API_BASE`(OpenAI 兼容代理),工厂参数 `apiKey` / `apiBase` 可覆盖。
69
- - **装插件**:`pythonPlugins: string[]`(pip 包名或 git URL),`setup` 阶段以 `uv tool install --with <pkg>` 追加进 bub 的 Python 工具环境。
70
- - bub 目前不支持 `mcpServers` / `skills` 配置项——没有对应的包装。
86
+ - **装 Skill**:`skills: SkillSpec[]`,与另外两个 Adapter 同一个类型。装进 `.agents/skills/<name>/`,发现指引写进 AGENTS.md。
87
+ - **装插件**:`pythonPlugins: PythonPluginSpec[]`(`{ package }`:PyPI 包、版本约束或 git URL),`setup` 阶段进 `uv tool install … --with <package>`。这个类型只属于 bub;package 集合进安装 checkpoint key,插件不同的两个变体不会复用同一份安装缓存。
88
+ - bub 没有 `mcpServers`——MCP 只属于支持它的 Adapter,Config 上压根没有这个字段。
71
89
  - **安装方式**:走 `uv tool install`(PyPI 包,不是 npm 包),首次安装会建 checkpoint 缓存加速后续沙箱。
72
90
  - **tracing**:内置,通过环境变量注入,协议 `http/protobuf`。
73
91
 
@@ -78,7 +96,8 @@ import { dockerSandbox } from "niceeval/sandbox";
78
96
 
79
97
  export default defineExperiment({
80
98
  agent: bubAgent({
81
- pythonPlugins: ["bub-plugin-memory"],
99
+ skills: [{ kind: "local", path: "skills/repository-guide.md" }],
100
+ pythonPlugins: [{ package: "bub-plugin-memory==1.3.0" }],
82
101
  }),
83
102
  model: "gpt-5.4",
84
103
  sandbox: dockerSandbox(),
@@ -91,11 +110,14 @@ export default defineExperiment({
91
110
  |---|---|---|---|
92
111
  | 鉴权环境变量 | `ANTHROPIC_API_KEY` | `CODEX_API_KEY` | `BUB_API_KEY` + `BUB_API_BASE` |
93
112
  | MCP server | ✅ `mcpServers` | ✅ `mcpServers` | ❌ |
94
- | Skill | ✅ `skills`(GitHub `org/repo`) | ✅ `skills`(GitHub `org/repo`) | |
95
- | 插件 | | | ✅ `pythonPlugins`(pip / git URL) |
113
+ | Skill | ✅ `skills: SkillSpec[]`(原生发现) | ✅ `skills: SkillSpec[]`(+ 发现指引) | `skills: SkillSpec[]`(+ 发现指引) |
114
+ | 原生 Plugin | `plugins: ClaudeCodePluginSpec[]` | ✅ `plugins: CodexPluginSpec[]` | |
115
+ | Python Plugin | ❌ | ❌ | ✅ `pythonPlugins: PythonPluginSpec[]` |
96
116
  | tracing | ✅(beta,仅结构与计时) | ✅ | ✅ |
97
117
  | 安装方式 | npm 全局包 | npm 全局包 | `uv tool install`(PyPI) |
98
118
 
119
+ 装了什么有据可查:Adapter 在 `setup` 收尾把安装清单写进沙箱的 `__niceeval__/agent-setup.json`,运行器把它存成 Attempt Artifact `agent-setup.json`(库里读 `attempt.agentSetup()`)。清单只记来源、ref、Skill/Plugin 名和解析出的版本,不记 API key 或环境变量值。
120
+
99
121
  ## 非 Sandbox 适配器
100
122
 
101
123
  被测对象不需要隔离工作区——已经部署的 HTTP 服务、自研 agent loop——用非 Sandbox 适配器无侵入接入,不需要 Docker。
@@ -4,13 +4,13 @@ sidebarTitle: "CI 发布报告"
4
4
  description: "把报告站接进 CI:.niceeval/ 提交进仓库,构建命令一行 view --out,push 即发布。发布的站和本地 niceeval view 完全一样;零可读结果时构建失败,保住上一次部署。"
5
5
  ---
6
6
 
7
- `niceeval view --out <目录>` 把查看器导出成一个纯静态目录:报告、快照列表、transcript、trace 瀑布,和本地 `niceeval view` 看到的完全一样(导出行为见[查看结果 · 导出与静态托管](/zh/guides/viewing-results#导出与静态托管))。CI 发布只需要让结果数据到构建机手里——把 `.niceeval/` 提交进仓库,仓库本身就是数据源。本地跑完 eval,提交并 push 就是发布,不需要任何脚本或本地构建命令。
7
+ `niceeval view --out <目录>` 把查看器导出成一个纯静态目录:报告、结果快照列表、transcript、trace 瀑布,和本地 `niceeval view` 看到的完全一样(导出行为见[查看结果 · 导出与静态托管](/zh/guides/viewing-results#导出与静态托管))。CI 发布只需要让结果数据到构建机手里——把 `.niceeval/` 提交进仓库,仓库本身就是数据源。本地跑完 eval,提交并 push 就是发布,不需要任何脚本或本地构建命令。
8
8
 
9
9
  ## `.gitignore` 只排除 `diff.json`
10
10
 
11
11
  ```gitignore
12
12
  # .niceeval/ 提交进仓库:它是线上报告的数据源,CI 构建时直接从这里生成整站。
13
- # 只排除 diff.json —— 单轮可达上百 MB,查看器也不读它。
13
+ # 只排除 diff.json —— 单轮可达上百 MB,体积太大不适合进仓库;Attempt 详情仍会读取它,只是不随静态导出复制。
14
14
  .niceeval/**/diff.json
15
15
  ```
16
16
 
@@ -20,7 +20,7 @@ description: "把报告站接进 CI:.niceeval/ 提交进仓库,构建命令一
20
20
  npx niceeval view --out site
21
21
  ```
22
22
 
23
- `view` 对零可读结果直接报错、非零退出,不会导出一张空报告——`.niceeval/` checkout 坏掉,或所有落盘与当前 niceeval 的 schemaVersion 不兼容被整批跳过时,构建失败,Vercel / GitHub Pages 保留上一次部署。错误逐条列出被跳过的 run 目录与原因,schemaVersion 场景还给出能直接查看旧落盘的 `npx niceeval@<版本> view` 命令。
23
+ `view` 对零可读结果直接报错、非零退出,不会导出一张空报告——`.niceeval/` checkout 坏掉,或所有落盘与当前 niceeval 的 schemaVersion 不兼容被整批跳过时,构建失败,Vercel / GitHub Pages 保留上一次部署。错误逐条列出被跳过的快照目录与原因,schemaVersion 场景还给出能直接查看旧落盘的 `npx niceeval@<版本> view` 命令。
24
24
 
25
25
  ## 发布自定义报告
26
26
 
@@ -84,7 +84,7 @@ jobs:
84
84
  uses: actions/deploy-pages@v4
85
85
  ```
86
86
 
87
- 日常循环从此只有两步:本地跑 eval,提交 `.niceeval/` 并 push。发布出去的站带完整历史——同事打开链接,看到的和你本地 `niceeval view` 一模一样;仓库、本地查看器、线上报告三处同一个真相源。仓库体积随落盘增长(`diff.json` 已排除,增长主要来自 transcript 和 trace 工件),不想保留的旧 run 直接删对应目录即可,查看器和报告跟着仓库走。
87
+ 日常循环从此只有两步:本地跑 eval,提交 `.niceeval/` 并 push。发布出去的站带完整历史——同事打开链接,看到的和你本地 `niceeval view` 一模一样;仓库、本地查看器、线上报告三处同一个真相源。仓库体积随落盘增长(`diff.json` 已排除,增长主要来自 transcript 和 trace artifact),不想保留的旧快照直接删对应快照目录即可,查看器和报告跟着仓库走。
88
88
 
89
89
  ## 发布的是完整证据
90
90