niceeval 0.6.1 → 0.7.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 (378) hide show
  1. package/INDEX.md +23 -23
  2. package/README.zh.md +6 -6
  3. package/dist/agents/types.d.ts +69 -7
  4. package/dist/context/types.d.ts +32 -12
  5. package/dist/i18n/en.d.ts +54 -0
  6. package/dist/i18n/zh-CN.d.ts +57 -3
  7. package/dist/o11y/types.d.ts +16 -2
  8. package/dist/report/aggregate.d.ts +32 -24
  9. package/dist/report/aggregate.js +158 -50
  10. package/dist/report/built-in/index.d.ts +2 -0
  11. package/dist/report/built-in/index.js +8 -0
  12. package/dist/report/components.d.ts +93 -160
  13. package/dist/report/components.js +377 -114
  14. package/dist/report/compute.d.ts +87 -81
  15. package/dist/report/compute.js +597 -417
  16. package/dist/report/flag.d.ts +32 -6
  17. package/dist/report/flag.js +92 -4
  18. package/dist/report/format.d.ts +19 -11
  19. package/dist/report/format.js +30 -13
  20. package/dist/report/index.d.ts +16 -16
  21. package/dist/report/index.js +20 -21
  22. package/dist/report/load.js +3 -2
  23. package/dist/report/locale.d.ts +57 -33
  24. package/dist/report/locale.js +122 -56
  25. package/dist/report/metrics.d.ts +23 -4
  26. package/dist/report/metrics.js +110 -25
  27. package/dist/report/primitives.d.ts +48 -15
  28. package/dist/report/primitives.js +135 -26
  29. package/dist/report/react/AttemptList.d.ts +9 -7
  30. package/dist/report/react/AttemptList.js +17 -10
  31. package/dist/report/react/DeltaTable.js +19 -18
  32. package/dist/report/react/EvalList.d.ts +4 -4
  33. package/dist/report/react/EvalList.js +0 -0
  34. package/dist/report/react/ExperimentComparison.d.ts +10 -0
  35. package/dist/report/react/ExperimentComparison.js +12 -0
  36. package/dist/report/react/ExperimentList.d.ts +4 -3
  37. package/dist/report/react/ExperimentList.js +17 -18
  38. package/dist/report/react/MetricBars.js +5 -4
  39. package/dist/report/react/MetricLine.js +12 -5
  40. package/dist/report/react/MetricMatrix.js +1 -1
  41. package/dist/report/react/MetricScatter.js +59 -28
  42. package/dist/report/react/MetricTable.js +2 -12
  43. package/dist/report/react/ScopeSummary.d.ts +10 -0
  44. package/dist/report/react/ScopeSummary.js +17 -0
  45. package/dist/report/react/Scoreboard.js +6 -6
  46. package/dist/report/react/cell.js +2 -2
  47. package/dist/report/react/chart-math.d.ts +23 -6
  48. package/dist/report/react/chart-math.js +71 -19
  49. package/dist/report/react/fixtures.d.ts +5 -9
  50. package/dist/report/react/fixtures.js +110 -147
  51. package/dist/report/react/index.d.ts +15 -5
  52. package/dist/report/react/index.js +18 -7
  53. package/dist/report/report.d.ts +137 -16
  54. package/dist/report/report.js +259 -28
  55. package/dist/report/text/faces.d.ts +17 -19
  56. package/dist/report/text/faces.js +253 -184
  57. package/dist/report/text/plot.js +1 -1
  58. package/dist/report/text/table.js +38 -7
  59. package/dist/report/tree.d.ts +90 -40
  60. package/dist/report/tree.js +252 -94
  61. package/dist/report/types.d.ts +247 -284
  62. package/dist/report/types.js +4 -3
  63. package/dist/report/web.d.ts +21 -5
  64. package/dist/report/web.js +42 -16
  65. package/dist/results/select.d.ts +38 -16
  66. package/dist/results/select.js +73 -25
  67. package/dist/results/types.d.ts +49 -14
  68. package/dist/runner/feedback/sink.d.ts +110 -0
  69. package/dist/runner/types.d.ts +513 -22
  70. package/dist/sandbox/docker.d.ts +23 -2
  71. package/dist/sandbox/e2b.d.ts +15 -1
  72. package/dist/sandbox/errors.d.ts +30 -3
  73. package/dist/sandbox/io-retry.d.ts +17 -0
  74. package/dist/sandbox/registry.d.ts +2 -0
  75. package/dist/sandbox/resolve.d.ts +18 -5
  76. package/dist/sandbox/retry.d.ts +11 -1
  77. package/dist/sandbox/types.d.ts +39 -5
  78. package/dist/sandbox/vercel.d.ts +7 -1
  79. package/dist/scoring/coverage.d.ts +30 -0
  80. package/dist/scoring/display.d.ts +21 -0
  81. package/dist/scoring/display.js +120 -0
  82. package/dist/scoring/types.d.ts +103 -20
  83. package/dist/shared/aggregate.d.ts +4 -2
  84. package/dist/shared/aggregate.js +8 -7
  85. package/dist/shared/types.d.ts +28 -0
  86. package/dist/tty-line.d.ts +0 -4
  87. package/dist/util.d.ts +23 -0
  88. package/docs-site/zh/README.md +44 -0
  89. package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
  90. package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
  91. package/docs-site/zh/examples/index.mdx +50 -0
  92. package/docs-site/zh/{concepts → explanation}/adapter.mdx +31 -13
  93. package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
  94. package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
  95. package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
  96. package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
  97. package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
  98. package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
  99. package/docs-site/zh/{concepts → explanation}/overview.mdx +11 -11
  100. package/docs-site/zh/{guides → explanation}/runner.mdx +18 -8
  101. package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
  102. package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +35 -33
  103. package/docs-site/zh/{guides → how-to}/authoring.mdx +35 -2
  104. package/docs-site/zh/{guides → how-to}/ci-integration.mdx +23 -12
  105. package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
  106. package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +47 -21
  107. package/docs-site/zh/{guides → how-to}/custom-reports.mdx +34 -39
  108. package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +25 -3
  109. package/docs-site/zh/{guides → how-to}/experiments.mdx +12 -5
  110. package/docs-site/zh/how-to/publish-report.mdx +105 -0
  111. package/docs-site/zh/{guides → how-to}/reporters.mdx +2 -2
  112. package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +56 -7
  113. package/docs-site/zh/how-to/sandbox-providers.mdx +350 -0
  114. package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +4 -4
  115. package/docs-site/zh/{guides → how-to}/viewing-results.mdx +82 -39
  116. package/docs-site/zh/{guides → how-to}/write-experiment.mdx +6 -4
  117. package/docs-site/zh/{guides → how-to}/write-send.mdx +30 -14
  118. package/docs-site/zh/index.mdx +24 -26
  119. package/docs-site/zh/introduction.mdx +8 -8
  120. package/docs-site/zh/reference/builtin-agents.mdx +32 -5
  121. package/docs-site/zh/reference/capabilities.mdx +8 -8
  122. package/docs-site/zh/reference/cli.mdx +40 -12
  123. package/docs-site/zh/reference/define-agent.mdx +58 -5
  124. package/docs-site/zh/reference/define-config.mdx +1 -1
  125. package/docs-site/zh/reference/define-eval.mdx +42 -9
  126. package/docs-site/zh/reference/events.mdx +3 -3
  127. package/docs-site/zh/reference/expect.mdx +26 -1
  128. package/docs-site/zh/{guides → reference}/official-adapters.mdx +32 -8
  129. package/docs-site/zh/{guides → reference}/report-components.mdx +45 -33
  130. package/docs-site/zh/{guides → reference}/results-data.mdx +21 -13
  131. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +57 -0
  132. package/docs-site/zh/troubleshooting/debugging.mdx +212 -0
  133. package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
  134. package/package.json +10 -2
  135. package/src/agents/ai-sdk-otel.test.ts +1 -0
  136. package/src/agents/ai-sdk.test.ts +3 -0
  137. package/src/agents/ai-sdk.ts +3 -0
  138. package/src/agents/bub-install-spec.test.ts +34 -0
  139. package/src/agents/bub-install-spec.ts +32 -0
  140. package/src/agents/bub.ts +31 -32
  141. package/src/agents/claude-code.test.ts +130 -9
  142. package/src/agents/claude-code.ts +76 -4
  143. package/src/agents/codex.test.ts +189 -40
  144. package/src/agents/codex.ts +155 -14
  145. package/src/agents/coding-cli-versions.test.ts +15 -0
  146. package/src/agents/coding-cli-versions.ts +3 -0
  147. package/src/agents/index.ts +13 -2
  148. package/src/agents/langgraph.test.ts +204 -0
  149. package/src/agents/langgraph.ts +495 -0
  150. package/src/agents/marketplace.ts +85 -0
  151. package/src/agents/native-config.test.ts +179 -0
  152. package/src/agents/native-config.ts +267 -0
  153. package/src/agents/openai-compat.test.ts +1 -0
  154. package/src/agents/openai-compat.ts +1 -1
  155. package/src/agents/openclaw.test.ts +31 -0
  156. package/src/agents/openclaw.ts +171 -0
  157. package/src/agents/plugin-config.test.ts +1 -0
  158. package/src/agents/sdk-streams.test.ts +79 -0
  159. package/src/agents/sdk-streams.ts +55 -10
  160. package/src/agents/skills.test.ts +1 -0
  161. package/src/agents/streaming.test.ts +3 -9
  162. package/src/agents/streaming.ts +2 -2
  163. package/src/agents/types.ts +71 -8
  164. package/src/agents/ui-message-stream.test.ts +3 -0
  165. package/src/cli.ts +446 -124
  166. package/src/context/context.test.ts +51 -12
  167. package/src/context/context.ts +162 -30
  168. package/src/context/session.test.ts +2 -1
  169. package/src/context/session.ts +115 -7
  170. package/src/context/types.ts +30 -12
  171. package/src/define.test.ts +13 -8
  172. package/src/define.ts +25 -4
  173. package/src/expect/index.ts +53 -23
  174. package/src/i18n/en.ts +81 -17
  175. package/src/i18n/zh-CN.ts +80 -17
  176. package/src/o11y/cost.test.ts +1 -0
  177. package/src/o11y/execution-tree.test.ts +1 -20
  178. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  179. package/src/o11y/otlp/parse.test.ts +1 -0
  180. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  181. package/src/o11y/parsers/bub.test.ts +1 -0
  182. package/src/o11y/parsers/claude-code.test.ts +1 -34
  183. package/src/o11y/parsers/openclaw.test.ts +154 -0
  184. package/src/o11y/parsers/openclaw.ts +310 -0
  185. package/src/o11y/prices.json +746 -311
  186. package/src/o11y/tool-names.test.ts +1 -0
  187. package/src/o11y/types.ts +16 -2
  188. package/src/report/aggregate.ts +178 -61
  189. package/src/report/built-in/index.tsx +9 -0
  190. package/src/report/components.tsx +625 -279
  191. package/src/report/compute.ts +723 -491
  192. package/src/report/dual-render.test.tsx +741 -1024
  193. package/src/report/flag.ts +104 -12
  194. package/src/report/format.ts +32 -12
  195. package/src/report/index.ts +119 -46
  196. package/src/report/load.ts +3 -2
  197. package/src/report/locale.ts +136 -65
  198. package/src/report/metrics.ts +108 -25
  199. package/src/report/primitives.tsx +196 -45
  200. package/src/report/react/AttemptList.tsx +30 -43
  201. package/src/report/react/DeltaTable.tsx +63 -45
  202. package/src/report/react/EvalList.tsx +0 -0
  203. package/src/report/react/ExperimentComparison.tsx +73 -0
  204. package/src/report/react/ExperimentList.tsx +50 -32
  205. package/src/report/react/MetricBars.tsx +5 -4
  206. package/src/report/react/MetricLine.tsx +13 -8
  207. package/src/report/react/MetricMatrix.tsx +2 -2
  208. package/src/report/react/MetricScatter.tsx +86 -34
  209. package/src/report/react/MetricTable.tsx +4 -76
  210. package/src/report/react/ScopeSummary.tsx +86 -0
  211. package/src/report/react/Scoreboard.tsx +28 -10
  212. package/src/report/react/cell.tsx +2 -2
  213. package/src/report/react/chart-math.test.ts +85 -0
  214. package/src/report/react/chart-math.ts +101 -22
  215. package/src/report/react/enhance.js +89 -5
  216. package/src/report/react/fixtures.ts +114 -154
  217. package/src/report/react/index.tsx +24 -39
  218. package/src/report/react/render.test.tsx +138 -158
  219. package/src/report/react/styles.css +243 -82
  220. package/src/report/report.test.ts +779 -841
  221. package/src/report/report.ts +423 -41
  222. package/src/report/text/faces.ts +290 -193
  223. package/src/report/text/plot.ts +1 -1
  224. package/src/report/text/table.ts +44 -7
  225. package/src/report/tree.ts +362 -104
  226. package/src/report/types.ts +261 -271
  227. package/src/report/web.ts +63 -20
  228. package/src/results/annotated-source.test.ts +62 -9
  229. package/src/results/annotated-source.ts +64 -6
  230. package/src/results/attempt-evidence.test.ts +13 -11
  231. package/src/results/attempt-evidence.ts +20 -13
  232. package/src/results/attempt-source.ts +6 -3
  233. package/src/results/copy.ts +150 -60
  234. package/src/results/host-equivalence.test.ts +34 -20
  235. package/src/results/index.ts +12 -4
  236. package/src/results/locator.test.ts +1 -22
  237. package/src/results/open.ts +15 -5
  238. package/src/results/publish.ts +149 -0
  239. package/src/results/results.test.ts +89 -54
  240. package/src/results/select.ts +104 -34
  241. package/src/results/truncate.ts +90 -0
  242. package/src/results/types.ts +43 -14
  243. package/src/results/writer.ts +31 -13
  244. package/src/runner/attempt.test.ts +138 -7
  245. package/src/runner/attempt.ts +603 -104
  246. package/src/runner/discover.test.ts +47 -0
  247. package/src/runner/discover.ts +36 -2
  248. package/src/runner/eval-source.test.ts +1 -27
  249. package/src/runner/feedback/agent.test.ts +504 -0
  250. package/src/runner/feedback/agent.ts +409 -0
  251. package/src/runner/feedback/ci.test.ts +562 -0
  252. package/src/runner/feedback/ci.ts +401 -0
  253. package/src/runner/feedback/coordinator.test.ts +317 -0
  254. package/src/runner/feedback/coordinator.ts +397 -0
  255. package/src/runner/feedback/failure.ts +40 -0
  256. package/src/runner/feedback/human.test.ts +616 -0
  257. package/src/runner/feedback/human.ts +535 -0
  258. package/src/runner/feedback/index.ts +66 -0
  259. package/src/runner/feedback/io.ts +78 -0
  260. package/src/runner/feedback/profile.test.ts +50 -0
  261. package/src/runner/feedback/profile.ts +58 -0
  262. package/src/runner/feedback/reducer.test.ts +395 -0
  263. package/src/runner/feedback/reducer.ts +260 -0
  264. package/src/runner/feedback/renderer.ts +82 -0
  265. package/src/runner/feedback/sink.ts +203 -0
  266. package/src/runner/feedback/testing.ts +106 -0
  267. package/src/runner/ledger.test.ts +230 -0
  268. package/src/runner/ledger.ts +329 -0
  269. package/src/runner/report.test.ts +128 -3
  270. package/src/runner/report.ts +33 -9
  271. package/src/runner/reporters/artifacts.ts +8 -2
  272. package/src/runner/reporters/braintrust.test.ts +8 -7
  273. package/src/runner/reporters/braintrust.ts +9 -2
  274. package/src/runner/reporters/index.ts +2 -2
  275. package/src/runner/reporters/json.test.ts +162 -0
  276. package/src/runner/reporters/json.ts +35 -8
  277. package/src/runner/reporters/shared.ts +1 -5
  278. package/src/runner/run.test.ts +760 -3
  279. package/src/runner/run.ts +243 -37
  280. package/src/runner/sandbox-prep.ts +3 -42
  281. package/src/runner/timing.ts +158 -0
  282. package/src/runner/types.ts +518 -22
  283. package/src/sandbox/checkpoint.test.ts +55 -0
  284. package/src/sandbox/checkpoint.ts +29 -8
  285. package/src/sandbox/cli-commands.ts +407 -0
  286. package/src/sandbox/docker.ts +115 -16
  287. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  288. package/src/sandbox/e2b-agent-template.ts +94 -0
  289. package/src/sandbox/e2b.ts +74 -9
  290. package/src/sandbox/errors.ts +111 -4
  291. package/src/sandbox/index.ts +2 -0
  292. package/src/sandbox/io-retry.test.ts +58 -0
  293. package/src/sandbox/io-retry.ts +45 -0
  294. package/src/sandbox/keep-registry.test.ts +86 -0
  295. package/src/sandbox/keep-registry.ts +142 -0
  296. package/src/sandbox/keep.ts +178 -0
  297. package/src/sandbox/paths.test.ts +1 -0
  298. package/src/sandbox/paths.ts +19 -8
  299. package/src/sandbox/registry.ts +20 -3
  300. package/src/sandbox/resolve.ts +76 -11
  301. package/src/sandbox/retry.test.ts +70 -0
  302. package/src/sandbox/retry.ts +46 -4
  303. package/src/sandbox/types.ts +44 -6
  304. package/src/sandbox/vercel.ts +43 -20
  305. package/src/scoring/collector.ts +60 -17
  306. package/src/scoring/coverage.ts +95 -0
  307. package/src/scoring/diff.ts +81 -0
  308. package/src/scoring/display.test.ts +121 -0
  309. package/src/scoring/display.ts +133 -0
  310. package/src/scoring/evidence.test.ts +189 -0
  311. package/src/scoring/judge.test.ts +142 -0
  312. package/src/scoring/judge.ts +15 -18
  313. package/src/scoring/scoped.ts +217 -50
  314. package/src/scoring/types.ts +117 -20
  315. package/src/scoring/verdict.ts +16 -4
  316. package/src/shared/aggregate.ts +8 -6
  317. package/src/shared/types.ts +31 -0
  318. package/src/show/compose.ts +50 -67
  319. package/src/show/index.ts +127 -56
  320. package/src/show/render.ts +662 -131
  321. package/src/show/report-host.test.ts +188 -0
  322. package/src/show/report-host.ts +375 -0
  323. package/src/show/show.test.ts +320 -54
  324. package/src/tty-line.ts +8 -26
  325. package/src/util.test.ts +1 -0
  326. package/src/util.ts +41 -0
  327. package/src/view/app/App.test.tsx +69 -0
  328. package/src/view/app/App.tsx +144 -48
  329. package/src/view/app/components/AttemptModal.tsx +423 -11
  330. package/src/view/app/components/CodeView.tsx +41 -14
  331. package/src/view/app/components/CopyControls.tsx +2 -2
  332. package/src/view/app/i18n.ts +37 -17
  333. package/src/view/app/lib/attempt-route.test.ts +1 -0
  334. package/src/view/app/lib/verdict.ts +7 -9
  335. package/src/view/app/main.tsx +13 -8
  336. package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
  337. package/src/view/app/types.ts +4 -1
  338. package/src/view/artifact-serving.test.ts +2 -1
  339. package/src/view/client-dist/app.css +1 -1
  340. package/src/view/client-dist/app.js +17 -17
  341. package/src/view/data.test.ts +10 -3
  342. package/src/view/data.ts +155 -49
  343. package/src/view/index.ts +56 -41
  344. package/src/view/server.ts +37 -15
  345. package/src/view/shared/types.ts +34 -5
  346. package/src/view/styles.css +227 -0
  347. package/src/view/view-report.test.ts +167 -62
  348. package/dist/report/built-ins/experiment-comparison.d.ts +0 -1
  349. package/dist/report/built-ins/experiment-comparison.js +0 -13
  350. package/dist/report/built-ins/index.d.ts +0 -1
  351. package/dist/report/built-ins/index.js +0 -2
  352. package/dist/report/react/GroupSummary.d.ts +0 -8
  353. package/dist/report/react/GroupSummary.js +0 -8
  354. package/dist/report/react/RunOverview.d.ts +0 -8
  355. package/dist/report/react/RunOverview.js +0 -12
  356. package/docs-site/zh/example/ai-agent-application.mdx +0 -152
  357. package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
  358. package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
  359. package/docs-site/zh/example/showcase.mdx +0 -39
  360. package/docs-site/zh/guides/publish-report.mdx +0 -91
  361. package/docs-site/zh/guides/sandbox-providers.mdx +0 -102
  362. package/src/report/built-in-user-parity.test.tsx +0 -640
  363. package/src/report/built-ins/experiment-comparison.tsx +0 -19
  364. package/src/report/built-ins/index.ts +0 -2
  365. package/src/report/react/GroupSummary.tsx +0 -66
  366. package/src/report/react/RunOverview.tsx +0 -109
  367. package/src/runner/reporters/console.ts +0 -70
  368. package/src/runner/reporters/live.test.ts +0 -56
  369. package/src/runner/reporters/live.ts +0 -247
  370. package/src/runner/reporters/quiet.test.ts +0 -66
  371. package/src/runner/reporters/quiet.ts +0 -49
  372. package/src/runner/reporters/table.ts +0 -277
  373. /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
  374. /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
  375. /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
  376. /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
  377. /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
  378. /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
@@ -0,0 +1,188 @@
1
+ // cases: docs/engineering/unit-tests/reports/cases.md
2
+ // 「外壳、页面与 Tabs」——宿主装载规范化、页索引命令上下文与标题回退链
3
+ // (契约:docs/feature/reports/library/shell.md)。
4
+ // 页内树的 resolve / render 归报告库测试;这里只测宿主侧的规范化与选择逻辑(纯函数)。
5
+
6
+ import { describe, expect, it } from "vitest";
7
+ import {
8
+ BUILT_IN_PAGE_TITLE,
9
+ BUILT_IN_REPORT_TITLE,
10
+ HostReportError,
11
+ localizeText,
12
+ localizedTextEquals,
13
+ normalizeHostReport,
14
+ resolveReportTitle,
15
+ showCommand,
16
+ } from "./report-host.ts";
17
+ import { pageIndexText } from "./render.ts";
18
+
19
+ const tree = { kind: "node" }; // 页 content 对宿主是不透明值,规范化不解析树
20
+
21
+ describe("装载规范化:外壳 + 非空页列表", () => {
22
+ it("content 缩写展开为唯一页 id `report`,页名是内置页名「报告 / Report」", () => {
23
+ const report = normalizeHostReport({ kind: "report", content: tree }, "reports/frontier.tsx");
24
+ expect(report.pages).toHaveLength(1);
25
+ expect(report.pages[0]).toMatchObject({ id: "report", title: BUILT_IN_PAGE_TITLE, content: tree });
26
+ expect(report.links).toEqual([]);
27
+ });
28
+
29
+ it("pages 形态按声明序保留页列表与外壳字段", () => {
30
+ const report = normalizeHostReport(
31
+ {
32
+ kind: "report",
33
+ title: { en: "Memory Evals", "zh-CN": "记忆能力评测" },
34
+ links: [{ label: "GitHub", href: "https://example.com" }],
35
+ footer: "Published nightly.",
36
+ pages: [
37
+ { id: "overview", title: { en: "Overview", "zh-CN": "总览" }, content: tree },
38
+ { id: "exam", title: { en: "Exam", "zh-CN": "成绩单" }, content: tree },
39
+ ],
40
+ },
41
+ "reports/site.tsx",
42
+ );
43
+ expect(report.pages.map((p) => p.id)).toEqual(["overview", "exam"]);
44
+ expect(report.title).toEqual({ en: "Memory Evals", "zh-CN": "记忆能力评测" });
45
+ expect(report.footer).toBe("Published nightly.");
46
+ });
47
+
48
+ it("content 与 pages 恰好声明一个:同给 / 同缺都报错,文案给出 <ExperimentComparison /> 下一步", () => {
49
+ for (const bad of [
50
+ { kind: "report", content: tree, pages: [{ id: "a", title: "A", content: tree }] },
51
+ { kind: "report", title: "T" },
52
+ ]) {
53
+ expect(() => normalizeHostReport(bad, "reports/site.tsx")).toThrow(HostReportError);
54
+ expect(() => normalizeHostReport(bad, "reports/site.tsx")).toThrow(/<ExperimentComparison \/>/);
55
+ }
56
+ });
57
+
58
+ it("空 pages 列表 / 重复 page id / 非法 page id 在装载期报错", () => {
59
+ expect(() => normalizeHostReport({ kind: "report", pages: [] }, "r.tsx")).toThrow(/non-empty/);
60
+ expect(() =>
61
+ normalizeHostReport(
62
+ { kind: "report", pages: [{ id: "exam", title: "A", content: tree }, { id: "exam", title: "B", content: tree }] },
63
+ "r.tsx",
64
+ ),
65
+ ).toThrow(/duplicate page id "exam"/);
66
+ for (const id of ["Exam", "a/b"]) {
67
+ expect(() =>
68
+ normalizeHostReport({ kind: "report", pages: [{ id, title: "A", content: tree }] }, "r.tsx"),
69
+ ).toThrow(/invalid/);
70
+ }
71
+ });
72
+
73
+ it("默认导出不是 defineReport 产物:完整用户反馈", () => {
74
+ expect(() => normalizeHostReport({ some: "object" }, "reports/bad.tsx")).toThrow(
75
+ /does not default-export a report/,
76
+ );
77
+ });
78
+
79
+ it("ReportLink.icon 是 { svg: string }:合法形状原样透传;无类型 JS 传其它形状装载报错", () => {
80
+ const svg = '<svg viewBox="0 0 16 16"><path d="M0 0h16v16z"/></svg>';
81
+ const report = normalizeHostReport(
82
+ { kind: "report", content: tree, links: [{ label: "GitHub", href: "https://example.com", icon: { svg } }] },
83
+ "reports/site.tsx",
84
+ );
85
+ expect(report.links[0]!.icon).toEqual({ svg });
86
+
87
+ // ReactNode / 组件 / 裸字符串都不是 { svg: string },装载期以完整用户反馈拒绝。
88
+ const reactNode = { $$typeof: Symbol.for("react.transitional.element"), type: "svg", props: {} };
89
+ for (const icon of [reactNode, "<svg/>", { svg: 42 }, { svg: "" }]) {
90
+ expect(() =>
91
+ normalizeHostReport(
92
+ { kind: "report", content: tree, links: [{ label: "GitHub", href: "https://example.com", icon }] },
93
+ "reports/site.tsx",
94
+ ),
95
+ ).toThrow(/icon" must be \{ svg: string \}/);
96
+ }
97
+ });
98
+
99
+ it("旧 build 函数形态(集成前桥接)恒为单页 report", () => {
100
+ const legacy = { build: () => tree };
101
+ const report = normalizeHostReport(legacy, "the built-in report");
102
+ expect(report.pages.map((p) => p.id)).toEqual(["report"]);
103
+ expect(report.pages[0]!.content).toBe(legacy);
104
+ });
105
+ });
106
+
107
+ describe("标题回退链:def.title → 唯一且相同的快照 name → 内置文案「Eval 运行结果 / Eval Results」", () => {
108
+ it("def.title 优先", () => {
109
+ expect(resolveReportTitle({ en: "T" }, [{ name: "S" }])).toEqual({ en: "T" });
110
+ });
111
+
112
+ it("无 def.title 时取 Scope 中唯一且相同(深相等,键顺序无关)的非空快照 name", () => {
113
+ expect(resolveReportTitle(undefined, [{ name: { en: "S", "zh-CN": "斯" } }, { name: { "zh-CN": "斯", en: "S" } }]))
114
+ .toEqual({ en: "S", "zh-CN": "斯" });
115
+ expect(resolveReportTitle(undefined, [{}, { name: "Only" }])).toBe("Only");
116
+ });
117
+
118
+ it("多个不同 name(en 相同、zh-CN 不同也算不同)不随机挑,落内置文案;全无 name 亦然", () => {
119
+ expect(
120
+ resolveReportTitle(undefined, [{ name: { en: "S", "zh-CN": "甲" } }, { name: { en: "S", "zh-CN": "乙" } }]),
121
+ ).toEqual(BUILT_IN_REPORT_TITLE);
122
+ expect(resolveReportTitle(undefined, [{}, {}])).toEqual(BUILT_IN_REPORT_TITLE);
123
+ expect(resolveReportTitle("", [])).toEqual(BUILT_IN_REPORT_TITLE); // 空串标题不算声明
124
+ // 链终点是内置文案(shell.md),不是产品名——品牌位固定 NiceEval,不吃标题。
125
+ expect(BUILT_IN_REPORT_TITLE).toEqual({ en: "Eval Results", "zh-CN": "Eval 运行结果" });
126
+ });
127
+
128
+ it("LocalizedText 深相等按字段值,不看键顺序", () => {
129
+ expect(localizedTextEquals({ a: "1", b: "2" }, { b: "2", a: "1" })).toBe(true);
130
+ expect(localizedTextEquals({ a: "1" }, { a: "1", b: "2" })).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe("LocalizedText 回退:locale → en → 键字典序第一个非空值", () => {
135
+ it("三级回退各自命中", () => {
136
+ expect(localizeText({ "zh-CN": "中", en: "E" }, "zh-CN")).toBe("中");
137
+ expect(localizeText({ "zh-CN": "中", en: "E" }, "fr")).toBe("E");
138
+ expect(localizeText({ "zh-TW": "繁", ja: "日" }, "en")).toBe("日"); // ja < zh-TW 字典序
139
+ expect(localizeText("plain", "en")).toBe("plain");
140
+ expect(localizeText({}, "en")).toBeUndefined();
141
+ });
142
+ });
143
+
144
+ describe("页索引与索引命令上下文", () => {
145
+ const report = normalizeHostReport(
146
+ {
147
+ kind: "report",
148
+ title: { en: "Memory Evals", "zh-CN": "记忆能力评测" },
149
+ links: [{ label: "GitHub", href: "https://example.com", icon: { svg: "<svg data-mark></svg>" } }],
150
+ pages: [
151
+ { id: "overview", title: { en: "Overview", "zh-CN": "总览" }, content: tree },
152
+ { id: "exam", title: { en: "Exam", "zh-CN": "成绩单" }, content: tree },
153
+ ],
154
+ },
155
+ "reports/site.tsx",
156
+ );
157
+
158
+ it("索引命令保留当前 --results / --report 与位置参数,复制即可复现下一层视图", () => {
159
+ const text = pageIndexText({
160
+ report,
161
+ title: "记忆能力评测",
162
+ command: { patterns: [], results: "tmp/published-results", report: "reports/site.tsx" },
163
+ locale: "zh-CN",
164
+ });
165
+ expect(text).toContain("记忆能力评测 · 2 页");
166
+ expect(text).toContain("niceeval show --results tmp/published-results --report reports/site.tsx --page overview");
167
+ expect(text).toContain("niceeval show --results tmp/published-results --report reports/site.tsx --page exam");
168
+ expect(text).toContain("总览");
169
+ expect(text).toContain("成绩单");
170
+ });
171
+
172
+ it("show 不消费 links:页索引不含 icon svg 与 href(icon 是 web 面属性)", () => {
173
+ const text = pageIndexText({
174
+ report,
175
+ title: "Memory Evals",
176
+ command: { patterns: [] },
177
+ locale: "en",
178
+ });
179
+ expect(text).not.toContain("<svg");
180
+ expect(text).not.toContain("https://example.com");
181
+ });
182
+
183
+ it("showCommand 按序携带位置参数与 --experiment / --results / --report / --page", () => {
184
+ expect(
185
+ showCommand({ patterns: ["memory/swelancer"], experiment: "dev-e2b", report: "reports/site.tsx", page: "exam" }),
186
+ ).toBe("niceeval show memory/swelancer --experiment dev-e2b --report reports/site.tsx --page exam");
187
+ });
188
+ });
@@ -0,0 +1,375 @@
1
+ // 报告装载的宿主联系面:show 与 view 共用(view 从这里 import;两个宿主对同一份
2
+ // defineReport 产物必须得到同一个规范化结果,见 docs/feature/reports/library/shell.md)。
3
+ //
4
+ // 契约(docs/feature/reports/library/shell.md「行为约束」/ architecture.md「外壳与页:装载规范化」):
5
+ // - `--report` 文件默认导出恒为 defineReport 产物;裸宿主装载 `niceeval/report/built-in` 的默认导出,
6
+ // 走同一条「装载 → resolve → validate → render」管线。
7
+ // - 装载规范化唯一产物是「外壳 + 非空页列表」:`defineReport(树)` ≡ `{ content: 树 }` ≡
8
+ // `pages: [{ id: "report", title: 内置页名, content: 树 }]`。
9
+ // - 标题回退单点:def.title → Scope 中唯一且相同(LocalizedText 深相等)的非空快照 name →
10
+ // 内置文案「Eval 运行结果 / Eval Results」。落点是首页 hero、浏览器标题与 show 页索引标题行;
11
+ // 页头品牌位恒为 NiceEval 字标,不归 title。
12
+ // - LocalizedText 回退:当前 locale → en → 按 locale 键字典序的第一个非空值。
13
+ //
14
+ // ⚠ 集成状态:src/report/** 正被并行重写(plan/reports-redesign-implementation.md)。这里把宿主
15
+ // 需要的新报告 runtime API 收敛成唯一联系面,并对「新 dist 尚未产出」提供旧 API 桥接
16
+ // (旧 ReportDefinition 是 build 函数形态,恒为单页)。集成阶段的对接点(搜索 INTEGRATION):
17
+ // 1. `dist/report/built-in/index.js` 默认导出(内建报告入口;恒为新格式,无桥接——
18
+ // 它随本包源码一起构建,不存在旧产物)。
19
+ // 2. `normalizeReport(definition)`:装载规范化(外壳 + 非空页列表);旧桥接对 build 形态自行包单页。
20
+ // 3. `renderReportTreeToText(tree, ctx, options)` / `renderReportTreeToStaticHtml(tree, ctx, options)`:
21
+ // 逐页渲染入口(ctx = { scope, results, report });旧桥接调 renderReportToText /
22
+ // renderReportToStaticHtml(definition 整体)。
23
+
24
+ import type { Results } from "../results/index.ts";
25
+ import type { LocalizedText } from "../types.ts";
26
+
27
+ /** 页:宿主寻址单位(`--page <id>` / `#/page/<id>`)。content 是报告树(旧桥接下是整个旧 definition)。 */
28
+ export interface HostReportPage {
29
+ id: string;
30
+ title: LocalizedText;
31
+ content: unknown;
32
+ }
33
+
34
+ export interface HostReportLink {
35
+ label: LocalizedText;
36
+ href: string;
37
+ /**
38
+ * 可选内联 SVG 字标(shell.md「字段穷尽」):web 面渲染在 label 前,静态导出原样内联;
39
+ * 不收组件(外壳声明经 viewData 序列化边界);show 不消费。
40
+ */
41
+ icon?: { svg: string };
42
+ }
43
+
44
+ /** `{src}` 与 `{inline}` 两种形态不可同时出现(shell.md「字段穷尽」)。 */
45
+ export type HostReportAsset = { src: string; inline?: never } | { inline: string; src?: never };
46
+
47
+ /** 装载规范化产物:外壳 + 非空页列表。show 只消费 title / pages;其余是 web 面属性。 */
48
+ export interface HostReport {
49
+ title?: LocalizedText;
50
+ links: HostReportLink[];
51
+ footer?: LocalizedText;
52
+ scripts: HostReportAsset[];
53
+ styles: HostReportAsset[];
54
+ pages: HostReportPage[];
55
+ }
56
+
57
+ /** 单页缩写展开出的唯一页使用内置页名(shell.md:「报告 / Report」)。 */
58
+ export const BUILT_IN_PAGE_TITLE: LocalizedText = { en: "Report", "zh-CN": "报告" };
59
+ export const SINGLE_PAGE_ID = "report";
60
+
61
+ /** 标题回退链的终点:内置文案「Eval 运行结果 / Eval Results」(shell.md「行为约束」)。 */
62
+ export const BUILT_IN_REPORT_TITLE: LocalizedText = { en: "Eval Results", "zh-CN": "Eval 运行结果" };
63
+
64
+ /** 规范化报告声明的只读注入(组合组件 ctx.report;ReportMeta,见 library/layout.md)。 */
65
+ export interface HostReportMeta {
66
+ title: LocalizedText;
67
+ links: HostReportLink[];
68
+ footer?: LocalizedText;
69
+ pages: { id: string; title: LocalizedText }[];
70
+ pageId: string;
71
+ }
72
+
73
+ /** 可预期的装载用户错误(与 ReportLoadError 同待遇:打一句直说问题与下一步,不抛堆栈)。 */
74
+ export class HostReportError extends Error {}
75
+
76
+ // ───────────────────────── LocalizedText ─────────────────────────
77
+
78
+ /**
79
+ * LocalizedText 的确定回退(shell.md「行为约束」):当前 locale → en → 按 locale 键字典序的
80
+ * 第一个非空值。对象没有任何非空值时返回 undefined(装载期应当已报错,渲染兜底不再抛)。
81
+ */
82
+ export function localizeText(text: LocalizedText | undefined, locale: string): string | undefined {
83
+ if (text === undefined) return undefined;
84
+ if (typeof text === "string") return text || undefined;
85
+ const exact = text[locale];
86
+ if (exact) return exact;
87
+ if (text.en) return text.en;
88
+ for (const key of Object.keys(text).sort()) {
89
+ if (text[key]) return text[key];
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ /** LocalizedText 深相等:按字段值比较,对象键顺序不影响结果(shell.md「标题回退必须确定」)。 */
95
+ export function localizedTextEquals(a: LocalizedText, b: LocalizedText): boolean {
96
+ if (typeof a === "string" || typeof b === "string") return a === b;
97
+ const keysA = Object.keys(a).sort();
98
+ const keysB = Object.keys(b).sort();
99
+ if (keysA.length !== keysB.length) return false;
100
+ return keysA.every((key, i) => key === keysB[i] && a[key] === b[key]);
101
+ }
102
+
103
+ /** LocalizedText 是否有任何非空值(空串 / 全空对象都算空)。 */
104
+ function hasText(text: LocalizedText | undefined): text is LocalizedText {
105
+ if (text === undefined) return false;
106
+ if (typeof text === "string") return text.length > 0;
107
+ return Object.values(text).some((v) => typeof v === "string" && v.length > 0);
108
+ }
109
+
110
+ /**
111
+ * 标题回退单点(show 页索引标题与 view hero / 浏览器标题共用):def.title → Scope 中唯一且相同
112
+ * (深相等)的非空快照 name → 内置文案「Eval 运行结果 / Eval Results」。多个不同 name 时
113
+ * 不随机挑一个,直接落到内置文案。
114
+ */
115
+ export function resolveReportTitle(
116
+ defTitle: LocalizedText | undefined,
117
+ snapshots: readonly { name?: LocalizedText }[],
118
+ ): LocalizedText {
119
+ if (hasText(defTitle)) return defTitle;
120
+ let unique: LocalizedText | undefined;
121
+ for (const snapshot of snapshots) {
122
+ if (!hasText(snapshot.name)) continue;
123
+ if (unique === undefined) {
124
+ unique = snapshot.name;
125
+ } else if (!localizedTextEquals(unique, snapshot.name)) {
126
+ return BUILT_IN_REPORT_TITLE;
127
+ }
128
+ }
129
+ return unique ?? BUILT_IN_REPORT_TITLE;
130
+ }
131
+
132
+ /** 组合组件 ctx.report 的形状(走完回退链的 title;scripts / styles 不进)。 */
133
+ export function reportMetaFor(
134
+ report: HostReport,
135
+ pageId: string,
136
+ snapshots: readonly { name?: LocalizedText }[],
137
+ ): HostReportMeta {
138
+ return {
139
+ title: resolveReportTitle(report.title, snapshots),
140
+ links: report.links,
141
+ ...(report.footer !== undefined ? { footer: report.footer } : {}),
142
+ pages: report.pages.map((p) => ({ id: p.id, title: p.title })),
143
+ pageId,
144
+ };
145
+ }
146
+
147
+ // ───────────────────────── 装载与规范化 ─────────────────────────
148
+
149
+ /** 旧(集成前)ReportDefinition:build 函数形态,恒为单页。集成阶段删除。 */
150
+ interface LegacyReportDefinition {
151
+ build: (ctx: unknown) => unknown;
152
+ }
153
+
154
+ function isLegacyDefinition(value: unknown): value is LegacyReportDefinition {
155
+ return (
156
+ (typeof value === "object" || typeof value === "function") &&
157
+ value !== null &&
158
+ typeof (value as Partial<LegacyReportDefinition>).build === "function"
159
+ );
160
+ }
161
+
162
+ const PAGE_ID_PATTERN = /^[a-z0-9-]+$/;
163
+
164
+ /**
165
+ * 装载期校验 `ReportLink.icon`(shell.md「字段穷尽」):唯一合法形状是 `{ svg: string }`。
166
+ * 无类型 JS 传组件 / ReactNode / 裸字符串都在这里被完整用户反馈拒绝——外壳声明要过
167
+ * viewData 的序列化边界,ReactNode 过不去,可序列化是外壳契约的一部分。
168
+ */
169
+ function assertLinkIcon(link: unknown, sourceLabel: string): void {
170
+ const icon = (link as { icon?: unknown })?.icon;
171
+ if (icon === undefined) return;
172
+ const svg = (icon as { svg?: unknown })?.svg;
173
+ if (typeof icon !== "object" || icon === null || typeof svg !== "string" || svg.length === 0) {
174
+ throw new HostReportError(
175
+ `${sourceLabel}: a link "icon" must be { svg: string } — an inline SVG string rendered before the label. ` +
176
+ "Components and React nodes are not accepted: the shell declaration crosses a serialization boundary. " +
177
+ 'Write e.g. icon: { svg: "<svg …>…</svg>" }.',
178
+ );
179
+ }
180
+ }
181
+
182
+ /**
183
+ * 装载规范化:把 defineReport 产物折成「外壳 + 非空页列表」。三种入参写法
184
+ * (树 / `content:` / `pages:`)展开成同一形状;page id 的唯一性与字符纪律在这里校验
185
+ * (shell.md「校验分两期」的装载期部分)。
186
+ */
187
+ export function normalizeHostReport(definition: unknown, sourceLabel: string): HostReport {
188
+ // INTEGRATION(2):新 defineReport 产物(kind === "report")。规范化后的外壳与页列表
189
+ // 若由新报告 runtime 导出的 normalizeReport 承担,这里改为直接调它;当前按 docs 的
190
+ // 字段穷尽读取产物自带的声明。
191
+ if (typeof definition === "object" && definition !== null && (definition as { kind?: unknown }).kind === "report") {
192
+ const def = definition as {
193
+ title?: LocalizedText;
194
+ links?: HostReportLink[];
195
+ footer?: LocalizedText;
196
+ scripts?: HostReportAsset[];
197
+ styles?: HostReportAsset[];
198
+ content?: unknown;
199
+ pages?: { id: string; title: LocalizedText; content: unknown }[];
200
+ };
201
+ const hasContent = def.content !== undefined;
202
+ const hasPages = def.pages !== undefined;
203
+ if (hasContent === hasPages) {
204
+ // content / pages 同缺或同给:装载期完整用户反馈,下一步是 content: <ExperimentComparison />。
205
+ throw new HostReportError(
206
+ `${sourceLabel}: a report declares exactly one of "content" or "pages" — ` +
207
+ (hasContent ? "it declares both. " : "it declares neither. ") +
208
+ `To render the built-in report content, write: content: <ExperimentComparison />.`,
209
+ );
210
+ }
211
+ const pages: HostReportPage[] = hasPages
212
+ ? def.pages!.map((p) => ({ id: p.id, title: p.title, content: p.content }))
213
+ : [{ id: SINGLE_PAGE_ID, title: BUILT_IN_PAGE_TITLE, content: def.content }];
214
+ if (pages.length === 0) {
215
+ throw new HostReportError(`${sourceLabel}: "pages" must be a non-empty list of report pages.`);
216
+ }
217
+ const seen = new Set<string>();
218
+ for (const page of pages) {
219
+ if (!PAGE_ID_PATTERN.test(page.id)) {
220
+ throw new HostReportError(
221
+ `${sourceLabel}: page id "${page.id}" is invalid. Page ids use lowercase letters, digits and hyphens.`,
222
+ );
223
+ }
224
+ if (seen.has(page.id)) {
225
+ throw new HostReportError(`${sourceLabel}: duplicate page id "${page.id}". Page ids must be unique per file.`);
226
+ }
227
+ seen.add(page.id);
228
+ }
229
+ for (const link of def.links ?? []) assertLinkIcon(link, sourceLabel);
230
+ return {
231
+ ...(def.title !== undefined ? { title: def.title } : {}),
232
+ links: def.links ?? [],
233
+ ...(def.footer !== undefined ? { footer: def.footer } : {}),
234
+ scripts: def.scripts ?? [],
235
+ styles: def.styles ?? [],
236
+ pages,
237
+ };
238
+ }
239
+
240
+ // INTEGRATION(2) 桥接:旧 build 函数形态(集成前的 dist);恒为单页,无外壳字段。
241
+ if (isLegacyDefinition(definition)) {
242
+ return {
243
+ links: [],
244
+ scripts: [],
245
+ styles: [],
246
+ pages: [{ id: SINGLE_PAGE_ID, title: BUILT_IN_PAGE_TITLE, content: definition }],
247
+ };
248
+ }
249
+
250
+ throw new HostReportError(
251
+ `${sourceLabel} does not default-export a report. Export default defineReport(<ExperimentComparison />) (or defineReport({ title, content })) from "niceeval/report".`,
252
+ );
253
+ }
254
+
255
+ /**
256
+ * 装载宿主报告:`--report <file>` 走 loadReportFile;缺省(裸 show / 裸 view)装载内建报告的
257
+ * 默认导出。两条路都进 normalizeHostReport,同一条装载管线。
258
+ */
259
+ export async function loadHostReport(
260
+ cwd: string,
261
+ reportPath: string | undefined,
262
+ options?: { freshImport?: boolean },
263
+ ): Promise<HostReport> {
264
+ if (reportPath !== undefined) {
265
+ const { loadReportFile } = await import("../../dist/report/load.js");
266
+ const definition = await loadReportFile(cwd, reportPath, options);
267
+ return normalizeHostReport(definition, reportPath);
268
+ }
269
+ return normalizeHostReport(await loadBuiltInDefinition(), "the built-in report");
270
+ }
271
+
272
+ /** 内建报告即 `niceeval/report/built-in` 的默认导出(dist 预编译产物,`prepare` 时构建)。 */
273
+ async function loadBuiltInDefinition(): Promise<unknown> {
274
+ const mod = await import("../../dist/report/built-in/index.js");
275
+ return mod.default;
276
+ }
277
+
278
+ // ───────────────────────── 逐页渲染 ─────────────────────────
279
+
280
+ export interface HostRenderContext {
281
+ /** 宿主按官方现刻水位挑好的 Scope(集成前类型名仍是 Selection;两者同物)。 */
282
+ scope: unknown;
283
+ results: Results;
284
+ report: HostReportMeta;
285
+ }
286
+
287
+ export interface HostTextRenderOptions {
288
+ width?: number;
289
+ locale?: string;
290
+ /**
291
+ * 索引命令的完整上下文(docs/feature/reports/show/reports.md:「索引命令携带完整上下文」):
292
+ * 组索引 / 页索引输出的每一条可复制命令都保留当前 --results / --report / --page 与位置参数。
293
+ * INTEGRATION(3):由新 text 面消费;旧桥接的 renderReportToText 不认识该字段,原样忽略。
294
+ */
295
+ commandContext?: HostCommandContext;
296
+ }
297
+
298
+ export interface HostCommandContext {
299
+ patterns: string[];
300
+ results?: string;
301
+ experiment?: string;
302
+ report?: string;
303
+ page?: string;
304
+ }
305
+
306
+ /** 按上下文拼一条可复制的 show 命令(页索引 / 组索引共用的携带规则)。 */
307
+ export function showCommand(ctx: HostCommandContext, extra: string[] = []): string {
308
+ const parts = ["niceeval show", ...ctx.patterns];
309
+ if (ctx.experiment !== undefined) parts.push(`--experiment ${ctx.experiment}`);
310
+ if (ctx.results !== undefined) parts.push(`--results ${ctx.results}`);
311
+ if (ctx.report !== undefined) parts.push(`--report ${ctx.report}`);
312
+ if (ctx.page !== undefined) parts.push(`--page ${ctx.page}`);
313
+ parts.push(...extra);
314
+ return parts.join(" ");
315
+ }
316
+
317
+ /**
318
+ * 渲染一页的 text 面。INTEGRATION(3):新 runtime 的入口是
319
+ * `renderReportTreeToText(tree, { scope, results, report }, options)`;旧桥接下 content 是
320
+ * 整个旧 definition,调 renderReportToText(definition, { selection, results }, options)。
321
+ */
322
+ export async function renderHostPageText(
323
+ page: HostReportPage,
324
+ ctx: HostRenderContext,
325
+ options: HostTextRenderOptions,
326
+ ): Promise<string> {
327
+ const mod = (await import("../../dist/report/report.js")) as Record<string, unknown>;
328
+ const renderTree = mod.renderReportTreeToText as
329
+ | ((tree: unknown, ctx: HostRenderContext, options: HostTextRenderOptions) => Promise<string>)
330
+ | undefined;
331
+ if (typeof renderTree === "function" && !isLegacyDefinition(page.content)) {
332
+ return renderTree(page.content, ctx, options);
333
+ }
334
+ if (isLegacyDefinition(page.content)) {
335
+ const renderLegacy = mod.renderReportToText as (
336
+ definition: unknown,
337
+ ctx: { selection: unknown; results: Results },
338
+ options: { width?: number; locale?: string },
339
+ ) => Promise<string>;
340
+ return renderLegacy(
341
+ page.content,
342
+ { selection: ctx.scope, results: ctx.results },
343
+ { ...(options.width !== undefined ? { width: options.width } : {}), ...(options.locale !== undefined ? { locale: options.locale } : {}) },
344
+ );
345
+ }
346
+ throw new HostReportError(
347
+ "The installed report runtime cannot render this report page yet (renderReportTreeToText is missing). Rebuild dist/report with `pnpm run build:report`.",
348
+ );
349
+ }
350
+
351
+ /** 渲染一页的 web 面(静态 HTML)。INTEGRATION(3) 同上,web 侧入口在 dist/report/web.js。 */
352
+ export async function renderHostPageHtml(
353
+ page: HostReportPage,
354
+ ctx: HostRenderContext,
355
+ options: { locale: string },
356
+ ): Promise<string> {
357
+ const mod = (await import("../../dist/report/web.js")) as Record<string, unknown>;
358
+ const renderTree = mod.renderReportTreeToStaticHtml as
359
+ | ((tree: unknown, ctx: HostRenderContext, options: { locale: string }) => Promise<string>)
360
+ | undefined;
361
+ if (typeof renderTree === "function" && !isLegacyDefinition(page.content)) {
362
+ return renderTree(page.content, ctx, options);
363
+ }
364
+ if (isLegacyDefinition(page.content)) {
365
+ const renderLegacy = mod.renderReportToStaticHtml as (
366
+ definition: unknown,
367
+ ctx: { selection: unknown; results: Results },
368
+ options: { locale: string },
369
+ ) => Promise<string>;
370
+ return renderLegacy(page.content, { selection: ctx.scope, results: ctx.results }, options);
371
+ }
372
+ throw new HostReportError(
373
+ "The installed report runtime cannot render this report page yet (renderReportTreeToStaticHtml is missing). Rebuild dist/report with `pnpm run build:report`.",
374
+ );
375
+ }