niceeval 0.5.3 → 0.6.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 (344) hide show
  1. package/INDEX.md +55 -0
  2. package/dist/agents/types.d.ts +317 -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/execution-tree.d.ts +103 -0
  9. package/dist/o11y/otlp/parse.d.ts +3 -0
  10. package/dist/o11y/otlp/receiver.d.ts +21 -0
  11. package/dist/o11y/otlp/select.d.ts +22 -0
  12. package/dist/o11y/otlp/turn-otel.d.ts +43 -0
  13. package/dist/o11y/types.d.ts +190 -0
  14. package/dist/report/aggregate.d.ts +57 -0
  15. package/dist/report/aggregate.js +212 -0
  16. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
  17. package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
  18. package/dist/report/built-ins/index.d.ts +1 -0
  19. package/dist/report/built-ins/index.js +2 -0
  20. package/dist/report/components.d.ts +178 -0
  21. package/dist/report/components.js +132 -0
  22. package/dist/report/compute.d.ts +106 -0
  23. package/dist/report/compute.js +618 -0
  24. package/dist/report/flag.d.ts +6 -0
  25. package/dist/report/flag.js +8 -0
  26. package/dist/report/format.d.ts +32 -0
  27. package/dist/report/format.js +126 -0
  28. package/dist/report/index.d.ts +19 -0
  29. package/dist/report/index.js +30 -0
  30. package/dist/report/load.d.ts +12 -0
  31. package/dist/report/load.js +56 -0
  32. package/dist/report/locale.d.ts +83 -0
  33. package/dist/report/locale.js +160 -0
  34. package/dist/report/metrics.d.ts +21 -0
  35. package/dist/report/metrics.js +115 -0
  36. package/dist/report/primitives.d.ts +64 -0
  37. package/dist/report/primitives.js +113 -0
  38. package/dist/report/react/AttemptList.d.ts +23 -0
  39. package/dist/report/react/AttemptList.js +20 -0
  40. package/dist/report/react/DeltaTable.d.ts +8 -0
  41. package/dist/report/react/DeltaTable.js +24 -0
  42. package/dist/report/react/EvalList.d.ts +10 -0
  43. package/dist/report/react/EvalList.js +0 -0
  44. package/dist/report/react/ExperimentList.d.ts +10 -0
  45. package/dist/report/react/ExperimentList.js +15 -0
  46. package/dist/report/react/GroupSummary.d.ts +8 -0
  47. package/dist/report/react/GroupSummary.js +8 -0
  48. package/dist/report/react/MetricBars.d.ts +10 -0
  49. package/dist/report/react/MetricBars.js +48 -0
  50. package/dist/report/react/MetricLine.d.ts +9 -0
  51. package/dist/report/react/MetricLine.js +68 -0
  52. package/dist/report/react/MetricMatrix.d.ts +10 -0
  53. package/dist/report/react/MetricMatrix.js +23 -0
  54. package/dist/report/react/MetricScatter.d.ts +9 -0
  55. package/dist/report/react/MetricScatter.js +100 -0
  56. package/dist/report/react/MetricTable.d.ts +11 -0
  57. package/dist/report/react/MetricTable.js +26 -0
  58. package/dist/report/react/RunOverview.d.ts +8 -0
  59. package/dist/report/react/RunOverview.js +12 -0
  60. package/dist/report/react/Scoreboard.d.ts +8 -0
  61. package/dist/report/react/Scoreboard.js +26 -0
  62. package/dist/report/react/cell.d.ts +9 -0
  63. package/dist/report/react/cell.js +9 -0
  64. package/dist/report/react/chart-math.d.ts +14 -0
  65. package/dist/report/react/chart-math.js +68 -0
  66. package/dist/report/react/colors.d.ts +14 -0
  67. package/dist/report/react/colors.js +45 -0
  68. package/dist/report/react/fixtures.d.ts +19 -0
  69. package/dist/report/react/fixtures.js +393 -0
  70. package/dist/report/react/format.d.ts +3 -0
  71. package/dist/report/react/format.js +7 -0
  72. package/dist/report/react/index.d.ts +6 -0
  73. package/dist/report/react/index.js +14 -0
  74. package/dist/report/report.d.ts +24 -0
  75. package/dist/report/report.js +49 -0
  76. package/dist/report/text/faces.d.ts +29 -0
  77. package/dist/report/text/faces.js +503 -0
  78. package/dist/report/text/layout.d.ts +75 -0
  79. package/dist/report/text/layout.js +173 -0
  80. package/dist/report/text/plot.d.ts +37 -0
  81. package/dist/report/text/plot.js +81 -0
  82. package/dist/report/text/table.d.ts +8 -0
  83. package/dist/report/text/table.js +86 -0
  84. package/dist/report/tree.d.ts +99 -0
  85. package/dist/report/tree.js +228 -0
  86. package/dist/report/types.d.ts +420 -0
  87. package/dist/report/types.js +4 -0
  88. package/dist/report/web.d.ts +15 -0
  89. package/dist/report/web.js +37 -0
  90. package/dist/results/annotated-source.d.ts +61 -0
  91. package/dist/results/attempt-evidence.d.ts +69 -0
  92. package/dist/results/attempt-source.d.ts +15 -0
  93. package/dist/results/locator.d.ts +75 -0
  94. package/dist/results/locator.js +0 -0
  95. package/dist/results/select.d.ts +45 -0
  96. package/dist/results/select.js +230 -0
  97. package/dist/results/source-hash.d.ts +9 -0
  98. package/dist/results/types.d.ts +204 -0
  99. package/dist/runner/eval-source.d.ts +22 -0
  100. package/dist/runner/fingerprint.d.ts +23 -0
  101. package/dist/runner/types.d.ts +332 -0
  102. package/dist/sandbox/docker-stream.d.ts +24 -0
  103. package/dist/sandbox/docker.d.ts +84 -0
  104. package/dist/sandbox/e2b.d.ts +31 -0
  105. package/dist/sandbox/errors.d.ts +4 -0
  106. package/dist/sandbox/local-files.d.ts +6 -0
  107. package/dist/sandbox/paths.d.ts +4 -0
  108. package/dist/sandbox/registry.d.ts +13 -0
  109. package/dist/sandbox/resolve.d.ts +40 -0
  110. package/dist/sandbox/retry.d.ts +10 -0
  111. package/dist/sandbox/shell.d.ts +11 -0
  112. package/dist/sandbox/source-files.d.ts +13 -0
  113. package/dist/sandbox/types.d.ts +203 -0
  114. package/dist/sandbox/vercel.d.ts +37 -0
  115. package/dist/scoring/types.d.ts +70 -0
  116. package/dist/shared/aggregate.d.ts +29 -0
  117. package/dist/shared/aggregate.js +55 -0
  118. package/dist/shared/types.d.ts +27 -0
  119. package/dist/shared/verdict.d.ts +27 -0
  120. package/dist/shared/verdict.js +34 -0
  121. package/dist/tty-line.d.ts +6 -0
  122. package/dist/types.d.ts +7 -0
  123. package/dist/util.d.ts +22 -0
  124. package/docs-site/zh/concepts/adapter.mdx +9 -4
  125. package/docs-site/zh/concepts/assert.mdx +8 -8
  126. package/docs-site/zh/concepts/evals.mdx +5 -5
  127. package/docs-site/zh/concepts/experiment.mdx +3 -1
  128. package/docs-site/zh/concepts/judge.mdx +9 -9
  129. package/docs-site/zh/concepts/overview.mdx +2 -2
  130. package/docs-site/zh/concepts/tier.mdx +6 -6
  131. package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
  132. package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
  133. package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
  134. package/docs-site/zh/guides/connect-otel.mdx +3 -3
  135. package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
  136. package/docs-site/zh/guides/custom-reports.mdx +196 -36
  137. package/docs-site/zh/guides/experiments.mdx +7 -0
  138. package/docs-site/zh/guides/fixtures.mdx +1 -1
  139. package/docs-site/zh/guides/official-adapters.mdx +31 -9
  140. package/docs-site/zh/guides/publish-report.mdx +4 -4
  141. package/docs-site/zh/guides/report-components.mdx +194 -55
  142. package/docs-site/zh/guides/reporters.mdx +2 -2
  143. package/docs-site/zh/guides/results-data.mdx +35 -34
  144. package/docs-site/zh/guides/runner.mdx +2 -2
  145. package/docs-site/zh/guides/sandbox-agent.mdx +10 -5
  146. package/docs-site/zh/guides/sandbox-providers.mdx +102 -0
  147. package/docs-site/zh/guides/viewing-results.mdx +199 -79
  148. package/docs-site/zh/guides/write-experiment.mdx +23 -1
  149. package/docs-site/zh/guides/write-send.mdx +2 -2
  150. package/docs-site/zh/index.mdx +4 -3
  151. package/docs-site/zh/introduction.mdx +2 -1
  152. package/docs-site/zh/quickstart.mdx +5 -3
  153. package/docs-site/zh/reference/builtin-agents.mdx +38 -13
  154. package/docs-site/zh/reference/capabilities.mdx +1 -1
  155. package/docs-site/zh/reference/cli.mdx +36 -13
  156. package/docs-site/zh/reference/define-agent.mdx +24 -12
  157. package/docs-site/zh/reference/define-config.mdx +1 -1
  158. package/docs-site/zh/reference/define-eval.mdx +3 -3
  159. package/docs-site/zh/reference/events.mdx +7 -1
  160. package/docs-site/zh/reference/expect.mdx +3 -3
  161. package/package.json +10 -6
  162. package/src/agents/bub.ts +134 -43
  163. package/src/agents/claude-code.test.ts +152 -0
  164. package/src/agents/claude-code.ts +127 -13
  165. package/src/agents/codex.test.ts +192 -0
  166. package/src/agents/codex.ts +151 -12
  167. package/src/agents/index.ts +11 -3
  168. package/src/agents/manifest.ts +41 -0
  169. package/src/agents/plugin-config.test.ts +94 -0
  170. package/src/agents/shared.ts +33 -2
  171. package/src/agents/skills.test.ts +214 -0
  172. package/src/agents/skills.ts +304 -0
  173. package/src/agents/streaming.ts +5 -5
  174. package/src/agents/types.ts +62 -2
  175. package/src/cli.ts +74 -47
  176. package/src/context/context.test.ts +20 -2
  177. package/src/context/context.ts +3 -0
  178. package/src/context/control-flow.ts +1 -1
  179. package/src/context/session.test.ts +92 -1
  180. package/src/context/session.ts +26 -1
  181. package/src/context/types.ts +4 -4
  182. package/src/define.test.ts +82 -0
  183. package/src/define.ts +72 -11
  184. package/src/expect/index.ts +5 -5
  185. package/src/i18n/en.ts +36 -15
  186. package/src/i18n/zh-CN.ts +39 -21
  187. package/src/index.ts +1 -1
  188. package/src/o11y/execution-tree.test.ts +452 -0
  189. package/src/o11y/execution-tree.ts +367 -0
  190. package/src/o11y/otlp/receiver.ts +1 -1
  191. package/src/o11y/otlp/select.ts +7 -3
  192. package/src/o11y/parsers/claude-code.test.ts +175 -0
  193. package/src/o11y/parsers/claude-code.ts +32 -0
  194. package/src/o11y/types.ts +6 -1
  195. package/src/report/aggregate.ts +26 -20
  196. package/src/report/built-in-user-parity.test.tsx +643 -0
  197. package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
  198. package/src/report/built-ins/index.ts +2 -0
  199. package/src/report/components.tsx +223 -50
  200. package/src/report/compute.ts +358 -53
  201. package/src/report/dual-render.test.tsx +1125 -0
  202. package/src/report/flag.ts +1 -1
  203. package/src/report/format.ts +50 -2
  204. package/src/report/index.ts +48 -14
  205. package/src/report/load.ts +1 -1
  206. package/src/report/locale.ts +206 -0
  207. package/src/report/metrics.ts +39 -15
  208. package/src/report/primitives.tsx +100 -2
  209. package/src/report/react/AttemptList.tsx +115 -0
  210. package/src/report/react/DeltaTable.tsx +9 -6
  211. package/src/report/react/EvalList.tsx +0 -0
  212. package/src/report/react/ExperimentList.tsx +108 -0
  213. package/src/report/react/GroupSummary.tsx +66 -0
  214. package/src/report/react/MetricBars.tsx +24 -13
  215. package/src/report/react/MetricLine.tsx +21 -21
  216. package/src/report/react/MetricMatrix.tsx +8 -4
  217. package/src/report/react/MetricScatter.tsx +136 -96
  218. package/src/report/react/MetricTable.tsx +115 -15
  219. package/src/report/react/RunOverview.tsx +48 -26
  220. package/src/report/react/Scoreboard.tsx +15 -7
  221. package/src/report/react/cell.tsx +18 -9
  222. package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
  223. package/src/report/react/colors.ts +24 -15
  224. package/src/report/react/enhance.js +129 -0
  225. package/src/report/react/fixtures.ts +163 -34
  226. package/src/report/react/format.ts +1 -1
  227. package/src/report/react/index.tsx +22 -6
  228. package/src/report/react/render.test.tsx +164 -31
  229. package/src/report/react/styles.css +405 -213
  230. package/src/report/report.test.ts +403 -94
  231. package/src/report/report.ts +29 -11
  232. package/src/report/text/faces.ts +303 -95
  233. package/src/report/text/layout.ts +76 -11
  234. package/src/report/text/table.ts +98 -0
  235. package/src/report/tree.ts +103 -20
  236. package/src/report/types.ts +196 -25
  237. package/src/report/web.ts +46 -19
  238. package/src/results/annotated-source.test.ts +146 -0
  239. package/src/results/annotated-source.ts +128 -0
  240. package/src/results/attempt-evidence.test.ts +158 -0
  241. package/src/results/attempt-evidence.ts +158 -0
  242. package/src/results/attempt-source.ts +48 -0
  243. package/src/results/copy.ts +154 -139
  244. package/src/results/format.ts +54 -47
  245. package/src/results/host-equivalence.test.ts +527 -0
  246. package/src/results/index.ts +56 -14
  247. package/src/results/locator.test.ts +198 -0
  248. package/src/results/locator.ts +0 -0
  249. package/src/results/open.ts +392 -175
  250. package/src/results/results.test.ts +943 -494
  251. package/src/results/select.ts +129 -23
  252. package/src/results/skipped-notice.ts +0 -0
  253. package/src/results/source-hash.ts +28 -0
  254. package/src/results/types.ts +98 -62
  255. package/src/results/writer.ts +304 -176
  256. package/src/runner/attempt.test.ts +170 -0
  257. package/src/runner/attempt.ts +97 -15
  258. package/src/runner/discover.test.ts +65 -0
  259. package/src/runner/discover.ts +6 -2
  260. package/src/runner/eval-source.test.ts +121 -0
  261. package/src/runner/eval-source.ts +45 -0
  262. package/src/runner/fingerprint.ts +54 -1
  263. package/src/runner/remote-sandbox.ts +1 -1
  264. package/src/runner/report.test.ts +6 -8
  265. package/src/runner/report.ts +5 -4
  266. package/src/runner/reporters/artifacts.ts +27 -41
  267. package/src/runner/reporters/braintrust.test.ts +3 -3
  268. package/src/runner/reporters/braintrust.ts +1 -1
  269. package/src/runner/reporters/console.ts +6 -6
  270. package/src/runner/reporters/json.ts +4 -4
  271. package/src/runner/reporters/live.test.ts +56 -0
  272. package/src/runner/reporters/live.ts +36 -10
  273. package/src/runner/reporters/quiet.test.ts +66 -0
  274. package/src/runner/reporters/quiet.ts +49 -0
  275. package/src/runner/reporters/shared.ts +5 -5
  276. package/src/runner/reporters/table.ts +17 -17
  277. package/src/runner/run.ts +61 -102
  278. package/src/runner/types.ts +43 -32
  279. package/src/sandbox/checkpoint.ts +1 -1
  280. package/src/sandbox/docker.ts +15 -2
  281. package/src/sandbox/e2b.ts +11 -5
  282. package/src/sandbox/errors.ts +13 -0
  283. package/src/sandbox/index.ts +3 -3
  284. package/src/sandbox/local-files.ts +1 -1
  285. package/src/sandbox/registry.ts +6 -5
  286. package/src/sandbox/resolve.ts +38 -23
  287. package/src/sandbox/retry.test.ts +68 -0
  288. package/src/sandbox/retry.ts +44 -0
  289. package/src/sandbox/types.ts +69 -28
  290. package/src/sandbox/vercel.ts +17 -3
  291. package/src/scoring/collector.ts +3 -3
  292. package/src/scoring/judge.ts +2 -2
  293. package/src/scoring/scoped.ts +13 -1
  294. package/src/scoring/types.ts +7 -7
  295. package/src/scoring/verdict.ts +4 -4
  296. package/src/shared/aggregate.ts +23 -3
  297. package/src/shared/types.ts +2 -2
  298. package/src/shared/{outcome.ts → verdict.ts} +15 -15
  299. package/src/show/compose.ts +12 -132
  300. package/src/show/index.ts +125 -94
  301. package/src/show/render.ts +660 -160
  302. package/src/show/show.test.ts +472 -253
  303. package/src/source-loc.ts +1 -1
  304. package/src/tty-line.ts +32 -0
  305. package/src/types.ts +2 -2
  306. package/src/util.test.ts +21 -1
  307. package/src/util.ts +5 -1
  308. package/src/view/app/App.tsx +33 -198
  309. package/src/view/app/components/AttemptModal.tsx +6 -6
  310. package/src/view/app/components/CodeView.tsx +2 -2
  311. package/src/view/app/components/CopyControls.tsx +32 -36
  312. package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
  313. package/src/view/app/components/ui/badge.tsx +1 -1
  314. package/src/view/app/components/ui/dialog.tsx +2 -2
  315. package/src/view/app/i18n.ts +40 -31
  316. package/src/view/app/lib/artifact-url.ts +1 -1
  317. package/src/view/app/lib/attempt-route.test.ts +37 -47
  318. package/src/view/app/lib/attempt-route.ts +26 -26
  319. package/src/view/app/lib/rows.ts +9 -112
  320. package/src/view/app/lib/verdict.ts +25 -0
  321. package/src/view/app/main.tsx +9 -12
  322. package/src/view/app/pages/RunsPage.tsx +4 -4
  323. package/src/view/app/pages/TracesPage.tsx +3 -3
  324. package/src/view/app/shared.ts +0 -1
  325. package/src/view/app/types.ts +6 -51
  326. package/src/view/artifact-serving.test.ts +136 -0
  327. package/src/view/client-dist/app.css +1 -1
  328. package/src/view/client-dist/app.js +21 -21
  329. package/src/view/data.test.ts +214 -143
  330. package/src/view/data.ts +166 -120
  331. package/src/view/index.ts +23 -11
  332. package/src/view/server.ts +52 -17
  333. package/src/view/shared/types.ts +22 -31
  334. package/src/view/styles.css +117 -327
  335. package/src/view/view-report.test.ts +146 -98
  336. package/docs-site/zh/guides/sandbox-backends.mdx +0 -75
  337. package/src/report/default-report.tsx +0 -222
  338. package/src/report/dual-face.test.tsx +0 -527
  339. package/src/report/react/CaseList.tsx +0 -70
  340. package/src/view/app/components/CostScoreChart.tsx +0 -147
  341. package/src/view/app/components/ExperimentTable.tsx +0 -284
  342. package/src/view/app/components/GroupSelector.tsx +0 -61
  343. package/src/view/app/components/primitives.tsx +0 -43
  344. package/src/view/app/lib/outcome.ts +0 -82
@@ -0,0 +1,503 @@
1
+ // 官方组件的 text 面:同一份算好的数据,渲染成终端字符(niceeval show 的形态)。
2
+ // 输出形态照 docs-site/zh/guides/report-components.mdx 的示例块;与 web 面共守
3
+ // 诚实契约:排序随 better、samples < total 角标、缺数据 — 不补 0、截断报剩余。
4
+ // 零 react、零 IO、纯同步 —— 这是 text 宿主不需要 react-dom 的那一半。
5
+ // chrome 文案(注脚、verdict 词、截断提示)经 ctx.locale 查 locale 字典,
6
+ // 默认 en 与历史输出逐字一致;数据(display、键、warnings message)不本地化。
7
+ import { attemptItemReason, capabilityBadge, formatDurationMs, formatMetricValue, formatPlainNumber, formatUSD, verdictMark, } from "../format.js";
8
+ import { countText, localeText, resolveMetricLabel } from "../locale.js";
9
+ import { indentBlock, padDisplay, textBar, wrapDisplay } from "./layout.js";
10
+ import { renderTableText } from "./table.js";
11
+ import { renderCharPlot, renderCoordinateTable } from "./plot.js";
12
+ const MISSING_MARK = "—";
13
+ /** 缺数据文案随 locale(en = "no data",与 MISSING_TEXT 一致)。 */
14
+ function missingText(locale) {
15
+ return localeText(locale, "cell.missing");
16
+ }
17
+ /** 格子的文本形态:缺数据 —,覆盖不全带 samples/total 角标。 */
18
+ export function cellText(cell) {
19
+ if (cell.value === null)
20
+ return MISSING_MARK;
21
+ return cell.samples < cell.total ? `${cell.display} ${cell.samples}/${cell.total}` : cell.display;
22
+ }
23
+ // ───────────────────────── RunOverview ─────────────────────────
24
+ export function overviewText(data, ctx) {
25
+ const { totals, snapshots } = data;
26
+ const locale = ctx.locale;
27
+ const runs = new Set(snapshots.map((s) => s.startedAt)).size;
28
+ const latest = snapshots.map((s) => s.startedAt).sort().at(-1);
29
+ const head = [
30
+ countText(locale, "overview.experiments", snapshots.length),
31
+ localeText(locale, "overview.evalsCount", { n: totals.evals }),
32
+ localeText(locale, "overview.attemptsCount", { n: totals.attempts }),
33
+ // 通过率只渲染 computeCell 算好的同一个 MetricCell(cellText 复用缺数据/coverage 角标语义),
34
+ // 不从 passed/failed/errored 现场重算——与 web 面 RunOverview 同一份数据同一种读法。
35
+ `${localeText(locale, "overview.passRate")} ${cellText(totals.passRate)}`,
36
+ countText(locale, "composedFrom", runs),
37
+ ...(latest ? [localeText(locale, "latestRun", { run: latest })] : []),
38
+ ].join(" · ");
39
+ const tallies = [
40
+ `${localeText(locale, "verdict.passed")} ${totals.passed}`,
41
+ `${localeText(locale, "verdict.failed")} ${totals.failed}`,
42
+ `${localeText(locale, "verdict.errored")} ${totals.errored}`,
43
+ `${localeText(locale, "verdict.skipped")} ${totals.skipped}`,
44
+ totals.costUSD === null ? missingText(locale) : formatUSD(totals.costUSD),
45
+ formatDurationMs(totals.durationMs),
46
+ ].join(" · ");
47
+ const lines = [head, tallies];
48
+ for (const warning of data.warnings)
49
+ lines.push(`! ${warning.message}`);
50
+ return lines.join("\n");
51
+ }
52
+ // ───────────────────────── GroupSummary ─────────────────────────
53
+ /**
54
+ * 一至两行:头行是通过率(GroupSummaryData.passRate.display,不重算比例)+ experiment/eval 数 +
55
+ * failed(+ errored,非零才列,与旧 GroupSelector 卡片一致)+ 总成本;第二行(有则加)是最后运行时间。
56
+ * 不依赖固定网格宽度,窄终端自然换行。
57
+ */
58
+ export function groupSummaryText(data, ctx) {
59
+ const locale = ctx.locale;
60
+ const head = [
61
+ `${localeText(locale, "overview.passRate")} ${cellText(data.passRate)}`,
62
+ countText(locale, "overview.experiments", data.experiments),
63
+ localeText(locale, "overview.evalsCount", { n: data.evals }),
64
+ `${localeText(locale, "verdict.failed")} ${data.verdicts.failed}`,
65
+ ...(data.verdicts.errored > 0 ? [`${localeText(locale, "verdict.errored")} ${data.verdicts.errored}`] : []),
66
+ data.totalCostUSD === null ? missingText(locale) : formatUSD(data.totalCostUSD),
67
+ ].join(" · ");
68
+ const lines = [head];
69
+ if (data.lastRunAt)
70
+ lines.push(localeText(locale, "latestRun", { run: data.lastRunAt }));
71
+ return lines.join("\n");
72
+ }
73
+ // ───────────────────────── MetricTable ─────────────────────────
74
+ /** verdict 计票的紧凑文案("3 passed / 1 failed"):非零判定逐个列,全部为零如实 —。 */
75
+ export function verdictTallyText(verdicts, locale) {
76
+ const parts = [];
77
+ for (const kind of ["passed", "failed", "errored", "skipped"]) {
78
+ if (verdicts[kind] > 0)
79
+ parts.push(`${verdicts[kind]} ${localeText(locale, `verdict.${kind}`)}`);
80
+ }
81
+ return parts.length > 0 ? parts.join(" / ") : MISSING_MARK;
82
+ }
83
+ export function tableText(data, ctx) {
84
+ const locale = ctx.locale;
85
+ // meta 在场时补 Model / Agent / Verdicts 列(rows: "experiment" 的榜单 parity);
86
+ // 列序对齐 view 原生榜单:experiment、model、agent、指标列…、verdicts
87
+ const hasMeta = data.rows.some((row) => row.meta !== undefined);
88
+ const hasModel = data.rows.some((row) => row.meta?.model !== undefined);
89
+ const hasVerdicts = data.rows.some((row) => row.meta?.verdicts !== undefined);
90
+ const columns = [
91
+ { key: "dimension", header: data.dimension },
92
+ ...(hasMeta && hasModel ? [{ key: "model", header: localeText(locale, "table.model") }] : []),
93
+ ...(hasMeta ? [{ key: "agent", header: localeText(locale, "table.agent") }] : []),
94
+ // 指标列的键用序号,不用 metric name —— 维度名、"model" 这些键都在同一个命名空间里,
95
+ // 指标恰好叫 "agent" 时不能把 meta 列顶掉。
96
+ ...data.columns.map((c, i) => ({ key: `metric${i}`, header: resolveMetricLabel(c.label, locale, c.key) })),
97
+ ...(hasVerdicts ? [{ key: "verdicts", header: localeText(locale, "table.verdicts") }] : []),
98
+ ];
99
+ const rows = data.rows.map((row) => {
100
+ const cells = { dimension: row.key };
101
+ if (hasMeta && hasModel)
102
+ cells.model = row.meta?.model ?? null;
103
+ if (hasMeta)
104
+ cells.agent = row.meta?.agent ?? null;
105
+ data.columns.forEach((col, i) => {
106
+ const cell = row.cells[col.key];
107
+ cells[`metric${i}`] = cell ? cellText(cell) : null;
108
+ });
109
+ if (hasVerdicts)
110
+ cells.verdicts = row.meta?.verdicts ? verdictTallyText(row.meta.verdicts, locale) : null;
111
+ return { key: row.key, cells };
112
+ });
113
+ const table = renderTableText({ columns, rows, locale }, ctx);
114
+ // rows: "experiment" 专属的行摘要(eval/attempt 数 + 最后运行时间):表格列已经挤满
115
+ // dimension/model/agent/指标/verdicts,这几个数字挤进行键下面单独一行,与 web 面
116
+ // 「行键下的小 sub-line」同一份信息、同一种「不进列」的取舍。
117
+ const metaLines = [];
118
+ for (const row of data.rows) {
119
+ if (row.meta?.evals === undefined)
120
+ continue;
121
+ const parts = [localeText(locale, "overview.evalsCount", { n: row.meta.evals })];
122
+ if (row.meta.attempts !== undefined && row.meta.attempts > row.meta.evals) {
123
+ parts.push(localeText(locale, "overview.attemptsCount", { n: row.meta.attempts }));
124
+ }
125
+ if (row.meta.lastRunAt)
126
+ parts.push(localeText(locale, "latestRun", { run: row.meta.lastRunAt }));
127
+ metaLines.push(` ${row.key}: ${parts.join(" · ")}`);
128
+ }
129
+ const blocks = [table, ...(metaLines.length > 0 ? [metaLines.join("\n")] : [])];
130
+ return blocks.join("\n\n");
131
+ }
132
+ // ───────────────────────── MetricMatrix ─────────────────────────
133
+ export function matrixText(data, ctx) {
134
+ // 表体全是维度键与 display,没有 chrome 文案;"next:" 是命令提示,不本地化。
135
+ const rowKeys = [];
136
+ const columnKeys = [];
137
+ const byPosition = new Map();
138
+ for (const entry of data.cells) {
139
+ if (!rowKeys.includes(entry.row))
140
+ rowKeys.push(entry.row);
141
+ if (!columnKeys.includes(entry.column))
142
+ columnKeys.push(entry.column);
143
+ byPosition.set(JSON.stringify([entry.row, entry.column]), entry.cell);
144
+ }
145
+ const columns = [
146
+ { key: "dimension", header: data.rows },
147
+ ...columnKeys.map((column, i) => ({ key: `column${i}`, header: column })),
148
+ ];
149
+ const rows = rowKeys.map((row) => {
150
+ const cells = { dimension: row };
151
+ columnKeys.forEach((column, i) => {
152
+ const cell = byPosition.get(JSON.stringify([row, column]));
153
+ cells[`column${i}`] = cell ? cellText(cell) : null; // 稀疏格子在文本里以 — 呈现,不编数
154
+ });
155
+ return { key: row, cells };
156
+ });
157
+ const table = renderTableText({ columns, rows, locale: ctx.locale }, ctx);
158
+ // 下钻命令:行维度是 eval 时,指向最值得看的一行(先挑有缺格的,再挑按 better 最差的)
159
+ if (data.rows !== "eval" || rowKeys.length === 0)
160
+ return table;
161
+ const better = data.metric.better ?? "higher";
162
+ let next;
163
+ let worst;
164
+ for (const row of rowKeys) {
165
+ let sum = 0;
166
+ let count = 0;
167
+ for (const column of columnKeys) {
168
+ const cell = byPosition.get(JSON.stringify([row, column]));
169
+ if (!cell || cell.value === null) {
170
+ next ??= row;
171
+ continue;
172
+ }
173
+ sum += cell.value;
174
+ count += 1;
175
+ }
176
+ if (count > 0) {
177
+ const value = sum / count;
178
+ const isWorse = worst === undefined || (better === "higher" ? value < worst.value : value > worst.value);
179
+ if (isWorse)
180
+ worst = { key: row, value };
181
+ }
182
+ }
183
+ next ??= worst?.key;
184
+ return next === undefined ? table : `${table}\n\nnext: niceeval show ${next}`;
185
+ }
186
+ // ───────────────────────── MetricBars(矩阵数据的另一种摆法)─────────────────────────
187
+ const BAR_WIDTH = 20;
188
+ export function barsText(data) {
189
+ const groupKeys = [];
190
+ const seriesKeys = [];
191
+ const byPosition = new Map();
192
+ for (const entry of data.cells) {
193
+ if (!groupKeys.includes(entry.row))
194
+ groupKeys.push(entry.row);
195
+ if (!seriesKeys.includes(entry.column))
196
+ seriesKeys.push(entry.column);
197
+ byPosition.set(JSON.stringify([entry.row, entry.column]), entry.cell);
198
+ }
199
+ const better = data.metric.better ?? "higher";
200
+ // 条长刻度:% 的天然域是 [0,1],其余以全图最大值为满条
201
+ const values = data.cells.map((c) => c.cell.value).filter((v) => v !== null);
202
+ const maxValue = values.length > 0 ? Math.max(...values) : 0;
203
+ const ratioOf = (value) => data.metric.unit === "%" ? value : maxValue === 0 ? 0 : value / maxValue;
204
+ const seriesWidth = Math.max(...seriesKeys.map((k) => k.length), 0);
205
+ const lines = [];
206
+ for (const group of groupKeys) {
207
+ lines.push(group);
208
+ const entries = seriesKeys.map((series) => ({
209
+ series,
210
+ cell: byPosition.get(JSON.stringify([group, series])),
211
+ }));
212
+ // 组内按值排序,方向随 better(缺数据沉底)
213
+ entries.sort((a, b) => {
214
+ const va = a.cell?.value ?? null;
215
+ const vb = b.cell?.value ?? null;
216
+ if (va === null && vb === null)
217
+ return 0;
218
+ if (va === null)
219
+ return 1;
220
+ if (vb === null)
221
+ return -1;
222
+ return better === "lower" ? va - vb : vb - va;
223
+ });
224
+ for (const { series, cell } of entries) {
225
+ const label = padDisplay(series, seriesWidth);
226
+ if (!cell || cell.value === null) {
227
+ lines.push(` ${label} ${MISSING_MARK}`);
228
+ continue;
229
+ }
230
+ lines.push(` ${label} ${textBar(ratioOf(cell.value), BAR_WIDTH)} ${cellText(cell)}`);
231
+ }
232
+ }
233
+ return lines.join("\n");
234
+ }
235
+ // ───────────────────────── Scoreboard ─────────────────────────
236
+ export function scoreboardText(data, ctx) {
237
+ const locale = ctx.locale;
238
+ const subjectKeys = [];
239
+ for (const row of data.rows) {
240
+ for (const subject of row.subjects) {
241
+ if (!subjectKeys.includes(subject.key))
242
+ subjectKeys.push(subject.key);
243
+ }
244
+ }
245
+ const columns = [
246
+ { key: "dimension", header: data.dimension },
247
+ { key: "total", header: localeText(locale, "scoreboard.totalText") },
248
+ ...subjectKeys.map((key, i) => ({ key: `subject${i}`, header: key })),
249
+ ];
250
+ const rows = data.rows.map((row) => {
251
+ const cells = {
252
+ dimension: row.key,
253
+ total: `${row.total.display}/${data.fullMarks}`,
254
+ };
255
+ subjectKeys.forEach((key, i) => {
256
+ const subject = row.subjects.find((s) => s.key === key);
257
+ if (!subject) {
258
+ cells[`subject${i}`] = null;
259
+ return;
260
+ }
261
+ const score = `${formatPlainNumber(subject.earned)}/${formatPlainNumber(subject.possible)}`;
262
+ cells[`subject${i}`] =
263
+ subject.missing > 0
264
+ ? `${score} ${localeText(locale, "scoreboard.missingText", { n: subject.missing })}`
265
+ : score;
266
+ });
267
+ return { key: row.key, cells };
268
+ });
269
+ const table = renderTableText({ columns, rows, locale }, ctx);
270
+ if (data.weights.length === 0)
271
+ return table;
272
+ // 实际生效的权重表 —— 成绩单可审计
273
+ const weights = data.weights.map((w) => `${w.prefix} ×${w.weight}`).join(" · ");
274
+ return `${table}\n${localeText(locale, "scoreboard.weights")} ${weights} · ${localeText(locale, "scoreboard.othersWeight")}`;
275
+ }
276
+ // ───────────────────────── MetricScatter ─────────────────────────
277
+ const POINT_MARKS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
278
+ function axisLabel(col, locale) {
279
+ return resolveMetricLabel(col.label, locale, col.key);
280
+ }
281
+ export function scatterText(data, ctx) {
282
+ const locale = ctx.locale;
283
+ const drawable = data.rows.filter((r) => r.x.value !== null && r.y.value !== null);
284
+ const missing = data.rows.length - drawable.length;
285
+ const footnotes = [];
286
+ if (missing > 0)
287
+ footnotes.push(countText(locale, "pointsMissing", missing));
288
+ const axes = { x: axisLabel(data.x, locale), y: axisLabel(data.y, locale) };
289
+ // 0 个可画点:x/y 指标没有可用数据(与 web 面同一事实)。
290
+ if (drawable.length === 0) {
291
+ return [localeText(locale, "scatter.noData", axes), ...footnotes].join("\n");
292
+ }
293
+ // 恰好 1 个可画点:成本 × 通过率的比较至少要两个实验,单点不成图。
294
+ if (drawable.length === 1) {
295
+ return [localeText(locale, "scatter.needTwo", axes), ...footnotes].join("\n");
296
+ }
297
+ // 点太密排不下时降级为坐标表,不硬挤
298
+ if (drawable.length > POINT_MARKS.length || ctx.width < 44) {
299
+ const table = renderCoordinateTable(drawable.map((r) => ({ key: r.key, x: r.x.display, y: r.y.display })), { key: data.points, x: axisLabel(data.x, locale), y: axisLabel(data.y, locale) });
300
+ return [table, ...footnotes].join("\n");
301
+ }
302
+ const points = drawable.map((r, i) => ({
303
+ mark: POINT_MARKS[i],
304
+ x: r.x.value,
305
+ y: r.y.value,
306
+ }));
307
+ // 同系列的点按 x 排序连线
308
+ const bySeries = new Map();
309
+ for (const r of drawable) {
310
+ if (r.series === undefined)
311
+ continue;
312
+ const list = bySeries.get(r.series) ?? [];
313
+ list.push({ x: r.x.value, y: r.y.value });
314
+ bySeries.set(r.series, list);
315
+ }
316
+ for (const list of bySeries.values())
317
+ list.sort((a, b) => a.x - b.x);
318
+ const invertX = data.x.better === "lower";
319
+ const plot = renderCharPlot({
320
+ width: ctx.width,
321
+ points,
322
+ lines: [...bySeries.values()].filter((l) => l.length > 1),
323
+ xLabel: `${axisLabel(data.x, locale)}${invertX ? ` ${localeText(locale, "scatter.axisReversed")}` : ""}`,
324
+ yLabel: axisLabel(data.y, locale),
325
+ formatX: (v) => formatMetricValue(v, data.x.unit),
326
+ formatY: (v) => formatMetricValue(v, data.y.unit),
327
+ invertX,
328
+ invertY: data.y.better === "lower",
329
+ });
330
+ const legend = drawable.map((r, i) => `${POINT_MARKS[i]} ${r.key}`).join(" ");
331
+ return [plot, "", localeText(locale, "scatter.betterUpperRight"), legend, ...footnotes].join("\n");
332
+ }
333
+ // ───────────────────────── MetricLine ─────────────────────────
334
+ export function lineText(data, ctx) {
335
+ const locale = ctx.locale;
336
+ const drawable = data.rows.filter((r) => r.x !== null && r.y.value !== null);
337
+ const missing = data.rows.length - drawable.length;
338
+ const footnotes = [];
339
+ if (missing > 0)
340
+ footnotes.push(countText(locale, "pointsMissing", missing));
341
+ if (drawable.length === 0)
342
+ return [missingText(locale), ...footnotes].join("\n");
343
+ // 系列 → 字母;无系列 = 单系列
344
+ const seriesKeys = [];
345
+ for (const r of drawable) {
346
+ const key = r.series ?? "";
347
+ if (!seriesKeys.includes(key))
348
+ seriesKeys.push(key);
349
+ }
350
+ if (seriesKeys.length > POINT_MARKS.length || ctx.width < 44) {
351
+ const table = renderCoordinateTable(drawable.map((r) => ({ key: r.series ? `${r.key} (${r.series})` : r.key, x: r.xDisplay, y: r.y.display })), { key: "experiment", x: data.x.label, y: axisLabel(data.y, locale) });
352
+ return [table, ...footnotes].join("\n");
353
+ }
354
+ const markOf = (r) => POINT_MARKS[seriesKeys.indexOf(r.series ?? "")];
355
+ const points = drawable.map((r) => ({
356
+ mark: markOf(r),
357
+ x: r.x,
358
+ y: r.y.value,
359
+ }));
360
+ const lines = seriesKeys.map((key) => drawable
361
+ .filter((r) => (r.series ?? "") === key)
362
+ .map((r) => ({ x: r.x, y: r.y.value }))
363
+ .sort((a, b) => a.x - b.x));
364
+ const plot = renderCharPlot({
365
+ width: ctx.width,
366
+ points,
367
+ lines: lines.filter((l) => l.length > 1),
368
+ xLabel: data.x.label,
369
+ yLabel: axisLabel(data.y, locale),
370
+ formatX: (v) => formatMetricValue(v, data.x.unit),
371
+ formatY: (v) => formatMetricValue(v, data.y.unit),
372
+ invertY: data.y.better === "lower",
373
+ });
374
+ const legend = seriesKeys
375
+ .map((key, i) => `${POINT_MARKS[i]} ${key === "" ? axisLabel(data.y, locale) : key}`)
376
+ .join(" ");
377
+ return [plot, "", legend, ...footnotes].join("\n");
378
+ }
379
+ // ───────────────────────── DeltaTable ─────────────────────────
380
+ export function deltaText(data, ctx) {
381
+ const columns = [
382
+ { key: "pair", header: "pair" },
383
+ ...data.columns.map((c, i) => ({ key: `metric${i}`, header: resolveMetricLabel(c.label, ctx.locale, c.key) })),
384
+ ];
385
+ const rows = data.rows.map((row) => {
386
+ const cells = { pair: row.key };
387
+ data.columns.forEach((col, i) => {
388
+ const cell = row.cells[col.key];
389
+ if (!cell) {
390
+ cells[`metric${i}`] = null;
391
+ return;
392
+ }
393
+ const a = cell.a.value === null ? MISSING_MARK : cell.a.display;
394
+ const b = cell.b.value === null ? MISSING_MARK : cell.b.display;
395
+ cells[`metric${i}`] = `${a} → ${b} ${cell.display}`;
396
+ });
397
+ return { key: row.key, cells };
398
+ });
399
+ return renderTableText({ columns, rows, locale: ctx.locale }, ctx);
400
+ }
401
+ // ───────────────────────── 实体列表(ExperimentList / EvalList / AttemptList)─────────────────────────
402
+ //
403
+ // 三面共用的紧凑标记:`locator✓[E,X,⏱]`(判定符紧跟 locator,证据能力方括号紧跟判定符,
404
+ // 中间不留空格)——docs-site/zh/guides/report-components.mdx「终端输出形成反馈闭环」定的形态。
405
+ // ExperimentList / EvalList 逐 attempt 只列这一个标记 + 各自的原因/耗时摘要,不重复整段
406
+ // niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼 `niceeval show <locator>`——
407
+ // 命令模板只在 AttemptList(叶子层)展示完整断言明细时才值得,不在中间层重复。
408
+ function locatorBadge(item) {
409
+ return `${item.locator}${verdictMark(item.verdict)}${capabilityBadge(item.capabilities)}`;
410
+ }
411
+ // ── ExperimentList ──
412
+ function experimentListEvalLine(row) {
413
+ const badges = row.attempts.map(locatorBadge).join(" ");
414
+ const trailer = row.verdict === "passed"
415
+ ? [formatDurationMs(row.duration.value ?? 0), row.cost.value === null ? undefined : formatUSD(row.cost.value)]
416
+ .filter((s) => s !== undefined)
417
+ .join(" · ")
418
+ : (row.reason ?? "");
419
+ return ` ${verdictMark(row.verdict)} ${row.evalId} ${badges} ${trailer}`;
420
+ }
421
+ export function experimentListText(items, ctx) {
422
+ const locale = ctx.locale;
423
+ if (items.length === 0)
424
+ return localeText(locale, "attemptList.empty");
425
+ const blocks = items.map((item) => {
426
+ const identity = item.model ? `${item.experimentId} · ${item.agent} · ${item.model}` : `${item.experimentId} · ${item.agent}`;
427
+ const summary = [
428
+ `${localeText(locale, "overview.passRate")} ${cellText(item.passRate)}`,
429
+ verdictTallyText(item.verdicts, locale),
430
+ localeText(locale, "overview.attemptsCount", { n: item.attempts }),
431
+ formatDurationMs(item.duration.value ?? 0),
432
+ item.cost.value === null ? missingText(locale) : formatUSD(item.cost.value),
433
+ ].join(" · ");
434
+ const evalLines = item.evalRows.map(experimentListEvalLine);
435
+ return [identity, ` ${summary}`, ...evalLines].join("\n");
436
+ });
437
+ return blocks.join("\n\n");
438
+ }
439
+ // ── EvalList ──
440
+ function evalListAttemptLine(item) {
441
+ const reason = attemptItemReason(item);
442
+ return ` ${locatorBadge(item)}${reason ? ` · ${reason}` : ""}`;
443
+ }
444
+ export function evalListText(items, ctx) {
445
+ const locale = ctx.locale;
446
+ if (items.length === 0)
447
+ return localeText(locale, "attemptList.empty");
448
+ const blocks = items.map((item) => {
449
+ const identity = `${item.evalId} · ${item.experimentId} · ${localeText(locale, `verdict.${item.verdict}`)}`;
450
+ const summary = [
451
+ localeText(locale, "attemptList.score", { score: cellText(item.score) }),
452
+ localeText(locale, "overview.attemptsCount", { n: item.attempts.length }),
453
+ `${formatDurationMs(item.duration.value ?? 0)} avg`,
454
+ item.cost.value === null ? `${missingText(locale)} avg` : `${formatUSD(item.cost.value)} avg`,
455
+ ].join(" · ");
456
+ const attemptLines = item.attempts.map(evalListAttemptLine);
457
+ return [identity, ` ${summary}`, ...attemptLines].join("\n");
458
+ });
459
+ return blocks.join("\n\n");
460
+ }
461
+ // ── AttemptList ──
462
+ /** 一个 AttemptListItem 的完整 text 卡片:判定符 + locator + 身份 + 耗时/成本 + 证据能力,
463
+ * 然后逐条断言(gate 与 soft 都列,与 web 面的 AttemptRow 同一份材料)。 */
464
+ function attemptListItemText(item, ctx, locale) {
465
+ const head = [
466
+ `${verdictMark(item.verdict)} ${item.locator}`,
467
+ item.evalId,
468
+ item.experimentId,
469
+ formatDurationMs(item.durationMs),
470
+ ...(item.costUSD !== undefined ? [formatUSD(item.costUSD)] : []),
471
+ ...(capabilityBadge(item.capabilities) ? [capabilityBadge(item.capabilities)] : []),
472
+ ].join(" · ");
473
+ const lines = [head];
474
+ if (item.error) {
475
+ lines.push(indentBlock(wrapDisplay(item.error, ctx.width - 4).join("\n"), " "));
476
+ }
477
+ for (const assertion of item.assertions) {
478
+ const scoreText = assertion.threshold !== undefined
479
+ ? `${formatPlainNumber(assertion.score)}/${formatPlainNumber(assertion.threshold)}`
480
+ : formatPlainNumber(assertion.score);
481
+ lines.push(` ${assertion.severity} ${assertion.name} · ${localeText(locale, `verdict.${assertion.passed ? "passed" : "failed"}`)}${assertion.severity === "soft" ? ` ${scoreText}` : ""}`);
482
+ if (assertion.detail)
483
+ lines.push(indentBlock(wrapDisplay(assertion.detail, ctx.width - 4).join("\n"), " "));
484
+ if (assertion.evidence) {
485
+ const limit = Math.max(240, ctx.width * 5);
486
+ const evidence = assertion.evidence.length <= limit
487
+ ? assertion.evidence
488
+ : `${assertion.evidence.slice(0, limit)}… (${assertion.evidence.length - limit} more chars; open ${item.locator} for full evidence)`;
489
+ lines.push(indentBlock(wrapDisplay(evidence, ctx.width - 6).join("\n"), " "));
490
+ }
491
+ }
492
+ return lines.join("\n");
493
+ }
494
+ export function attemptListText(items, total, ctx) {
495
+ const locale = ctx.locale;
496
+ if (items.length === 0)
497
+ return localeText(locale, "attemptList.empty");
498
+ const blocks = items.map((item) => attemptListItemText(item, ctx, locale));
499
+ const remaining = (total ?? items.length) - items.length;
500
+ if (remaining > 0)
501
+ blocks.push(localeText(locale, "attemptList.truncatedText", { n: remaining }));
502
+ return blocks.join("\n\n");
503
+ }
@@ -0,0 +1,75 @@
1
+ /** 终端显示宽度:CJK / 全角记 2 列,其余记 1。启发式覆盖常用区段,够对齐表格用。 */
2
+ export declare function charDisplayWidth(codePoint: number): number;
3
+ /**
4
+ * 一段文本在终端里占几列:CJK / 全角字符记 2 列,其余记 1 列。
5
+ *
6
+ * 对齐终端输出用它,不要用 `String.prototype.length`——`length` 数的是 UTF-16 码元,
7
+ * 一个汉字占 2 个显示列却只算 1 个码元,中文一进来列就错位。
8
+ *
9
+ * @param text 任意文本。
10
+ * @returns 显示列数。
11
+ */
12
+ export declare function stringWidth(text: string): number;
13
+ /**
14
+ * 按显示宽度在右侧补空格(左对齐)。超宽不截断,如实溢出。
15
+ *
16
+ * `String.prototype.padEnd` 的显示宽度版:内容含中文时才对得齐。
17
+ *
18
+ * @param text 要补齐的文本。
19
+ * @param width 目标显示宽度。
20
+ * @returns 补齐后的文本。
21
+ */
22
+ export declare function padDisplay(text: string, width: number): string;
23
+ /**
24
+ * 按显示宽度在左侧补空格(右对齐)。超宽不截断,如实溢出。
25
+ *
26
+ * 数字列靠它对齐小数点。
27
+ *
28
+ * @param text 要补齐的文本。
29
+ * @param width 目标显示宽度。
30
+ * @returns 补齐后的文本。
31
+ */
32
+ export declare function padStartDisplay(text: string, width: number): string;
33
+ /**
34
+ * 按显示宽度折行:优先在空格处断,单个词超宽(URL / 中文长句)按列宽硬断。
35
+ *
36
+ * @param text 要折行的文本(`\n` 分段)。
37
+ * @param width 每行的最大显示宽度。
38
+ * @returns 折好的行;至少一行(空串输入 → `[""]`)。
39
+ */
40
+ export declare function wrapDisplay(text: string, width: number): string[];
41
+ /**
42
+ * 给一整块文本的每行加同一段前缀(嵌套块缩进)。空行不加,不留尾随空格。
43
+ *
44
+ * @param block 多行文本。
45
+ * @param indent 每行的前缀,通常是若干空格。
46
+ * @returns 缩进后的文本块。
47
+ */
48
+ export declare function indentBlock(block: string, indent: string): string;
49
+ /** 列对齐方向:`"left"` 是默认,`"right"` 按显示宽度右对齐(数字列用)。 */
50
+ export type ColumnAlign = "left" | "right";
51
+ /**
52
+ * 对齐列渲染:每列宽 = 该列最宽格的显示宽度,列间 3 空格,行尾不留空白。首行是表头。
53
+ * `align[c]` 给第 c 列的对齐方向,缺省 `"left"`。
54
+ *
55
+ * 内部件:公开面是 <Table> 原语(见 ./table.ts 的 renderTableText,它先按 ctx.width
56
+ * 定好列宽再调这里)。不导出到 `niceeval/report` —— 两条并行的建表路径只会让作者选错。
57
+ */
58
+ export declare function renderAlignedRows(rows: string[][], align?: readonly ColumnAlign[], separator?: string): string;
59
+ /**
60
+ * 字符条形:比例 → `█` 填充、`░` 补齐到 `barWidth` 显示列。比例超出 [0, 1] 时钳住。
61
+ *
62
+ * @param ratio 填充比例,0 到 1。
63
+ * @param barWidth 整条的显示宽度。
64
+ * @returns 定宽的字符条。
65
+ */
66
+ export declare function textBar(ratio: number, barWidth: number): string;
67
+ /**
68
+ * 多块文本并排成栏(`Row` 的 text 面):逐行拼接,各栏按显示宽度补齐,短栏补空行。
69
+ *
70
+ * @param blocks 每栏一块多行文本。
71
+ * @param columnWidths 每栏的显示宽度,与 `blocks` 一一对应。
72
+ * @param separator 栏间分隔符,默认 `" │ "`。
73
+ * @returns 并排后的文本块。
74
+ */
75
+ export declare function joinColumns(blocks: string[], columnWidths: number[], separator?: string): string;