niceeval 0.6.2 → 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 (216) hide show
  1. package/INDEX.md +23 -23
  2. package/README.zh.md +6 -6
  3. package/dist/agents/types.d.ts +2 -2
  4. package/dist/i18n/zh-CN.d.ts +3 -3
  5. package/dist/report/aggregate.d.ts +32 -26
  6. package/dist/report/aggregate.js +157 -76
  7. package/dist/report/built-in/index.d.ts +2 -0
  8. package/dist/report/built-in/index.js +8 -0
  9. package/dist/report/components.d.ts +91 -164
  10. package/dist/report/components.js +377 -114
  11. package/dist/report/compute.d.ts +86 -73
  12. package/dist/report/compute.js +592 -432
  13. package/dist/report/flag.d.ts +28 -17
  14. package/dist/report/flag.js +86 -16
  15. package/dist/report/format.d.ts +11 -11
  16. package/dist/report/format.js +17 -15
  17. package/dist/report/index.d.ts +16 -17
  18. package/dist/report/index.js +20 -22
  19. package/dist/report/load.js +3 -2
  20. package/dist/report/locale.d.ts +49 -34
  21. package/dist/report/locale.js +106 -58
  22. package/dist/report/metrics.d.ts +10 -3
  23. package/dist/report/metrics.js +46 -12
  24. package/dist/report/primitives.d.ts +42 -15
  25. package/dist/report/primitives.js +135 -26
  26. package/dist/report/react/AttemptList.d.ts +10 -8
  27. package/dist/report/react/AttemptList.js +18 -10
  28. package/dist/report/react/DeltaTable.js +19 -18
  29. package/dist/report/react/EvalList.d.ts +3 -3
  30. package/dist/report/react/EvalList.js +0 -0
  31. package/dist/report/react/ExperimentComparison.d.ts +4 -2
  32. package/dist/report/react/ExperimentComparison.js +5 -4
  33. package/dist/report/react/ExperimentList.d.ts +3 -3
  34. package/dist/report/react/ExperimentList.js +16 -15
  35. package/dist/report/react/MetricBars.js +5 -4
  36. package/dist/report/react/MetricLine.js +12 -5
  37. package/dist/report/react/MetricMatrix.js +1 -1
  38. package/dist/report/react/MetricScatter.js +54 -17
  39. package/dist/report/react/MetricTable.js +2 -12
  40. package/dist/report/react/ScopeSummary.d.ts +10 -0
  41. package/dist/report/react/ScopeSummary.js +17 -0
  42. package/dist/report/react/Scoreboard.js +6 -6
  43. package/dist/report/react/cell.js +2 -2
  44. package/dist/report/react/fixtures.d.ts +5 -9
  45. package/dist/report/react/fixtures.js +105 -149
  46. package/dist/report/react/index.d.ts +15 -5
  47. package/dist/report/react/index.js +18 -7
  48. package/dist/report/report.d.ts +137 -20
  49. package/dist/report/report.js +261 -34
  50. package/dist/report/text/faces.d.ts +17 -19
  51. package/dist/report/text/faces.js +225 -157
  52. package/dist/report/text/plot.js +1 -1
  53. package/dist/report/text/table.js +2 -2
  54. package/dist/report/tree.d.ts +90 -40
  55. package/dist/report/tree.js +252 -94
  56. package/dist/report/types.d.ts +245 -300
  57. package/dist/report/types.js +4 -3
  58. package/dist/report/web.d.ts +21 -5
  59. package/dist/report/web.js +42 -16
  60. package/dist/results/select.d.ts +38 -16
  61. package/dist/results/select.js +73 -25
  62. package/dist/results/types.d.ts +38 -14
  63. package/dist/shared/aggregate.d.ts +3 -2
  64. package/dist/shared/aggregate.js +5 -4
  65. package/docs-site/zh/README.md +44 -0
  66. package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
  67. package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
  68. package/docs-site/zh/examples/index.mdx +50 -0
  69. package/docs-site/zh/{concepts → explanation}/adapter.mdx +11 -11
  70. package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
  71. package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
  72. package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
  73. package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
  74. package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
  75. package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
  76. package/docs-site/zh/{concepts → explanation}/overview.mdx +5 -5
  77. package/docs-site/zh/{guides → explanation}/runner.mdx +1 -1
  78. package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
  79. package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +7 -7
  80. package/docs-site/zh/{guides → how-to}/authoring.mdx +2 -2
  81. package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
  82. package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +18 -18
  83. package/docs-site/zh/{guides → how-to}/custom-reports.mdx +6 -6
  84. package/docs-site/zh/{guides → how-to}/experiments.mdx +3 -3
  85. package/docs-site/zh/{guides → how-to}/publish-report.mdx +2 -2
  86. package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +2 -2
  87. package/docs-site/zh/{guides → how-to}/sandbox-providers.mdx +1 -1
  88. package/docs-site/zh/{guides → how-to}/viewing-results.mdx +6 -6
  89. package/docs-site/zh/{guides → how-to}/write-experiment.mdx +3 -3
  90. package/docs-site/zh/{guides → how-to}/write-send.mdx +13 -13
  91. package/docs-site/zh/index.mdx +23 -25
  92. package/docs-site/zh/introduction.mdx +8 -8
  93. package/docs-site/zh/reference/builtin-agents.mdx +5 -5
  94. package/docs-site/zh/reference/capabilities.mdx +6 -6
  95. package/docs-site/zh/reference/cli.mdx +9 -7
  96. package/docs-site/zh/reference/define-agent.mdx +1 -1
  97. package/docs-site/zh/reference/events.mdx +3 -3
  98. package/docs-site/zh/{guides → reference}/official-adapters.mdx +7 -7
  99. package/docs-site/zh/{guides → reference}/report-components.mdx +5 -5
  100. package/docs-site/zh/{guides → reference}/results-data.mdx +5 -5
  101. package/docs-site/zh/{guides → troubleshooting}/debug-sandbox.mdx +2 -2
  102. package/docs-site/zh/{guides → troubleshooting}/debugging.mdx +4 -2
  103. package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
  104. package/package.json +6 -2
  105. package/src/agents/index.ts +2 -2
  106. package/src/agents/openai-compat.ts +1 -1
  107. package/src/agents/streaming.ts +2 -2
  108. package/src/agents/types.ts +3 -3
  109. package/src/cli.ts +42 -23
  110. package/src/context/context.ts +1 -1
  111. package/src/context/session.test.ts +1 -1
  112. package/src/context/session.ts +1 -1
  113. package/src/i18n/en.ts +18 -16
  114. package/src/i18n/zh-CN.ts +16 -15
  115. package/src/report/aggregate.ts +175 -87
  116. package/src/report/built-in/index.tsx +9 -0
  117. package/src/report/components.tsx +625 -285
  118. package/src/report/compute.ts +717 -515
  119. package/src/report/dual-render.test.tsx +738 -1148
  120. package/src/report/flag.ts +97 -33
  121. package/src/report/format.ts +18 -22
  122. package/src/report/index.ts +113 -58
  123. package/src/report/load.ts +3 -2
  124. package/src/report/locale.ts +120 -69
  125. package/src/report/metrics.ts +42 -12
  126. package/src/report/primitives.tsx +190 -45
  127. package/src/report/react/AttemptList.tsx +32 -20
  128. package/src/report/react/DeltaTable.tsx +63 -45
  129. package/src/report/react/EvalList.tsx +0 -0
  130. package/src/report/react/ExperimentComparison.tsx +12 -7
  131. package/src/report/react/ExperimentList.tsx +38 -26
  132. package/src/report/react/MetricBars.tsx +5 -4
  133. package/src/report/react/MetricLine.tsx +13 -8
  134. package/src/report/react/MetricMatrix.tsx +2 -2
  135. package/src/report/react/MetricScatter.tsx +74 -20
  136. package/src/report/react/MetricTable.tsx +4 -76
  137. package/src/report/react/ScopeSummary.tsx +86 -0
  138. package/src/report/react/Scoreboard.tsx +28 -10
  139. package/src/report/react/cell.tsx +2 -2
  140. package/src/report/react/enhance.js +57 -5
  141. package/src/report/react/fixtures.ts +109 -156
  142. package/src/report/react/index.tsx +24 -39
  143. package/src/report/react/render.test.tsx +139 -104
  144. package/src/report/react/styles.css +181 -91
  145. package/src/report/report.test.ts +761 -1031
  146. package/src/report/report.ts +425 -47
  147. package/src/report/text/faces.ts +257 -164
  148. package/src/report/text/plot.ts +1 -1
  149. package/src/report/text/table.ts +2 -2
  150. package/src/report/tree.ts +362 -104
  151. package/src/report/types.ts +257 -287
  152. package/src/report/web.ts +63 -20
  153. package/src/results/attempt-evidence.test.ts +4 -4
  154. package/src/results/attempt-evidence.ts +5 -5
  155. package/src/results/copy.ts +6 -6
  156. package/src/results/host-equivalence.test.ts +26 -14
  157. package/src/results/index.ts +10 -4
  158. package/src/results/open.ts +8 -4
  159. package/src/results/results.test.ts +4 -3
  160. package/src/results/select.ts +104 -34
  161. package/src/results/types.ts +36 -14
  162. package/src/runner/feedback/human.test.ts +1 -1
  163. package/src/runner/run.ts +1 -1
  164. package/src/sandbox/cli-commands.ts +2 -2
  165. package/src/scoring/judge.test.ts +1 -1
  166. package/src/shared/aggregate.ts +5 -4
  167. package/src/show/compose.ts +50 -67
  168. package/src/show/index.ts +107 -56
  169. package/src/show/render.ts +43 -27
  170. package/src/show/report-host.test.ts +188 -0
  171. package/src/show/report-host.ts +375 -0
  172. package/src/show/show.test.ts +86 -36
  173. package/src/view/app/App.test.tsx +69 -0
  174. package/src/view/app/App.tsx +144 -48
  175. package/src/view/app/components/AttemptModal.tsx +324 -63
  176. package/src/view/app/components/CodeView.tsx +10 -4
  177. package/src/view/app/i18n.ts +31 -17
  178. package/src/view/app/main.tsx +13 -8
  179. package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
  180. package/src/view/app/types.ts +4 -1
  181. package/src/view/client-dist/app.css +1 -1
  182. package/src/view/client-dist/app.js +14 -14
  183. package/src/view/data.test.ts +9 -3
  184. package/src/view/data.ts +145 -49
  185. package/src/view/index.ts +48 -44
  186. package/src/view/server.ts +35 -15
  187. package/src/view/shared/types.ts +34 -5
  188. package/src/view/styles.css +224 -0
  189. package/src/view/view-report.test.ts +161 -57
  190. package/dist/report/built-ins/experiment-comparison.d.ts +0 -39
  191. package/dist/report/built-ins/experiment-comparison.js +0 -119
  192. package/dist/report/built-ins/index.d.ts +0 -2
  193. package/dist/report/built-ins/index.js +0 -2
  194. package/dist/report/react/GroupSummary.d.ts +0 -8
  195. package/dist/report/react/GroupSummary.js +0 -8
  196. package/dist/report/react/RunOverview.d.ts +0 -8
  197. package/dist/report/react/RunOverview.js +0 -12
  198. package/docs-site/zh/example/ai-agent-application.mdx +0 -152
  199. package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
  200. package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
  201. package/docs-site/zh/example/showcase.mdx +0 -39
  202. package/src/report/built-in-user-parity.test.tsx +0 -597
  203. package/src/report/built-ins/experiment-comparison.tsx +0 -179
  204. package/src/report/built-ins/index.ts +0 -7
  205. package/src/report/react/GroupSummary.tsx +0 -66
  206. package/src/report/react/RunOverview.tsx +0 -109
  207. /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
  208. /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
  209. /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
  210. /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
  211. /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
  212. /package/docs-site/zh/{guides → how-to}/ci-integration.mdx +0 -0
  213. /package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +0 -0
  214. /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
  215. /package/docs-site/zh/{guides → how-to}/reporters.mdx +0 -0
  216. /package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +0 -0
@@ -1,4 +1,5 @@
1
- // niceeval/report 的公开类型:指标(Metric)、维度(Dimension / flag())与计算函数
2
- // 产物(即组件的 data props)。数据契约照 docs/feature/reports/library.md「数据计算与缓存边界」;
3
- // 这些不是持久化格式,没有 format / schemaVersion 信封,兼容性跟随 npm 版本。
1
+ // niceeval/report 的公开类型:指标(Metric)、维度(Dimension / flag() / runConfig())与
2
+ // 计算函数产物(即组件的 data)。数据契约照 docs/feature/reports/library/ 各分篇;
3
+ // 这些不是持久化格式,没有 format / schemaVersion 信封,兼容性跟随 npm 版本
4
+ // (组件消费 data 时校验结构,不符按完整用户反馈报错并提示版本漂移)。
4
5
  export {};
@@ -1,15 +1,31 @@
1
1
  import type { AttemptLocator } from "../results/locator.ts";
2
+ import type { Scope } from "../results/types.ts";
2
3
  import { type ReportLocale } from "./locale.ts";
3
- import type { ReportContext, ReportDefinition } from "./report.ts";
4
+ import { type ReportDefinition, type ReportHostContext } from "./report.ts";
4
5
  export interface StaticHtmlOptions {
6
+ /** 渲染哪一页;缺省第一页。未命中抛 ReportPageNotFoundError。 */
7
+ pageId?: string;
5
8
  /** 证据室深链;缺省用 view 的 attempt 路由 `#/attempt/@<locator>`(单段、不透明)。 */
6
9
  attemptHref?: (locator: AttemptLocator) => string;
7
10
  /** 官方组件 chrome 文案的 locale;默认 "en"。 */
8
11
  locale?: ReportLocale;
9
12
  }
10
13
  /**
11
- * build渲染前解析数据组件(唯一的 await 边界)→ 树校验(与 text 宿主同一遍)→ 静态渲染
12
- * web 面;Selection 有挑选警告时在报告顶部前置一块警告 HTML;报告树里的 RunOverview
13
- * 已经渲染同一条时不重复。
14
+ * web 宿主的装载语义:选页 resolve(组合展开 + spec 取数,唯一的 await 边界)→
15
+ * 树校验(与 text 宿主同一遍)→ 静态渲染 web 面;Scope 有挑选警告时在报告顶部前置
16
+ * 一块警告 HTML(宿主是 warning 的唯一呈现者,组件数据不复制 warning)。
14
17
  */
15
- export declare function renderReportToStaticHtml(definition: ReportDefinition, ctx: ReportContext, options?: StaticHtmlOptions): Promise<string>;
18
+ export declare function renderReportToStaticHtml(definition: ReportDefinition, ctx: ReportHostContext, options?: StaticHtmlOptions): Promise<string>;
19
+ /**
20
+ * 渲染一页报告树的 web 面(宿主逐页调用;页选择归宿主):resolve → validate → 静态渲染。
21
+ * Scope 有挑选警告时在页顶前置警告块(带 command 的警告渲染为可复制命令)——宿主是
22
+ * warning 的唯一呈现者,组件数据不复制 warning。ctx.report 是宿主规范化后的声明。
23
+ */
24
+ export declare function renderReportTreeToStaticHtml(tree: import("./tree.ts").ReportNode, ctx: {
25
+ scope: Scope;
26
+ results: import("../results/types.ts").Results;
27
+ report: import("./report.ts").ReportMeta;
28
+ }, options?: {
29
+ attemptHref?: (locator: AttemptLocator) => string;
30
+ locale?: ReportLocale;
31
+ }): Promise<string>;
@@ -3,35 +3,61 @@
3
3
  // 所以本文件不从 niceeval/report 的入口 re-export —— 宿主与测试按源路径 import。
4
4
  import * as React from "react";
5
5
  import { renderToStaticMarkup } from "react-dom/server";
6
- import { resolveReportTree, runWithWebContext, validateReportTree } from "./tree.js";
6
+ import { resolveReportTree, runWithWebContext, validateReportTree, ResolveMemo, } from "./tree.js";
7
7
  import { DEFAULT_REPORT_LOCALE } from "./locale.js";
8
+ import { buildReportMeta, pickReportPage } from "./report.js";
8
9
  /**
9
- * 挑选警告的 HTML 形态:宿主级前置块,与 RunOverview 里的警告用同一套结构和类名
10
- * (`.nre nre-report-warnings` 外壳内一个 `ul.nre-warnings` + `li.nre-warning[data-kind]`,
11
- * 复用 styles.css 已有的 `.nre .nre-warnings` 样式)。经 renderToStaticMarkup 走 React,
12
- * message 文本自动转义,不裸拼 HTML。裸跑 / --report 都在报告顶上如实报残缺,不静默。
10
+ * 挑选警告的 HTML 形态:宿主级前置块(`.nre nre-report-warnings` 外壳内一个
11
+ * `ul.nre-warnings` + `li.nre-warning[data-kind]`,复用 styles.css 已有样式)。
12
+ * `command` 的警告把命令渲染为可复制块(`.nre-warning-command`);无 command
13
+ * 只显示 message,不硬造动作。经 renderToStaticMarkup React,文本自动转义。
13
14
  */
14
- function renderSelectionWarningsHtml(warnings) {
15
- return renderToStaticMarkup(React.createElement("div", { className: "nre nre-report-warnings" }, React.createElement("ul", { className: "nre-warnings" }, warnings.map((w, i) => React.createElement("li", { key: i, className: "nre-warning", "data-kind": w.kind }, w.message)))));
15
+ function renderScopeWarningsHtml(scope) {
16
+ return renderToStaticMarkup(React.createElement("div", { className: "nre nre-report-warnings" }, React.createElement("ul", { className: "nre-warnings" }, scope.warnings.map((w, i) => React.createElement("li", { key: i, className: "nre-warning", "data-kind": w.kind }, w.message, "command" in w && w.command
17
+ ? React.createElement("code", { className: "nre-warning-command", "data-nre-copy": w.command }, w.command)
18
+ : null)))));
16
19
  }
17
20
  /**
18
- * build渲染前解析数据组件(唯一的 await 边界)→ 树校验(与 text 宿主同一遍)→ 静态渲染
19
- * web 面;Selection 有挑选警告时在报告顶部前置一块警告 HTML;报告树里的 RunOverview
20
- * 已经渲染同一条时不重复。
21
+ * web 宿主的装载语义:选页 resolve(组合展开 + spec 取数,唯一的 await 边界)→
22
+ * 树校验(与 text 宿主同一遍)→ 静态渲染 web 面;Scope 有挑选警告时在报告顶部前置
23
+ * 一块警告 HTML(宿主是 warning 的唯一呈现者,组件数据不复制 warning)。
21
24
  */
22
25
  export async function renderReportToStaticHtml(definition, ctx, options) {
23
- const node = await definition.build(ctx);
24
- const resolved = await resolveReportTree(node);
26
+ const page = pickReportPage(definition, options?.pageId);
27
+ const meta = buildReportMeta(definition, ctx.scope, page.id);
28
+ const resolved = await resolveReportTree(page.content, {
29
+ scope: ctx.scope,
30
+ results: ctx.results,
31
+ report: meta,
32
+ memo: new ResolveMemo(),
33
+ });
25
34
  validateReportTree(resolved);
26
35
  const webCtx = {
27
36
  attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
28
37
  locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
29
38
  };
30
39
  const body = runWithWebContext(webCtx, () => renderToStaticMarkup(resolved));
31
- const missingWarnings = ctx.selection.warnings.filter((warning) => {
32
- const escapedMessage = renderToStaticMarkup(React.createElement(React.Fragment, null, warning.message));
33
- return !body.includes(escapedMessage);
40
+ const warnings = ctx.scope.warnings.length > 0 ? renderScopeWarningsHtml(ctx.scope) : "";
41
+ return warnings + body;
42
+ }
43
+ /**
44
+ * 渲染一页报告树的 web 面(宿主逐页调用;页选择归宿主):resolve → validate → 静态渲染。
45
+ * Scope 有挑选警告时在页顶前置警告块(带 command 的警告渲染为可复制命令)——宿主是
46
+ * warning 的唯一呈现者,组件数据不复制 warning。ctx.report 是宿主规范化后的声明。
47
+ */
48
+ export async function renderReportTreeToStaticHtml(tree, ctx, options) {
49
+ const resolved = await resolveReportTree(tree, {
50
+ scope: ctx.scope,
51
+ results: ctx.results,
52
+ report: ctx.report,
53
+ memo: new ResolveMemo(),
34
54
  });
35
- const warnings = missingWarnings.length > 0 ? renderSelectionWarningsHtml(missingWarnings) : "";
55
+ validateReportTree(resolved);
56
+ const webCtx = {
57
+ attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
58
+ locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
59
+ };
60
+ const body = runWithWebContext(webCtx, () => renderToStaticMarkup(resolved));
61
+ const warnings = ctx.scope.warnings.length > 0 ? renderScopeWarningsHtml(ctx.scope) : "";
36
62
  return warnings + body;
37
63
  }
@@ -1,37 +1,59 @@
1
- import type { AttemptHandle, DedupeWarning, Experiment, Results, Selection, SelectionWarning, Snapshot } from "./types.ts";
1
+ import type { AttemptHandle, DedupeWarning, Experiment, Results, Scope, ScopeWarning, Snapshot } from "./types.ts";
2
+ import type { ExperimentRunInfo, JsonValue } from "../types.ts";
2
3
  /** Results.latest() 的实现:每个实验取最新一次快照(= exp.snapshots[0]),生成挑选警告。 */
3
4
  export declare function selectLatest(experiments: Experiment[], opts?: {
4
5
  experiments?: string | string[];
5
- }): Selection;
6
+ }): Scope;
6
7
  /** selectCurrentResults 的范围输入:experiment id 前缀与 eval id 前缀,都可缺省。 */
7
8
  export interface ResultScope {
8
9
  /** experiment id 前缀(--experiment),分段匹配语义同 filterExperiments。 */
9
- experiment?: string;
10
- /** eval id 前缀(位置参数),收窄 Selection 覆盖的 eval;覆盖警告分母同步收窄到范围内。 */
10
+ experiment?: string | string[];
11
+ /** eval id 前缀(位置参数),收窄 Scope 覆盖的 eval;覆盖警告分母同步收窄到范围内。 */
11
12
  patterns?: string[];
12
13
  }
13
14
  /**
14
- * 两个宿主(show / view)共用的现刻水位选择器:每个 experiment × eval 取时间上最新的那份
15
- * 判定,跨 run 合成。results.latest() 只挑「每实验最新快照」,带 eval 前缀的局部重跑会产出
16
- * 残缺快照;现刻水位承诺「不会因为一次局部重跑变残缺」,所以在实验的全部历史快照上逐 eval
17
- * 向更早的 run 补齐,再把合成好的 Selection 交给宿主注入报告槽——内置默认报告与 --report 吃
18
- * 同一份。
15
+ * current() 跨快照拼接的可比性前提所比较的字段集(docs/feature/results/library.md
16
+ * 「官方现刻水位」):会改变单题被测行为或判定的字段。runs / earlyExit / maxConcurrency /
17
+ * selectedEvalIds / evalFilterFingerprint / description 是编排与选题字段,不参与比较。
18
+ */
19
+ export interface ComparabilityConfig {
20
+ agent: string;
21
+ model?: string;
22
+ reasoningEffort?: string;
23
+ flags?: Record<string, JsonValue>;
24
+ budget?: number;
25
+ timeoutMs?: number;
26
+ sandbox?: ExperimentRunInfo["sandbox"];
27
+ }
28
+ /** 一个快照的可比性配置投影;pairsByFlag 与 experimentListData 复用同一字段集。 */
29
+ export declare function comparabilityConfigOf(snapshot: Snapshot): ComparabilityConfig;
30
+ /** 可序列化值的深相等(对象键序无关;undefined 字段与缺席字段等价)。 */
31
+ export declare function deepEqualJson(a: unknown, b: unknown): boolean;
32
+ /**
33
+ * 两个宿主(show / view)共用的现刻水位选择器:每个 experiment × eval 取「包含该 eval 的
34
+ * 最新快照」里的全部 attempt,跨 run 合成。results.latest() 只挑「每实验最新快照」,带 eval
35
+ * 前缀的局部重跑会产出残缺快照;现刻水位承诺「不会因为一次局部重跑变残缺」,所以在实验的
36
+ * 历史快照上逐 eval 向更早的 run 补齐,再把合成好的 Scope 交给宿主注入报告槽。
37
+ *
38
+ * **可比性前提**:每个 experiment 以最新快照的可比性配置(agent / model / reasoningEffort /
39
+ * flags / budget / timeoutMs / sandbox)为基准,只有配置与基准深相等的历史快照才参与补齐;
40
+ * 改过配置后只补跑部分 eval 时,旧配置快照覆盖的其余题不冒充新配置的水位,按 partial-coverage
41
+ * 如实告警。这保证 current() 产出的每个 experiment 只对应一套配置。
19
42
  *
20
43
  * 同一 eval 的全部 attempts 必须整批取自包含它的最新快照,不把历史快照的 attempts 平铺后
21
44
  * 按 eval 聚合——否则会把不同运行的重试混成一次虚构运行。合成快照的 dir/元数据只服务报告
22
45
  * 分组与来源展示,证据身份一律来自 attempt 自己的 ref。
23
- * 警告随 Selection 重算:partial-coverage 的分母 = 已知并集 ∩ 范围(范围收窄时分母同步收窄,
24
- * 不让范围外的缺口刷屏);stale / unfinished 与 results.latest() 同口径。
25
46
  */
26
- export declare function selectCurrentResults(results: Results, scope?: ResultScope): Selection;
47
+ export declare function selectCurrentResults(results: Results, scope?: ResultScope): Scope;
27
48
  /**
28
- * Selection 构造:filter 只删不换 —— 快照删减,warnings 修剪规则是
29
- * 「experimentId 不在幸存快照中的丢弃,非实验作用域的保留」(为将来非 per-experiment 的 kind 留位置)。
49
+ * Scope 构造:attempts 按口径物化(快照 attempts 的平铺);filter 只删不换 —— 快照删减,
50
+ * attempts 随之同步修剪,warnings 修剪规则是「experimentId 不在幸存快照中的丢弃,
51
+ * 非实验作用域的保留」(为将来非 per-experiment 的 kind 留位置)。
30
52
  */
31
- export declare function makeSelection(snapshots: Snapshot[], warnings: SelectionWarning[]): Selection;
53
+ export declare function makeScope(mode: Scope["mode"], snapshots: Snapshot[], warnings: ScopeWarning[]): Scope;
32
54
  /**
33
55
  * 跨快照聚合前的身份键去重:(experimentId, evalId, attempt, startedAt)。
34
- * --resume 会把上一轮已通过的结果原样合入新快照,同一 attempt 因此存在于多份落盘;
56
+ * 携带合入会把上一轮已通过的结果原样合入新快照,同一 attempt 因此存在于多份落盘;
35
57
  * 重复时保留最新快照里的那份(内容相同,取新快照的副本让 ref 落在最新落盘上;
36
58
  * 位置取首次出现处,顺序稳定)。startedAt 缺失时宁可不去重也不误删,记入 warnings。
37
59
  */
@@ -1,15 +1,15 @@
1
- // 快照 Selection 与 attempt 去重(定稿见 docs/feature/results/library.md「选择快照」「身份键与去重」)。
1
+ // 快照 Scope 与 attempt 去重(定稿见 docs/feature/results/library.md「选择快照」「官方现刻水位」「身份键与去重」)。
2
2
  //
3
- // 选择器只有一个(latest),长在集合上;它不是 DSL,只是最常用的那次筛选。
4
- // 选择器必须诚实:残缺、落后、未收尾都被算出来,以结构化 warnings 随 Selection 走 ——
5
- // 渲染与否在消费方(message 是渲染好的英文句子),但缺口不静默。
3
+ // 选择器长在集合上(results.latest() / results.current()),不是 DSL,只是最常用的两种口径。
4
+ // 选择器必须诚实:残缺、落后、未收尾都被算出来,以结构化 warnings 随 Scope 走 ——
5
+ // 渲染与否在消费方(message 是渲染好的英文句子,以下一步收尾),但缺口不静默。
6
6
  import { evalPrefixPredicate } from "../shared/aggregate.js";
7
7
  /** Results.latest() 的实现:每个实验取最新一次快照(= exp.snapshots[0]),生成挑选警告。 */
8
8
  export function selectLatest(experiments, opts) {
9
9
  const selected = filterExperiments(experiments, opts?.experiments);
10
10
  const snapshots = selected.map((exp) => exp.latest);
11
11
  const warnings = [];
12
- // stale 的基准:Selection 中最新的落盘(无阈值,如实触发;要阈值消费方按字段自比)。
12
+ // stale 的基准:Scope 中最新的落盘(无阈值,如实触发;要阈值消费方按字段自比)。
13
13
  let latestStartedAt = "";
14
14
  for (const snapshot of snapshots) {
15
15
  if (snapshot.startedAt > latestStartedAt)
@@ -28,6 +28,7 @@ export function selectLatest(experiments, opts) {
28
28
  covered,
29
29
  total,
30
30
  message: `snapshot covers ${covered} of ${total} evals seen in history; re-run \`niceeval exp ${exp.id}\` for a full snapshot`,
31
+ command: `niceeval exp ${exp.id}`,
31
32
  });
32
33
  }
33
34
  if (snapshot.startedAt < latestStartedAt) {
@@ -36,7 +37,8 @@ export function selectLatest(experiments, opts) {
36
37
  experimentId: exp.id,
37
38
  startedAt: snapshot.startedAt,
38
39
  latestStartedAt,
39
- message: `snapshot "${exp.id}" (${snapshot.startedAt}) predates the latest run in this selection by ${humanizeGap(snapshot.startedAt, latestStartedAt)}`,
40
+ message: `snapshot "${exp.id}" (${snapshot.startedAt}) predates the latest run in this scope by ${humanizeGap(snapshot.startedAt, latestStartedAt)}; re-run \`niceeval exp ${exp.id}\` to align, or ignore if evals, agent and model are unchanged between the runs`,
41
+ command: `niceeval exp ${exp.id}`,
40
42
  });
41
43
  }
42
44
  if (!snapshot.completedAt) {
@@ -45,24 +47,58 @@ export function selectLatest(experiments, opts) {
45
47
  experimentId: exp.id,
46
48
  startedAt: snapshot.startedAt,
47
49
  dir: snapshot.dir,
48
- message: `snapshot "${exp.id}" (${snapshot.startedAt}) has no completedAt — the run was interrupted; results may be incomplete`,
50
+ message: `snapshot "${exp.id}" (${snapshot.startedAt}) has no completedAt — the run was interrupted; re-run \`niceeval exp ${exp.id}\` for a complete snapshot`,
51
+ command: `niceeval exp ${exp.id}`,
49
52
  });
50
53
  }
51
54
  }
52
- return makeSelection(snapshots, warnings);
55
+ return makeScope("latest-snapshots", snapshots, warnings);
56
+ }
57
+ /** 一个快照的可比性配置投影;pairsByFlag 与 experimentListData 复用同一字段集。 */
58
+ export function comparabilityConfigOf(snapshot) {
59
+ const info = snapshot.experiment;
60
+ return {
61
+ agent: snapshot.agent,
62
+ ...(snapshot.model !== undefined ? { model: snapshot.model } : {}),
63
+ ...(info?.reasoningEffort !== undefined ? { reasoningEffort: info.reasoningEffort } : {}),
64
+ ...(info?.flags !== undefined ? { flags: info.flags } : {}),
65
+ ...(info?.budget !== undefined ? { budget: info.budget } : {}),
66
+ ...(info?.timeoutMs !== undefined ? { timeoutMs: info.timeoutMs } : {}),
67
+ ...(info?.sandbox !== undefined ? { sandbox: info.sandbox } : {}),
68
+ };
69
+ }
70
+ /** 可序列化值的深相等(对象键序无关;undefined 字段与缺席字段等价)。 */
71
+ export function deepEqualJson(a, b) {
72
+ if (a === b)
73
+ return true;
74
+ if (Array.isArray(a) || Array.isArray(b)) {
75
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
76
+ return false;
77
+ return a.every((item, i) => deepEqualJson(item, b[i]));
78
+ }
79
+ if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
80
+ const keysA = Object.keys(a).filter((k) => a[k] !== undefined);
81
+ const keysB = Object.keys(b).filter((k) => b[k] !== undefined);
82
+ if (keysA.length !== keysB.length)
83
+ return false;
84
+ return keysA.every((k) => deepEqualJson(a[k], b[k]));
85
+ }
86
+ return false;
53
87
  }
54
88
  /**
55
- * 两个宿主(show / view)共用的现刻水位选择器:每个 experiment × eval 取时间上最新的那份
56
- * 判定,跨 run 合成。results.latest() 只挑「每实验最新快照」,带 eval 前缀的局部重跑会产出
57
- * 残缺快照;现刻水位承诺「不会因为一次局部重跑变残缺」,所以在实验的全部历史快照上逐 eval
58
- * 向更早的 run 补齐,再把合成好的 Selection 交给宿主注入报告槽——内置默认报告与 --report 吃
59
- * 同一份。
89
+ * 两个宿主(show / view)共用的现刻水位选择器:每个 experiment × eval 取「包含该 eval 的
90
+ * 最新快照」里的全部 attempt,跨 run 合成。results.latest() 只挑「每实验最新快照」,带 eval
91
+ * 前缀的局部重跑会产出残缺快照;现刻水位承诺「不会因为一次局部重跑变残缺」,所以在实验的
92
+ * 历史快照上逐 eval 向更早的 run 补齐,再把合成好的 Scope 交给宿主注入报告槽。
93
+ *
94
+ * **可比性前提**:每个 experiment 以最新快照的可比性配置(agent / model / reasoningEffort /
95
+ * flags / budget / timeoutMs / sandbox)为基准,只有配置与基准深相等的历史快照才参与补齐;
96
+ * 改过配置后只补跑部分 eval 时,旧配置快照覆盖的其余题不冒充新配置的水位,按 partial-coverage
97
+ * 如实告警。这保证 current() 产出的每个 experiment 只对应一套配置。
60
98
  *
61
99
  * 同一 eval 的全部 attempts 必须整批取自包含它的最新快照,不把历史快照的 attempts 平铺后
62
100
  * 按 eval 聚合——否则会把不同运行的重试混成一次虚构运行。合成快照的 dir/元数据只服务报告
63
101
  * 分组与来源展示,证据身份一律来自 attempt 自己的 ref。
64
- * 警告随 Selection 重算:partial-coverage 的分母 = 已知并集 ∩ 范围(范围收窄时分母同步收窄,
65
- * 不让范围外的缺口刷屏);stale / unfinished 与 results.latest() 同口径。
66
102
  */
67
103
  export function selectCurrentResults(results, scope = {}) {
68
104
  const match = scope.patterns && scope.patterns.length > 0 ? evalPrefixPredicate(scope.patterns) : () => true;
@@ -70,9 +106,13 @@ export function selectCurrentResults(results, scope = {}) {
70
106
  const snapshots = [];
71
107
  const warnings = [];
72
108
  for (const exp of experiments) {
109
+ // 可比性基准 = 该实验最新快照的可比性配置;不一致的旧快照整份跳过,不贡献 attempt。
110
+ const baseline = comparabilityConfigOf(exp.latest);
73
111
  // 逐题取最新:快照按最新在前,首个出现即最新判定
74
112
  const taken = new Map();
75
113
  for (const snapshot of exp.snapshots) {
114
+ if (!deepEqualJson(comparabilityConfigOf(snapshot), baseline))
115
+ continue;
76
116
  for (const ev of snapshot.evals) {
77
117
  if (!match(ev.id) || taken.has(ev.id))
78
118
  continue;
@@ -97,6 +137,8 @@ export function selectCurrentResults(results, scope = {}) {
97
137
  startedAt,
98
138
  agent: base.agent,
99
139
  ...(base.model !== undefined ? { model: base.model } : {}),
140
+ ...(base.experiment !== undefined ? { experiment: base.experiment } : {}),
141
+ ...(base.name !== undefined ? { name: base.name } : {}),
100
142
  producer: base.producer,
101
143
  schemaVersion: base.schemaVersion,
102
144
  evals,
@@ -105,8 +147,8 @@ export function selectCurrentResults(results, scope = {}) {
105
147
  ...(newest.completedAt !== undefined ? { completedAt: newest.completedAt } : {}),
106
148
  ...(base.knownEvalIds ? { knownEvalIds: [...base.knownEvalIds] } : {}),
107
149
  });
108
- // 残缺检测:跨快照补齐后仍缺,只可能是历史上见过(或 knownEvalIds 声明过)
109
- // 却从未在可读落盘里出现的题 —— 分母收窄到范围内,不让范围外的缺口刷屏。
150
+ // 残缺检测:跨快照补齐后仍缺,来自「历史上见过却从未在可比配置的可读落盘里出现」的题
151
+ // (含改配置后未补跑的题)—— 分母收窄到范围内,不让范围外的缺口刷屏。
110
152
  const total = exp.evalIds.filter(match).length;
111
153
  if (evals.length < total) {
112
154
  warnings.push({
@@ -115,6 +157,7 @@ export function selectCurrentResults(results, scope = {}) {
115
157
  covered: evals.length,
116
158
  total,
117
159
  message: `verdicts cover ${evals.length} of ${total} evals seen in history; re-run \`niceeval exp ${exp.id}\` for a full snapshot`,
160
+ command: `niceeval exp ${exp.id}`,
118
161
  });
119
162
  }
120
163
  }
@@ -130,7 +173,8 @@ export function selectCurrentResults(results, scope = {}) {
130
173
  experimentId: snapshot.experimentId,
131
174
  startedAt: snapshot.startedAt,
132
175
  latestStartedAt,
133
- message: `verdicts for "${snapshot.experimentId}" were produced at ${snapshot.startedAt}, before the latest run in this selection (${latestStartedAt})`,
176
+ message: `verdicts for "${snapshot.experimentId}" were produced at ${snapshot.startedAt}, ${humanizeGap(snapshot.startedAt, latestStartedAt)} before the latest run in this scope; re-run \`niceeval exp ${snapshot.experimentId}\` to align, or ignore if evals, agent and model are unchanged between the runs`,
177
+ command: `niceeval exp ${snapshot.experimentId}`,
134
178
  });
135
179
  }
136
180
  if (snapshot.completedAt === undefined) {
@@ -139,19 +183,23 @@ export function selectCurrentResults(results, scope = {}) {
139
183
  experimentId: snapshot.experimentId,
140
184
  startedAt: snapshot.startedAt,
141
185
  dir: snapshot.dir,
142
- message: `snapshot "${snapshot.experimentId}" (${snapshot.startedAt}) is unfinished (the process was interrupted); completed attempts are read as-is, but the set may be incomplete`,
186
+ message: `snapshot "${snapshot.experimentId}" (${snapshot.startedAt}) is unfinished (the process was interrupted); completed attempts are read as-is, but the set may be incomplete — re-run \`niceeval exp ${snapshot.experimentId}\` for a complete snapshot`,
187
+ command: `niceeval exp ${snapshot.experimentId}`,
143
188
  });
144
189
  }
145
190
  }
146
- return makeSelection(snapshots, warnings);
191
+ return makeScope("current-evals", snapshots, warnings);
147
192
  }
148
193
  /**
149
- * Selection 构造:filter 只删不换 —— 快照删减,warnings 修剪规则是
150
- * 「experimentId 不在幸存快照中的丢弃,非实验作用域的保留」(为将来非 per-experiment 的 kind 留位置)。
194
+ * Scope 构造:attempts 按口径物化(快照 attempts 的平铺);filter 只删不换 —— 快照删减,
195
+ * attempts 随之同步修剪,warnings 修剪规则是「experimentId 不在幸存快照中的丢弃,
196
+ * 非实验作用域的保留」(为将来非 per-experiment 的 kind 留位置)。
151
197
  */
152
- export function makeSelection(snapshots, warnings) {
198
+ export function makeScope(mode, snapshots, warnings) {
153
199
  return {
200
+ mode,
154
201
  snapshots,
202
+ attempts: snapshots.flatMap((s) => s.attempts),
155
203
  warnings,
156
204
  filter(predicate) {
157
205
  const kept = snapshots.filter(predicate);
@@ -160,13 +208,13 @@ export function makeSelection(snapshots, warnings) {
160
208
  const scope = w.experimentId;
161
209
  return typeof scope !== "string" || survivors.has(scope);
162
210
  });
163
- return makeSelection(kept, keptWarnings);
211
+ return makeScope(mode, kept, keptWarnings);
164
212
  },
165
213
  };
166
214
  }
167
215
  /**
168
216
  * 跨快照聚合前的身份键去重:(experimentId, evalId, attempt, startedAt)。
169
- * --resume 会把上一轮已通过的结果原样合入新快照,同一 attempt 因此存在于多份落盘;
217
+ * 携带合入会把上一轮已通过的结果原样合入新快照,同一 attempt 因此存在于多份落盘;
170
218
  * 重复时保留最新快照里的那份(内容相同,取新快照的副本让 ref 落在最新落盘上;
171
219
  * 位置取首次出现处,顺序稳定)。startedAt 缺失时宁可不去重也不误删,记入 warnings。
172
220
  */
@@ -157,46 +157,68 @@ export interface Results {
157
157
  experiments: Experiment[];
158
158
  skipped: SkippedDir[];
159
159
  /**
160
- * 每个实验取最新一次快照,返回 Selection(快照与挑选警告绑在一起走)。
160
+ * 每个实验取最新一次快照,返回 Scope(快照与挑选警告绑在一起走)。
161
161
  * `experiments` 是 experiment id 前缀过滤(string | string[]),同 CLI 位置参数语义。
162
162
  */
163
163
  latest(opts?: {
164
164
  experiments?: string | string[];
165
- }): Selection;
165
+ }): Scope;
166
+ /**
167
+ * 官方现刻水位:每个 experiment × eval 取「包含该 eval 的最新快照」里的全部 attempt,
168
+ * 跨历史拼出当前判定水位。可比性前提:每个 experiment 以最新快照的可比性配置
169
+ * (agent / model / reasoningEffort / flags / budget / timeoutMs / sandbox)为基准,
170
+ * 配置不一致的旧快照不贡献 attempt,缺口走 partial-coverage
171
+ * (见 docs/feature/results/library.md「官方现刻水位」)。
172
+ */
173
+ current(opts?: {
174
+ experiments?: string | string[];
175
+ }): Scope;
166
176
  }
167
177
  /**
168
- * Selection:选出的快照 + 挑选过程算出的警告。渲染与否在消费方,但缺口永远被算出来。
169
- * 下游(Reports 计算函数、copySnapshots)收 `Selection | Snapshot[]`;
170
- * 手工挑的裸数组没有挑选过程,自然没有 warnings 可带,也如实。
178
+ * Scope(范围):选出的快照 + 口径 + 已按口径物化的 attempt 全集 + 挑选警告。
179
+ * 渲染与否在消费方,但缺口永远被算出来。下游(Reports 计算函数、copySnapshots)收
180
+ * `Scope | readonly Snapshot[]`;手工挑的裸数组没有挑选过程,自然没有 warnings 可带,也如实。
171
181
  */
172
- export interface Selection {
182
+ export interface Scope {
183
+ /** 这份 Scope 的口径,字面写在数据上。 */
184
+ mode: "latest-snapshots" | "current-evals";
173
185
  snapshots: Snapshot[];
174
- warnings: SelectionWarning[];
175
186
  /**
176
- * 只删不换:返回新 Selection,快照删减,warnings 按规则修剪 ——
177
- * experimentId 不在幸存快照中的丢弃,非实验作用域的保留。
187
+ * 按口径物化的 attempt 全集:消费 attempts 就自动正确,不需要自己 flatten snapshots,
188
+ * 也就不可能算错口径。官方计算函数同样只消费它。
189
+ */
190
+ attempts: AttemptHandle[];
191
+ warnings: ScopeWarning[];
192
+ /**
193
+ * 只删不换:返回新 Scope,快照删减,attempts 与 warnings 随之同步修剪 ——
194
+ * experimentId 不在幸存快照中的警告丢弃,非实验作用域的警告保留。
178
195
  * 「换成上一个完整快照」这类替换式重挑不给方法,回 exp.snapshots 自己挑。
179
196
  */
180
- filter(predicate: (snapshot: Snapshot) => boolean): Selection;
197
+ filter(predicate: (snapshot: Snapshot) => boolean): Scope;
181
198
  }
182
199
  /**
183
- * 挑选警告:每种带 kind、可判断的结构化字段和渲染好的英文 message;
184
- * kind 是契约的一部分,全集与触发条件见 docs/feature/results/library.md「警告 kind 全集」。
200
+ * 挑选警告:每种带 kind、可判断的结构化字段和渲染好的英文 message;能用一条命令直接推进的
201
+ * kind 同时带 `command`(已替换真实 id,复制即跑)。kind 是契约的一部分,全集与触发条件见
202
+ * docs/feature/results/library.md「警告 kind 全集」。
185
203
  */
186
- export type SelectionWarning = {
204
+ export type ScopeWarning = {
187
205
  /** 选中快照的覆盖 < 该实验已知 eval 并集(本地历史 ∪ knownEvalIds)。 */
188
206
  kind: "partial-coverage";
189
207
  experimentId: string;
190
208
  covered: number;
191
209
  total: number;
192
210
  message: string;
211
+ /** 一条可复制即跑的推进命令:`niceeval exp <experimentId>`。 */
212
+ command: string;
193
213
  } | {
194
- /** 该实验选中的快照早于 Selection 中最新的落盘;无阈值,如实触发,要阈值消费方按字段自比。 */
214
+ /** 该实验选中的快照早于 Scope 中最新的落盘;无阈值,如实触发,要阈值消费方按字段自比。 */
195
215
  kind: "stale-snapshot";
196
216
  experimentId: string;
197
217
  startedAt: string;
198
218
  latestStartedAt: string;
199
219
  message: string;
220
+ /** 一条可复制即跑的推进命令:`niceeval exp <experimentId>`。 */
221
+ command: string;
200
222
  } | {
201
223
  /** 选中快照缺 completedAt(进程中断,未收尾);已落盘 attempt 照常读出,警告提示集合可能不完整。 */
202
224
  kind: "unfinished-snapshot";
@@ -205,6 +227,8 @@ export type SelectionWarning = {
205
227
  /** 该快照目录的绝对路径。 */
206
228
  dir: string;
207
229
  message: string;
230
+ /** 一条可复制即跑的推进命令:`niceeval exp <experimentId>`。 */
231
+ command: string;
208
232
  };
209
233
  /** dedupeAttempts 的警告:身份键缺 startedAt,宁可不去重也不误删。 */
210
234
  export interface DedupeWarning {
@@ -16,8 +16,9 @@ export declare function displayExperimentName(id: string | undefined): string |
16
16
  */
17
17
  export declare function experimentGroupOf(experimentId: string): string | undefined;
18
18
  /**
19
- * eval id 前缀过滤,同 CLI 位置参数的分段语义(src/runner/discover.ts):
20
- * "algebra" 匹配自身与 "algebra/..." 子级,不误配 "algebra2";允许 "algebra/" 尾斜杠写法,等价。
19
+ * eval id 前缀过滤,同 CLI 位置参数语义(docs/feature/reports/show.md「打开与收窄」):
20
+ * eval 位置参数是收窄过滤,按**裸前缀宽松匹配**——"algebra" 命中 "algebra""algebra/..."
21
+ * 也命中 "algebra2",多命中正是它的用途(与 `--experiment` 的按路径段匹配有意不同)。
21
22
  */
22
23
  export declare function evalPrefixPredicate(evals?: string | string[]): (id: string) => boolean;
23
24
  /** 无 experimentId 时的兜底标签。 */
@@ -36,14 +36,15 @@ export function experimentGroupOf(experimentId) {
36
36
  return experimentId.split("/").slice(0, -1).join("/");
37
37
  }
38
38
  /**
39
- * eval id 前缀过滤,同 CLI 位置参数的分段语义(src/runner/discover.ts):
40
- * "algebra" 匹配自身与 "algebra/..." 子级,不误配 "algebra2";允许 "algebra/" 尾斜杠写法,等价。
39
+ * eval id 前缀过滤,同 CLI 位置参数语义(docs/feature/reports/show.md「打开与收窄」):
40
+ * eval 位置参数是收窄过滤,按**裸前缀宽松匹配**——"algebra" 命中 "algebra""algebra/..."
41
+ * 也命中 "algebra2",多命中正是它的用途(与 `--experiment` 的按路径段匹配有意不同)。
41
42
  */
42
43
  export function evalPrefixPredicate(evals) {
43
44
  if (evals === undefined)
44
45
  return () => true;
45
- const prefixes = (Array.isArray(evals) ? evals : [evals]).map((p) => p.replace(/\/+$/, ""));
46
- return (id) => prefixes.some((prefix) => id === prefix || id.startsWith(prefix + "/"));
46
+ const prefixes = Array.isArray(evals) ? evals : [evals];
47
+ return (id) => prefixes.some((prefix) => id.startsWith(prefix));
47
48
  }
48
49
  /** 无 experimentId 时的兜底标签。 */
49
50
  export function fallbackExperimentLabel(result) {
@@ -0,0 +1,44 @@
1
+ # 中文公开文档信息架构
2
+
3
+ `zh/` 按读者此刻的需求组织,不按 NiceEval 的内部模块组织。判断一页放在哪里时,先问读者是在学习、完成任务、查事实、理解原理,还是修复问题。
4
+
5
+ ## 五个正文分区
6
+
7
+ | 目录 | 页面类型 | 读者要什么 | 本站边界 |
8
+ | --- | --- | --- | --- |
9
+ | `tutorials/` | Tutorial | 跟着一条安全路径拿到第一次成功 | 当前只有 `quickstart.mdx`。不为凑齐目录增加第二条入门路径 |
10
+ | `how-to/` | How-to | 完成一个现实任务 | 标题用任务表述;步骤、验证方式和失败后的下一步要清楚 |
11
+ | `explanation/` | Explanation | 理解概念、边界和运行原理 | 不承担完整操作步骤或字段罗列 |
12
+ | `reference/` | Technical Reference | 快速查准确、完整的事实 | API 签名、字段、类型和 CLI flags 以源码为权威来源 |
13
+ | `troubleshooting/` | Troubleshooting | 从可见症状定位并修复问题 | 按症状组织,不按内部模块组织 |
14
+
15
+ `index.mdx` 和 `introduction.mdx` 是站点入口,不硬归入正文类型。`examples/` 是独立资源入口,不属于 Diátaxis 的四种正文类型。
16
+
17
+ ## Examples 的收录标准
18
+
19
+ `examples/` 只收录有真实可运行源码的项目。一个案例页只回答四件事:被测对象是什么、这个项目证明什么、源码在哪里、怎么运行。操作步骤的通用版本链接 How-to,字段和配置全集链接 Reference,不在案例页复制。
20
+
21
+ - 接入前后能从仓库源码计算时,由生成器产出 diff 页面,不手抄代码。
22
+ - 同一个可运行项目只保留一张案例页。Skill、Plugin 等相邻主题共享实验设计时合并,不复制两份近似正文。
23
+ - 只有一个条目的 Showcase 不单独成页,真实项目直接列在 `examples/index.mdx`。
24
+ - 没有可运行源码、只有片段或设想的内容不进 Examples。片段进入对应 How-to,未实现方向留在 Roadmap。
25
+
26
+ ## Reference 的生成边界
27
+
28
+ Technical Reference 不等于整页都由生成器拼出来。页面仍可手写短导语、最小示例和去往 How-to 的链接,但代码形状不能手抄:
29
+
30
+ - `{/* GENERATED:BEGIN ... */}` 与 `{/* GENERATED:END ... */}` 之间的 API 成员、字段和 CLI flags 由 `pnpm docs:reference` 从源码紧邻注释生成。
31
+ - 新增需要穷举的接口字段、函数、联合类型或 CLI flag 时,扩展 `scripts/generate-reference.ts` 的 region 映射,不在 MDX 里另写一份字段全集。
32
+ - 能力矩阵、选择表和行为约束暂时由手写 Reference 承担;它们必须只描述当前实现,并避免重复已经生成的类型签名。
33
+ - `reference/` 中没有生成区块的页面不因此成为 API 事实的第二来源。出现代码形状时,优先链接已有生成页;确实缺少生成入口时先补生成器。
34
+
35
+ 运行 `pnpm docs:reference` 后,生成器只改 `zh/reference/` 中已登记的 region。`test/reference-consistency.test.ts` 会拦截源码与生成区块的漂移。
36
+
37
+ ## 当前页面归类
38
+
39
+ - `explanation/runner.mdx` 解释执行引擎如何发现、调度、缓存和产出结果,不是一步一任务的操作指南。
40
+ - `reference/official-adapters.mdx`、`reference/report-components.mdx` 和 `reference/results-data.mdx` 用于查能力、组件或数据 API,不能留在 How-to。
41
+ - `troubleshooting/debugging.mdx` 与 `troubleshooting/debug-sandbox.mdx` 从失败症状出发,目录独立,在导航中归入 How-to Guides。
42
+ - `how-to/sandbox-providers.mdx` 目前保留在 How-to,因为主问题是选择并配置 Provider;若以后字段表继续增长,再拆出独立 Provider Reference。
43
+
44
+ 新增或移动页面时,同时更新 `docs-site/docs.json` 和旧路径 redirect。校验命令以 [`../AGENTS.md`](../AGENTS.md) 为准。