niceeval 0.5.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/INDEX.md +55 -0
  2. package/dist/agents/types.d.ts +321 -0
  3. package/dist/context/types.d.ts +335 -0
  4. package/dist/i18n/core.d.ts +11 -0
  5. package/dist/i18n/en.d.ts +181 -0
  6. package/dist/i18n/index.d.ts +5 -0
  7. package/dist/i18n/zh-CN.d.ts +183 -0
  8. package/dist/o11y/otlp/parse.d.ts +3 -0
  9. package/dist/o11y/otlp/receiver.d.ts +21 -0
  10. package/dist/o11y/otlp/turn-otel.d.ts +43 -0
  11. package/dist/o11y/types.d.ts +190 -0
  12. package/dist/report/aggregate.d.ts +57 -0
  13. package/dist/report/aggregate.js +212 -0
  14. package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
  15. package/dist/report/built-ins/experiment-comparison.js +13 -0
  16. package/dist/report/built-ins/index.d.ts +1 -0
  17. package/dist/report/built-ins/index.js +2 -0
  18. package/dist/report/components.d.ts +180 -0
  19. package/dist/report/components.js +132 -0
  20. package/dist/report/compute.d.ts +106 -0
  21. package/dist/report/compute.js +600 -0
  22. package/dist/report/flag.d.ts +6 -0
  23. package/dist/report/flag.js +8 -0
  24. package/dist/report/format.d.ts +26 -0
  25. package/dist/report/format.js +109 -0
  26. package/dist/report/index.d.ts +19 -0
  27. package/dist/report/index.js +30 -0
  28. package/dist/report/load.d.ts +12 -0
  29. package/dist/report/load.js +56 -0
  30. package/dist/report/locale.d.ts +94 -0
  31. package/dist/report/locale.js +183 -0
  32. package/dist/report/metrics.d.ts +21 -0
  33. package/dist/report/metrics.js +115 -0
  34. package/dist/report/primitives.d.ts +64 -0
  35. package/dist/report/primitives.js +113 -0
  36. package/dist/report/react/AttemptList.d.ts +23 -0
  37. package/dist/report/react/AttemptList.js +19 -0
  38. package/dist/report/react/DeltaTable.d.ts +8 -0
  39. package/dist/report/react/DeltaTable.js +24 -0
  40. package/dist/report/react/EvalList.d.ts +10 -0
  41. package/dist/report/react/EvalList.js +0 -0
  42. package/dist/report/react/ExperimentList.d.ts +11 -0
  43. package/dist/report/react/ExperimentList.js +67 -0
  44. package/dist/report/react/GroupSummary.d.ts +8 -0
  45. package/dist/report/react/GroupSummary.js +8 -0
  46. package/dist/report/react/MetricBars.d.ts +10 -0
  47. package/dist/report/react/MetricBars.js +48 -0
  48. package/dist/report/react/MetricLine.d.ts +9 -0
  49. package/dist/report/react/MetricLine.js +68 -0
  50. package/dist/report/react/MetricMatrix.d.ts +10 -0
  51. package/dist/report/react/MetricMatrix.js +23 -0
  52. package/dist/report/react/MetricScatter.d.ts +9 -0
  53. package/dist/report/react/MetricScatter.js +96 -0
  54. package/dist/report/react/MetricTable.d.ts +11 -0
  55. package/dist/report/react/MetricTable.js +26 -0
  56. package/dist/report/react/RunOverview.d.ts +8 -0
  57. package/dist/report/react/RunOverview.js +12 -0
  58. package/dist/report/react/Scoreboard.d.ts +8 -0
  59. package/dist/report/react/Scoreboard.js +26 -0
  60. package/dist/report/react/cell.d.ts +9 -0
  61. package/dist/report/react/cell.js +9 -0
  62. package/dist/report/react/chart-math.d.ts +14 -0
  63. package/dist/report/react/chart-math.js +68 -0
  64. package/dist/report/react/colors.d.ts +14 -0
  65. package/dist/report/react/colors.js +45 -0
  66. package/dist/report/react/fixtures.d.ts +19 -0
  67. package/dist/report/react/fixtures.js +398 -0
  68. package/dist/report/react/format.d.ts +3 -0
  69. package/dist/report/react/format.js +7 -0
  70. package/dist/report/react/index.d.ts +6 -0
  71. package/dist/report/react/index.js +14 -0
  72. package/dist/report/report.d.ts +24 -0
  73. package/dist/report/report.js +49 -0
  74. package/dist/report/text/faces.d.ts +29 -0
  75. package/dist/report/text/faces.js +541 -0
  76. package/dist/report/text/layout.d.ts +75 -0
  77. package/dist/report/text/layout.js +173 -0
  78. package/dist/report/text/plot.d.ts +37 -0
  79. package/dist/report/text/plot.js +81 -0
  80. package/dist/report/text/table.d.ts +8 -0
  81. package/dist/report/text/table.js +86 -0
  82. package/dist/report/tree.d.ts +99 -0
  83. package/dist/report/tree.js +228 -0
  84. package/dist/report/types.d.ts +408 -0
  85. package/dist/report/types.js +4 -0
  86. package/dist/report/web.d.ts +15 -0
  87. package/dist/report/web.js +37 -0
  88. package/dist/results/locator.d.ts +75 -0
  89. package/dist/results/locator.js +0 -0
  90. package/dist/results/select.d.ts +45 -0
  91. package/dist/results/select.js +230 -0
  92. package/dist/results/source-hash.d.ts +9 -0
  93. package/dist/results/types.d.ts +204 -0
  94. package/dist/runner/eval-source.d.ts +22 -0
  95. package/dist/runner/fingerprint.d.ts +23 -0
  96. package/dist/runner/types.d.ts +332 -0
  97. package/dist/sandbox/docker-stream.d.ts +24 -0
  98. package/dist/sandbox/docker.d.ts +84 -0
  99. package/dist/sandbox/e2b.d.ts +31 -0
  100. package/dist/sandbox/errors.d.ts +4 -0
  101. package/dist/sandbox/local-files.d.ts +6 -0
  102. package/dist/sandbox/paths.d.ts +4 -0
  103. package/dist/sandbox/registry.d.ts +13 -0
  104. package/dist/sandbox/resolve.d.ts +40 -0
  105. package/dist/sandbox/retry.d.ts +10 -0
  106. package/dist/sandbox/shell.d.ts +11 -0
  107. package/dist/sandbox/source-files.d.ts +13 -0
  108. package/dist/sandbox/types.d.ts +203 -0
  109. package/dist/sandbox/vercel.d.ts +37 -0
  110. package/dist/scoring/types.d.ts +70 -0
  111. package/dist/shared/aggregate.d.ts +29 -0
  112. package/dist/shared/aggregate.js +55 -0
  113. package/dist/shared/types.d.ts +27 -0
  114. package/dist/shared/verdict.d.ts +27 -0
  115. package/dist/shared/verdict.js +34 -0
  116. package/dist/tty-line.d.ts +6 -0
  117. package/dist/types.d.ts +7 -0
  118. package/dist/util.d.ts +22 -0
  119. package/docs-site/zh/concepts/adapter.mdx +7 -7
  120. package/docs-site/zh/concepts/assert.mdx +19 -18
  121. package/docs-site/zh/concepts/evals.mdx +12 -11
  122. package/docs-site/zh/concepts/experiment.mdx +2 -2
  123. package/docs-site/zh/concepts/judge.mdx +9 -9
  124. package/docs-site/zh/concepts/overview.mdx +3 -3
  125. package/docs-site/zh/concepts/tier.mdx +6 -6
  126. package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
  127. package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
  128. package/docs-site/zh/guides/agent-feedback-loop.mdx +237 -78
  129. package/docs-site/zh/guides/connect-otel.mdx +3 -3
  130. package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
  131. package/docs-site/zh/guides/custom-reports.mdx +196 -36
  132. package/docs-site/zh/guides/experiments.mdx +7 -1
  133. package/docs-site/zh/guides/fixtures.mdx +4 -2
  134. package/docs-site/zh/guides/official-adapters.mdx +31 -9
  135. package/docs-site/zh/guides/publish-report.mdx +4 -4
  136. package/docs-site/zh/guides/report-components.mdx +194 -55
  137. package/docs-site/zh/guides/reporters.mdx +2 -2
  138. package/docs-site/zh/guides/results-data.mdx +61 -34
  139. package/docs-site/zh/guides/runner.mdx +2 -2
  140. package/docs-site/zh/guides/sandbox-agent.mdx +3 -2
  141. package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +12 -12
  142. package/docs-site/zh/guides/viewing-results.mdx +200 -79
  143. package/docs-site/zh/guides/write-experiment.mdx +9 -1
  144. package/docs-site/zh/guides/write-send.mdx +4 -3
  145. package/docs-site/zh/index.mdx +4 -3
  146. package/docs-site/zh/introduction.mdx +2 -1
  147. package/docs-site/zh/quickstart.mdx +5 -3
  148. package/docs-site/zh/reference/builtin-agents.mdx +38 -13
  149. package/docs-site/zh/reference/capabilities.mdx +1 -1
  150. package/docs-site/zh/reference/cli.mdx +36 -13
  151. package/docs-site/zh/reference/define-agent.mdx +15 -13
  152. package/docs-site/zh/reference/define-config.mdx +1 -1
  153. package/docs-site/zh/reference/define-eval.mdx +3 -3
  154. package/docs-site/zh/reference/events.mdx +9 -3
  155. package/docs-site/zh/reference/expect.mdx +13 -8
  156. package/package.json +10 -6
  157. package/src/agents/bub.ts +129 -42
  158. package/src/agents/claude-code.test.ts +152 -0
  159. package/src/agents/claude-code.ts +127 -13
  160. package/src/agents/codex.test.ts +192 -0
  161. package/src/agents/codex.ts +151 -12
  162. package/src/agents/index.ts +9 -3
  163. package/src/agents/manifest.ts +41 -0
  164. package/src/agents/plugin-config.test.ts +94 -0
  165. package/src/agents/shared.ts +33 -2
  166. package/src/agents/skills.test.ts +214 -0
  167. package/src/agents/skills.ts +304 -0
  168. package/src/agents/streaming.ts +5 -5
  169. package/src/agents/types.ts +59 -3
  170. package/src/cli.ts +74 -47
  171. package/src/context/context.test.ts +20 -2
  172. package/src/context/control-flow.ts +1 -1
  173. package/src/context/session.test.ts +92 -1
  174. package/src/context/session.ts +23 -1
  175. package/src/context/types.ts +4 -4
  176. package/src/define.test.ts +8 -8
  177. package/src/define.ts +11 -11
  178. package/src/expect/index.ts +5 -5
  179. package/src/i18n/en.ts +33 -15
  180. package/src/i18n/zh-CN.ts +37 -21
  181. package/src/index.ts +1 -1
  182. package/src/o11y/execution-tree.test.ts +452 -0
  183. package/src/o11y/execution-tree.ts +367 -0
  184. package/src/o11y/otlp/receiver.ts +1 -1
  185. package/src/o11y/otlp/select.ts +7 -3
  186. package/src/o11y/parsers/claude-code.test.ts +175 -0
  187. package/src/o11y/parsers/claude-code.ts +32 -0
  188. package/src/o11y/types.ts +6 -1
  189. package/src/report/aggregate.ts +26 -20
  190. package/src/report/built-in-user-parity.test.tsx +640 -0
  191. package/src/report/built-ins/experiment-comparison.tsx +19 -0
  192. package/src/report/built-ins/index.ts +2 -0
  193. package/src/report/components.tsx +225 -50
  194. package/src/report/compute.ts +335 -54
  195. package/src/report/dual-render.test.tsx +1129 -0
  196. package/src/report/flag.ts +1 -1
  197. package/src/report/format.ts +35 -2
  198. package/src/report/index.ts +47 -14
  199. package/src/report/load.ts +1 -1
  200. package/src/report/locale.ts +229 -0
  201. package/src/report/metrics.ts +39 -15
  202. package/src/report/primitives.tsx +100 -2
  203. package/src/report/react/AttemptList.tsx +113 -0
  204. package/src/report/react/DeltaTable.tsx +9 -6
  205. package/src/report/react/EvalList.tsx +0 -0
  206. package/src/report/react/ExperimentList.tsx +220 -0
  207. package/src/report/react/GroupSummary.tsx +66 -0
  208. package/src/report/react/MetricBars.tsx +24 -13
  209. package/src/report/react/MetricLine.tsx +21 -21
  210. package/src/report/react/MetricMatrix.tsx +8 -4
  211. package/src/report/react/MetricScatter.tsx +126 -96
  212. package/src/report/react/MetricTable.tsx +115 -15
  213. package/src/report/react/RunOverview.tsx +48 -26
  214. package/src/report/react/Scoreboard.tsx +15 -7
  215. package/src/report/react/cell.tsx +18 -9
  216. package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
  217. package/src/report/react/colors.ts +24 -15
  218. package/src/report/react/enhance.js +168 -0
  219. package/src/report/react/fixtures.ts +169 -34
  220. package/src/report/react/format.ts +1 -1
  221. package/src/report/react/index.tsx +21 -6
  222. package/src/report/react/render.test.tsx +180 -31
  223. package/src/report/react/styles.css +432 -213
  224. package/src/report/report.test.ts +398 -94
  225. package/src/report/report.ts +29 -11
  226. package/src/report/text/faces.ts +344 -96
  227. package/src/report/text/layout.ts +76 -11
  228. package/src/report/text/table.ts +98 -0
  229. package/src/report/tree.ts +103 -20
  230. package/src/report/types.ts +184 -25
  231. package/src/report/web.ts +46 -19
  232. package/src/results/annotated-source.test.ts +146 -0
  233. package/src/results/annotated-source.ts +128 -0
  234. package/src/results/attempt-evidence.test.ts +158 -0
  235. package/src/results/attempt-evidence.ts +158 -0
  236. package/src/results/attempt-source.ts +48 -0
  237. package/src/results/copy.ts +154 -139
  238. package/src/results/format.ts +54 -47
  239. package/src/results/host-equivalence.test.ts +527 -0
  240. package/src/results/index.ts +56 -14
  241. package/src/results/locator.test.ts +198 -0
  242. package/src/results/locator.ts +0 -0
  243. package/src/results/open.ts +392 -175
  244. package/src/results/results.test.ts +943 -494
  245. package/src/results/select.ts +129 -23
  246. package/src/results/skipped-notice.ts +0 -0
  247. package/src/results/source-hash.ts +28 -0
  248. package/src/results/types.ts +98 -62
  249. package/src/results/writer.ts +304 -176
  250. package/src/runner/attempt.test.ts +170 -0
  251. package/src/runner/attempt.ts +48 -13
  252. package/src/runner/discover.test.ts +65 -0
  253. package/src/runner/discover.ts +6 -2
  254. package/src/runner/eval-source.test.ts +121 -0
  255. package/src/runner/eval-source.ts +45 -0
  256. package/src/runner/fingerprint.ts +54 -1
  257. package/src/runner/remote-sandbox.ts +1 -1
  258. package/src/runner/report.test.ts +6 -8
  259. package/src/runner/report.ts +5 -4
  260. package/src/runner/reporters/artifacts.ts +27 -41
  261. package/src/runner/reporters/braintrust.test.ts +3 -3
  262. package/src/runner/reporters/braintrust.ts +1 -1
  263. package/src/runner/reporters/console.ts +6 -6
  264. package/src/runner/reporters/json.ts +4 -4
  265. package/src/runner/reporters/live.test.ts +56 -0
  266. package/src/runner/reporters/live.ts +36 -10
  267. package/src/runner/reporters/quiet.test.ts +66 -0
  268. package/src/runner/reporters/quiet.ts +49 -0
  269. package/src/runner/reporters/shared.ts +5 -5
  270. package/src/runner/reporters/table.ts +17 -17
  271. package/src/runner/run.ts +61 -102
  272. package/src/runner/types.ts +43 -32
  273. package/src/sandbox/checkpoint.ts +1 -1
  274. package/src/sandbox/docker.ts +15 -2
  275. package/src/sandbox/e2b.ts +11 -5
  276. package/src/sandbox/errors.ts +13 -0
  277. package/src/sandbox/index.ts +3 -3
  278. package/src/sandbox/local-files.ts +1 -1
  279. package/src/sandbox/registry.ts +6 -5
  280. package/src/sandbox/resolve.ts +38 -23
  281. package/src/sandbox/retry.test.ts +68 -0
  282. package/src/sandbox/retry.ts +44 -0
  283. package/src/sandbox/types.ts +24 -24
  284. package/src/sandbox/vercel.ts +17 -3
  285. package/src/scoring/collector.ts +3 -3
  286. package/src/scoring/judge.ts +2 -2
  287. package/src/scoring/scoped.ts +13 -1
  288. package/src/scoring/types.ts +7 -7
  289. package/src/scoring/verdict.ts +4 -4
  290. package/src/shared/aggregate.ts +23 -3
  291. package/src/shared/types.ts +2 -2
  292. package/src/shared/{outcome.ts → verdict.ts} +15 -15
  293. package/src/show/compose.ts +12 -132
  294. package/src/show/index.ts +119 -95
  295. package/src/show/render.ts +462 -159
  296. package/src/show/show.test.ts +471 -253
  297. package/src/source-loc.ts +1 -1
  298. package/src/tty-line.ts +32 -0
  299. package/src/types.ts +2 -2
  300. package/src/util.test.ts +21 -1
  301. package/src/util.ts +5 -1
  302. package/src/view/app/App.tsx +33 -198
  303. package/src/view/app/components/AttemptModal.tsx +6 -6
  304. package/src/view/app/components/CodeView.tsx +2 -2
  305. package/src/view/app/components/CopyControls.tsx +32 -36
  306. package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
  307. package/src/view/app/components/ui/badge.tsx +1 -1
  308. package/src/view/app/components/ui/dialog.tsx +2 -2
  309. package/src/view/app/i18n.ts +40 -31
  310. package/src/view/app/lib/artifact-url.ts +1 -1
  311. package/src/view/app/lib/attempt-route.test.ts +37 -47
  312. package/src/view/app/lib/attempt-route.ts +26 -26
  313. package/src/view/app/lib/rows.ts +9 -112
  314. package/src/view/app/lib/verdict.ts +25 -0
  315. package/src/view/app/main.tsx +9 -12
  316. package/src/view/app/pages/RunsPage.tsx +4 -4
  317. package/src/view/app/pages/TracesPage.tsx +3 -3
  318. package/src/view/app/shared.ts +0 -1
  319. package/src/view/app/types.ts +6 -51
  320. package/src/view/artifact-serving.test.ts +136 -0
  321. package/src/view/client-dist/app.css +1 -1
  322. package/src/view/client-dist/app.js +21 -21
  323. package/src/view/data.test.ts +214 -143
  324. package/src/view/data.ts +166 -120
  325. package/src/view/index.ts +23 -11
  326. package/src/view/server.ts +52 -17
  327. package/src/view/shared/types.ts +22 -31
  328. package/src/view/styles.css +117 -327
  329. package/src/view/view-report.test.ts +146 -98
  330. package/src/report/default-report.tsx +0 -222
  331. package/src/report/dual-face.test.tsx +0 -527
  332. package/src/report/react/CaseList.tsx +0 -70
  333. package/src/view/app/components/CostScoreChart.tsx +0 -147
  334. package/src/view/app/components/ExperimentTable.tsx +0 -284
  335. package/src/view/app/components/GroupSelector.tsx +0 -61
  336. package/src/view/app/components/primitives.tsx +0 -43
  337. package/src/view/app/lib/outcome.ts +0 -82
@@ -0,0 +1,37 @@
1
+ // web 宿主(view --report)的装载入口:同一棵树走 web 面,renderToStaticMarkup 吐静态
2
+ // HTML 烘进查看器的报告槽。只有这一侧真正 import react-dom(import 边界即运行时边界),
3
+ // 所以本文件不从 niceeval/report 的入口 re-export —— 宿主与测试按源路径 import。
4
+ import * as React from "react";
5
+ import { renderToStaticMarkup } from "react-dom/server";
6
+ import { resolveReportTree, runWithWebContext, validateReportTree } from "./tree.js";
7
+ import { DEFAULT_REPORT_LOCALE } from "./locale.js";
8
+ /**
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 都在报告顶上如实报残缺,不静默。
13
+ */
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)))));
16
+ }
17
+ /**
18
+ * build → 渲染前解析数据组件(唯一的 await 边界)→ 树校验(与 text 宿主同一遍)→ 静态渲染
19
+ * web 面;Selection 有挑选警告时在报告顶部前置一块警告 HTML;报告树里的 RunOverview
20
+ * 已经渲染同一条时不重复。
21
+ */
22
+ export async function renderReportToStaticHtml(definition, ctx, options) {
23
+ const node = await definition.build(ctx);
24
+ const resolved = await resolveReportTree(node);
25
+ validateReportTree(resolved);
26
+ const webCtx = {
27
+ attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
28
+ locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
29
+ };
30
+ 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);
34
+ });
35
+ const warnings = missingWarnings.length > 0 ? renderSelectionWarningsHtml(missingWarnings) : "";
36
+ return warnings + body;
37
+ }
@@ -0,0 +1,75 @@
1
+ /** 不透明的 Attempt 定位符:`@` + 1 位 scheme 字符 + 定长 base36 body。字符串品牌,防止和普通 string 混用。 */
2
+ export type AttemptLocator = string & {
3
+ readonly __brand: "AttemptLocator";
4
+ };
5
+ /** locator 派生自的不可变身份元组;attempt 是 EvalResult.attempt 的 0-indexed 值,不是展示用的 1-indexed 序号。 */
6
+ export interface AttemptIdentity {
7
+ experimentId: string;
8
+ /** SnapshotMeta.startedAt 字段(不是快照目录名——两者通常但不总是相等,见 writer.ts 的说明)。 */
9
+ snapshotStartedAt: string;
10
+ evalId: string;
11
+ /** 0-indexed;展示层(CLI/show)在边界处 +1,身份本身永远用内部下标。 */
12
+ attempt: number;
13
+ }
14
+ /** locator 前缀,恒为 `@`;与 Eval id 前缀在 CLI 位置参数解析里无歧义(eval id 不以 `@` 起头)。 */
15
+ export declare const ATTEMPT_LOCATOR_PREFIX = "@";
16
+ /**
17
+ * 确定性、带版本编码:同一身份元组永远产出同一 locator;scheme 版本混进哈希输入,
18
+ * 一旦编码规则升级(scheme 号递增),新旧两代 locator 天然落在不同哈希空间,不会互相撞车。
19
+ */
20
+ export declare function encodeAttemptLocator(identity: AttemptIdentity): AttemptLocator;
21
+ /** decodeAttemptLocator 的结果:locator 是身份的单向哈希,这里只判断字符串本身合不合法,不还原身份元组。 */
22
+ export type LocatorDecodeResult = {
23
+ valid: true;
24
+ scheme: number;
25
+ } | {
26
+ valid: false;
27
+ reason: string;
28
+ };
29
+ /**
30
+ * 语法校验:`@` 前缀 + scheme 字符 + body。不查 body 是否真对应某个已知 Attempt——
31
+ * 那是 reader 建好的 locator → AttemptHandle 索引的事(resolveAttemptLocator)。
32
+ */
33
+ export declare function decodeAttemptLocator(locator: string): LocatorDecodeResult;
34
+ /** buildLocatorIndex 的输入项:一个已知 Attempt 的身份 + 调用方自己的句柄类型(通常是 AttemptHandle)。 */
35
+ export interface LocatorAttempt<T> {
36
+ identity: AttemptIdentity;
37
+ handle: T;
38
+ }
39
+ /**
40
+ * 撞车:两个不同的身份元组独立编码出了同一个 locator 字符串。设计上应当极其罕见
41
+ * (见 BODY_LENGTH 的空间量级),但绝不能被静默吞掉——reader 打开结果根时一旦命中,
42
+ * 直接抛出,交给上层(未来的 CLI 层)决定怎么给用户看。
43
+ */
44
+ export declare class LocatorCollisionError extends Error {
45
+ readonly locator: AttemptLocator;
46
+ readonly identities: readonly [AttemptIdentity, AttemptIdentity];
47
+ constructor(locator: AttemptLocator, identities: readonly [AttemptIdentity, AttemptIdentity]);
48
+ }
49
+ /**
50
+ * 批量建 locator → 句柄索引。同一身份元组出现多次(如 --resume 携带条目在新旧两个快照里
51
+ * 都能被扫描到,身份沿用原 Attempt 不变)不算撞车,后出现的覆盖先出现的;真正撞车
52
+ * ——两个不同身份编码同一个字符串——才抛 LocatorCollisionError。
53
+ *
54
+ * @param encode @internal 仅供测试注入确定性会撞车的编码函数;生产调用不传,用默认实现。
55
+ */
56
+ export declare function buildLocatorIndex<T>(attempts: Iterable<LocatorAttempt<T>>, encode?: (identity: AttemptIdentity) => AttemptLocator): Map<AttemptLocator, T>;
57
+ /** resolveAttemptLocator 的结果:found / malformed / not-found 三种失败模式互相区分,不折叠成一个 Error。 */
58
+ export type LocatorResolution<T> = {
59
+ kind: "found";
60
+ locator: AttemptLocator;
61
+ handle: T;
62
+ } | {
63
+ kind: "malformed";
64
+ input: string;
65
+ reason: string;
66
+ } | {
67
+ kind: "not-found";
68
+ locator: AttemptLocator;
69
+ };
70
+ /**
71
+ * 拿用户输入的原始字符串(通常来自 CLI 的 `@...` 位置参数)在已建好的索引里查找。
72
+ * 先语法校验(decodeAttemptLocator),语法都不对就不必查索引;语法对但索引里没有 ——
73
+ * 已损坏、已过期(指向被清理的快照)、或纯粹打错——一律 not-found,由调用方决定怎么提示。
74
+ */
75
+ export declare function resolveAttemptLocator<T>(index: ReadonlyMap<AttemptLocator, T>, input: string): LocatorResolution<T>;
Binary file
@@ -0,0 +1,45 @@
1
+ import type { AttemptHandle, DedupeWarning, Experiment, Results, Selection, SelectionWarning, Snapshot } from "./types.ts";
2
+ /** Results.latest() 的实现:每个实验取最新一次快照(= exp.snapshots[0]),生成挑选警告。 */
3
+ export declare function selectLatest(experiments: Experiment[], opts?: {
4
+ experiments?: string | string[];
5
+ }): Selection;
6
+ /** selectCurrentResults 的范围输入:experiment id 前缀与 eval id 前缀,都可缺省。 */
7
+ export interface ResultScope {
8
+ /** experiment id 前缀(--experiment),分段匹配语义同 filterExperiments。 */
9
+ experiment?: string;
10
+ /** eval id 前缀(位置参数),收窄 Selection 覆盖的 eval;覆盖警告分母同步收窄到范围内。 */
11
+ patterns?: string[];
12
+ }
13
+ /**
14
+ * 两个宿主(show / view)共用的现刻水位选择器:每个 experiment × eval 取时间上最新的那份
15
+ * 判定,跨 run 合成。results.latest() 只挑「每实验最新快照」,带 eval 前缀的局部重跑会产出
16
+ * 残缺快照;现刻水位承诺「不会因为一次局部重跑变残缺」,所以在实验的全部历史快照上逐 eval
17
+ * 向更早的 run 补齐,再把合成好的 Selection 交给宿主注入报告槽——内置默认报告与 --report 吃
18
+ * 同一份。
19
+ *
20
+ * 同一 eval 的全部 attempts 必须整批取自包含它的最新快照,不把历史快照的 attempts 平铺后
21
+ * 按 eval 聚合——否则会把不同运行的重试混成一次虚构运行。合成快照的 dir/元数据只服务报告
22
+ * 分组与来源展示,证据身份一律来自 attempt 自己的 ref。
23
+ * 警告随 Selection 重算:partial-coverage 的分母 = 已知并集 ∩ 范围(范围收窄时分母同步收窄,
24
+ * 不让范围外的缺口刷屏);stale / unfinished 与 results.latest() 同口径。
25
+ */
26
+ export declare function selectCurrentResults(results: Results, scope?: ResultScope): Selection;
27
+ /**
28
+ * Selection 构造:filter 只删不换 —— 快照删减,warnings 修剪规则是
29
+ * 「experimentId 不在幸存快照中的丢弃,非实验作用域的保留」(为将来非 per-experiment 的 kind 留位置)。
30
+ */
31
+ export declare function makeSelection(snapshots: Snapshot[], warnings: SelectionWarning[]): Selection;
32
+ /**
33
+ * 跨快照聚合前的身份键去重:(experimentId, evalId, attempt, startedAt)。
34
+ * --resume 会把上一轮已通过的结果原样合入新快照,同一 attempt 因此存在于多份落盘;
35
+ * 重复时保留最新快照里的那份(内容相同,取新快照的副本让 ref 落在最新落盘上;
36
+ * 位置取首次出现处,顺序稳定)。startedAt 缺失时宁可不去重也不误删,记入 warnings。
37
+ */
38
+ export declare function dedupeAttempts(attempts: AttemptHandle[]): {
39
+ attempts: AttemptHandle[];
40
+ warnings: DedupeWarning[];
41
+ };
42
+ /** 快照新旧比较:startedAt 优先,同刻按快照目录名(时间戳 + 随机后缀,字典序即时序)。 */
43
+ export declare function isNewerSnapshot(a: Snapshot, b: Snapshot): boolean;
44
+ /** experiment id 分段前缀过滤(--experiment / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
45
+ export declare function filterExperiments(experiments: Experiment[], filter?: string | string[]): Experiment[];
@@ -0,0 +1,230 @@
1
+ // 快照 Selection 与 attempt 去重(定稿见 docs/feature/results/library.md「选择快照」「身份键与去重」)。
2
+ //
3
+ // 选择器只有一个(latest),长在集合上;它不是 DSL,只是最常用的那次筛选。
4
+ // 选择器必须诚实:残缺、落后、未收尾都被算出来,以结构化 warnings 随 Selection 走 ——
5
+ // 渲染与否在消费方(message 是渲染好的英文句子),但缺口不静默。
6
+ import { evalPrefixPredicate } from "../shared/aggregate.js";
7
+ /** Results.latest() 的实现:每个实验取最新一次快照(= exp.snapshots[0]),生成挑选警告。 */
8
+ export function selectLatest(experiments, opts) {
9
+ const selected = filterExperiments(experiments, opts?.experiments);
10
+ const snapshots = selected.map((exp) => exp.latest);
11
+ const warnings = [];
12
+ // stale 的基准:Selection 中最新的落盘(无阈值,如实触发;要阈值消费方按字段自比)。
13
+ let latestStartedAt = "";
14
+ for (const snapshot of snapshots) {
15
+ if (snapshot.startedAt > latestStartedAt)
16
+ latestStartedAt = snapshot.startedAt;
17
+ }
18
+ for (const exp of selected) {
19
+ const snapshot = exp.latest;
20
+ // 残缺检测:分母 = 该实验已知 eval 并集(本地历史 ∪ 各快照携带的 knownEvalIds)。
21
+ // 位置参数允许只重跑一道题,产出的「最新快照」可能只有一道题 —— 不能安静吞下。
22
+ const covered = snapshot.evals.length;
23
+ const total = exp.evalIds.length;
24
+ if (covered < total) {
25
+ warnings.push({
26
+ kind: "partial-coverage",
27
+ experimentId: exp.id,
28
+ covered,
29
+ total,
30
+ message: `snapshot covers ${covered} of ${total} evals seen in history; re-run \`niceeval exp ${exp.id}\` for a full snapshot`,
31
+ });
32
+ }
33
+ if (snapshot.startedAt < latestStartedAt) {
34
+ warnings.push({
35
+ kind: "stale-snapshot",
36
+ experimentId: exp.id,
37
+ startedAt: snapshot.startedAt,
38
+ latestStartedAt,
39
+ message: `snapshot "${exp.id}" (${snapshot.startedAt}) predates the latest run in this selection by ${humanizeGap(snapshot.startedAt, latestStartedAt)}`,
40
+ });
41
+ }
42
+ if (!snapshot.completedAt) {
43
+ warnings.push({
44
+ kind: "unfinished-snapshot",
45
+ experimentId: exp.id,
46
+ startedAt: snapshot.startedAt,
47
+ dir: snapshot.dir,
48
+ message: `snapshot "${exp.id}" (${snapshot.startedAt}) has no completedAt — the run was interrupted; results may be incomplete`,
49
+ });
50
+ }
51
+ }
52
+ return makeSelection(snapshots, warnings);
53
+ }
54
+ /**
55
+ * 两个宿主(show / view)共用的现刻水位选择器:每个 experiment × eval 取时间上最新的那份
56
+ * 判定,跨 run 合成。results.latest() 只挑「每实验最新快照」,带 eval 前缀的局部重跑会产出
57
+ * 残缺快照;现刻水位承诺「不会因为一次局部重跑变残缺」,所以在实验的全部历史快照上逐 eval
58
+ * 向更早的 run 补齐,再把合成好的 Selection 交给宿主注入报告槽——内置默认报告与 --report 吃
59
+ * 同一份。
60
+ *
61
+ * 同一 eval 的全部 attempts 必须整批取自包含它的最新快照,不把历史快照的 attempts 平铺后
62
+ * 按 eval 聚合——否则会把不同运行的重试混成一次虚构运行。合成快照的 dir/元数据只服务报告
63
+ * 分组与来源展示,证据身份一律来自 attempt 自己的 ref。
64
+ * 警告随 Selection 重算:partial-coverage 的分母 = 已知并集 ∩ 范围(范围收窄时分母同步收窄,
65
+ * 不让范围外的缺口刷屏);stale / unfinished 与 results.latest() 同口径。
66
+ */
67
+ export function selectCurrentResults(results, scope = {}) {
68
+ const match = scope.patterns && scope.patterns.length > 0 ? evalPrefixPredicate(scope.patterns) : () => true;
69
+ const experiments = filterExperiments(results.experiments, scope.experiment);
70
+ const snapshots = [];
71
+ const warnings = [];
72
+ for (const exp of experiments) {
73
+ // 逐题取最新:快照按最新在前,首个出现即最新判定
74
+ const taken = new Map();
75
+ for (const snapshot of exp.snapshots) {
76
+ for (const ev of snapshot.evals) {
77
+ if (!match(ev.id) || taken.has(ev.id))
78
+ continue;
79
+ taken.set(ev.id, { ev, snapshot });
80
+ }
81
+ }
82
+ if (taken.size === 0)
83
+ continue;
84
+ const picks = [...taken.values()].sort((a, b) => a.ev.id.localeCompare(b.ev.id));
85
+ let startedAt = "";
86
+ let newest = picks[0].snapshot;
87
+ for (const pick of picks) {
88
+ if (pick.snapshot.startedAt > startedAt) {
89
+ startedAt = pick.snapshot.startedAt;
90
+ newest = pick.snapshot;
91
+ }
92
+ }
93
+ const evals = picks.map((p) => p.ev);
94
+ const base = exp.latest;
95
+ snapshots.push({
96
+ experimentId: exp.id,
97
+ startedAt,
98
+ agent: base.agent,
99
+ ...(base.model !== undefined ? { model: base.model } : {}),
100
+ producer: base.producer,
101
+ schemaVersion: base.schemaVersion,
102
+ evals,
103
+ attempts: evals.flatMap((ev) => ev.attempts),
104
+ dir: newest.dir,
105
+ ...(newest.completedAt !== undefined ? { completedAt: newest.completedAt } : {}),
106
+ ...(base.knownEvalIds ? { knownEvalIds: [...base.knownEvalIds] } : {}),
107
+ });
108
+ // 残缺检测:跨快照补齐后仍缺,只可能是历史上见过(或 knownEvalIds 声明过)
109
+ // 却从未在可读落盘里出现的题 —— 分母收窄到范围内,不让范围外的缺口刷屏。
110
+ const total = exp.evalIds.filter(match).length;
111
+ if (evals.length < total) {
112
+ warnings.push({
113
+ kind: "partial-coverage",
114
+ experimentId: exp.id,
115
+ covered: evals.length,
116
+ total,
117
+ message: `verdicts cover ${evals.length} of ${total} evals seen in history; re-run \`niceeval exp ${exp.id}\` for a full snapshot`,
118
+ });
119
+ }
120
+ }
121
+ let latestStartedAt = "";
122
+ for (const snapshot of snapshots) {
123
+ if (snapshot.startedAt > latestStartedAt)
124
+ latestStartedAt = snapshot.startedAt;
125
+ }
126
+ for (const snapshot of snapshots) {
127
+ if (snapshot.startedAt < latestStartedAt) {
128
+ warnings.push({
129
+ kind: "stale-snapshot",
130
+ experimentId: snapshot.experimentId,
131
+ startedAt: snapshot.startedAt,
132
+ latestStartedAt,
133
+ message: `verdicts for "${snapshot.experimentId}" were produced at ${snapshot.startedAt}, before the latest run in this selection (${latestStartedAt})`,
134
+ });
135
+ }
136
+ if (snapshot.completedAt === undefined) {
137
+ warnings.push({
138
+ kind: "unfinished-snapshot",
139
+ experimentId: snapshot.experimentId,
140
+ startedAt: snapshot.startedAt,
141
+ 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`,
143
+ });
144
+ }
145
+ }
146
+ return makeSelection(snapshots, warnings);
147
+ }
148
+ /**
149
+ * Selection 构造:filter 只删不换 —— 快照删减,warnings 修剪规则是
150
+ * 「experimentId 不在幸存快照中的丢弃,非实验作用域的保留」(为将来非 per-experiment 的 kind 留位置)。
151
+ */
152
+ export function makeSelection(snapshots, warnings) {
153
+ return {
154
+ snapshots,
155
+ warnings,
156
+ filter(predicate) {
157
+ const kept = snapshots.filter(predicate);
158
+ const survivors = new Set(kept.map((s) => s.experimentId));
159
+ const keptWarnings = warnings.filter((w) => {
160
+ const scope = w.experimentId;
161
+ return typeof scope !== "string" || survivors.has(scope);
162
+ });
163
+ return makeSelection(kept, keptWarnings);
164
+ },
165
+ };
166
+ }
167
+ /**
168
+ * 跨快照聚合前的身份键去重:(experimentId, evalId, attempt, startedAt)。
169
+ * --resume 会把上一轮已通过的结果原样合入新快照,同一 attempt 因此存在于多份落盘;
170
+ * 重复时保留最新快照里的那份(内容相同,取新快照的副本让 ref 落在最新落盘上;
171
+ * 位置取首次出现处,顺序稳定)。startedAt 缺失时宁可不去重也不误删,记入 warnings。
172
+ */
173
+ export function dedupeAttempts(attempts) {
174
+ const deduped = [];
175
+ const indexByKey = new Map();
176
+ const warnings = [];
177
+ for (const attempt of attempts) {
178
+ const r = attempt.result;
179
+ if (!r.startedAt) {
180
+ warnings.push({
181
+ kind: "missing-startedAt",
182
+ experimentId: attempt.experimentId,
183
+ evalId: attempt.evalId,
184
+ message: `attempt ${r.attempt} of eval "${attempt.evalId}" in experiment "${attempt.experimentId}" has no startedAt; kept as-is without dedupe`,
185
+ });
186
+ deduped.push(attempt);
187
+ continue;
188
+ }
189
+ const key = JSON.stringify([attempt.experimentId, r.id, r.attempt, r.startedAt]);
190
+ const existing = indexByKey.get(key);
191
+ if (existing === undefined) {
192
+ indexByKey.set(key, deduped.length);
193
+ deduped.push(attempt);
194
+ }
195
+ else if (isNewerSnapshot(attempt.snapshot, deduped[existing].snapshot)) {
196
+ deduped[existing] = attempt;
197
+ }
198
+ }
199
+ return { attempts: deduped, warnings };
200
+ }
201
+ /** 快照新旧比较:startedAt 优先,同刻按快照目录名(时间戳 + 随机后缀,字典序即时序)。 */
202
+ export function isNewerSnapshot(a, b) {
203
+ const byStart = a.startedAt.localeCompare(b.startedAt);
204
+ if (byStart !== 0)
205
+ return byStart > 0;
206
+ return a.dir.localeCompare(b.dir) > 0;
207
+ }
208
+ /** experiment id 分段前缀过滤(--experiment / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
209
+ export function filterExperiments(experiments, filter) {
210
+ if (filter === undefined)
211
+ return experiments;
212
+ // 允许 "compare/" 这种带尾斜杠的写法,与 "compare" 等价;分段匹配不误配 "compare2"。
213
+ const prefixes = (Array.isArray(filter) ? filter : [filter]).map((p) => p.replace(/\/+$/, ""));
214
+ return experiments.filter((exp) => prefixes.some((p) => exp.id === p || exp.id.startsWith(p + "/")));
215
+ }
216
+ /** stale 警告的人话时距:选粒度最大的单位,四舍五入。 */
217
+ function humanizeGap(fromIso, toIso) {
218
+ const ms = Math.max(0, Date.parse(toIso) - Date.parse(fromIso));
219
+ const seconds = Math.round(ms / 1000);
220
+ if (seconds < 90)
221
+ return `${seconds} second${seconds === 1 ? "" : "s"}`;
222
+ const minutes = Math.round(seconds / 60);
223
+ if (minutes < 90)
224
+ return `${minutes} minute${minutes === 1 ? "" : "s"}`;
225
+ const hours = Math.round(minutes / 60);
226
+ if (hours < 36)
227
+ return `${hours} hour${hours === 1 ? "" : "s"}`;
228
+ const days = Math.round(hours / 24);
229
+ return `${days} day${days === 1 ? "" : "s"}`;
230
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 归一化 eval 源码文本:去 UTF-8 BOM、把 CRLF / CR 统一成 LF。
3
+ * 幂等——已归一化的文本再跑一次结果不变,所以 capture 时归一化一次、重建时可以放心再跑一次,
4
+ * 两处算出的 SHA-256 恒相同。不做其它改写(不 trim 行尾空白、不折叠空行):
5
+ * 行号是断言 SourceLoc 的锚,任何改变行结构的归一化都会让映射错位。
6
+ */
7
+ export declare function normalizeEvalSource(text: string): string;
8
+ /** 对(已归一化的)源码文本算 SHA-256 十六进制摘要。 */
9
+ export declare function hashEvalSource(content: string): string;
@@ -0,0 +1,204 @@
1
+ import type { EvalResult, ExperimentRunInfo, LocalizedText } from "../types.ts";
2
+ import type { O11ySummary, StreamEvent, TraceSpan } from "../types.ts";
3
+ import type { AgentSetupManifest, DiffData, SourceArtifact } from "../types.ts";
4
+ import type { AttemptLocator } from "./locator.ts";
5
+ /** attempt 级 artifact 的种类;文件名见 format.ts 的 artifactFileOf,布局见 docs/feature/results/architecture.md。 */
6
+ export declare const ARTIFACT_KINDS: readonly ["events", "trace", "o11y", "agentSetup", "diff", "sources"];
7
+ export type ArtifactKind = (typeof ARTIFACT_KINDS)[number];
8
+ /** 写这份结果的工具:niceeval 自己,或经 niceeval/results 写入面转换的第三方 harness。 */
9
+ export interface Producer {
10
+ name: string;
11
+ version?: string;
12
+ commit?: string;
13
+ }
14
+ /**
15
+ * `snapshot.json` 的持久化契约:快照元数据 —— 身份、快照级字段与版本元数据,
16
+ * 不含任何逐 attempt 数据。快照开始时写入;收尾时补写 `completedAt`。
17
+ * 字段规则与版本判定见 docs/feature/results/architecture.md「snapshot.json」「版本与升级设计」。
18
+ */
19
+ export interface SnapshotMeta {
20
+ /** 恒为 "niceeval.results";和 schemaVersion、producer 一起构成持久化契约,永不移动或改名。 */
21
+ format: "niceeval.results";
22
+ /** 结果格式版本;与读取器不同即视为不兼容,提示用 producer.version 对应的 niceeval 查看。 */
23
+ schemaVersion: number;
24
+ producer: Producer;
25
+ /** 权威的实验身份;实验目录名是它的清洗投影。 */
26
+ experimentId: string;
27
+ /** 实验运行配置(flags / runs / earlyExit / sandbox / timeoutMs / budget),快照内全部 attempt 共享。 */
28
+ experiment?: ExperimentRunInfo;
29
+ agent: string;
30
+ model?: string;
31
+ startedAt: string;
32
+ /** 收尾时补写;缺失 = 快照未收尾(进程中断),已落盘的 attempt 照常可读。 */
33
+ completedAt?: string;
34
+ /** 写入时刻该实验已知的 eval 并集 —— 残缺检测的分母随数据走(copySnapshots 自动补记,writer 可声明)。 */
35
+ knownEvalIds?: string[];
36
+ /** 项目名(来自 config.name),透传给 `niceeval view` 顶部 hero 显示。 */
37
+ name?: LocalizedText;
38
+ }
39
+ /**
40
+ * 回到证据的引用:快照目录(根相对)+ 快照内 attempt 目录(快照相对)。
41
+ * 字段名(snapshot / attempt)是 view 深链 `#/attempt/<snapshot>/<attempt>` 的持久化路由契约,
42
+ * 不随句柄改名;`snapshot` 恒为两段(`<实验目录>/<快照目录>`),`attempt` 是 `<evalId 路径>/a<n>`,
43
+ * 路由按「前两段 = 快照」解析。Reports 的 MetricCell.refs 用的是同一个身份。
44
+ */
45
+ export interface AttemptRef {
46
+ /** 根相对快照目录:`<experiment-dir>/<快照目录>`。 */
47
+ snapshot: string;
48
+ /** 快照相对 attempt 目录:`<evalId 路径>/a<n>`。 */
49
+ attempt: string;
50
+ }
51
+ /**
52
+ * 单个 eval attempt:瘦身条目 + 重 artifact 的懒加载方法。
53
+ * 懒加载即存在性判断: artifact 缺失返回 null,不抛错;同一 handle 内读过一次即记忆化。
54
+ * artifact 定位按候选顺序回退:先本 attempt 目录,再 artifactBase 指向的原快照 attempt 目录
55
+ * (--resume 合入条目的 artifact 留在原快照里);原快照被清理后如实返回 null。
56
+ */
57
+ export interface AttemptHandle {
58
+ /** 属于哪道题 —— 直达字段,不绕 result。 */
59
+ evalId: string;
60
+ /** 属于哪个实验。 */
61
+ experimentId: string;
62
+ /** EvalResult 瘦身条目:判定、断言、用量、成本(快照级字段已拼合)。 */
63
+ result: EvalResult;
64
+ /** 证据引用,指条目所在的落盘(合入后的新快照); artifact 经候选回退仍可达。 */
65
+ ref: AttemptRef;
66
+ /** 所属快照(反向引用);去重「保留最新快照里的那份」靠它比较新旧。 */
67
+ snapshot: Snapshot;
68
+ /**
69
+ * 不透明的 Attempt 定位符(见 `locator.ts`);openResults() 产出的 handle 恒会填这个字段
70
+ * (result.json 里有就直接用,没有则按当前身份兜底算,见 open.ts 的回填逻辑)。可选只是为了
71
+ * 不强制手工构造的 AttemptHandle(测试里的内存 fake)也必须带上——真实读取路径永远有值。
72
+ */
73
+ locator?: AttemptLocator;
74
+ events(): Promise<StreamEvent[] | null>;
75
+ trace(): Promise<TraceSpan[] | null>;
76
+ o11y(): Promise<O11ySummary | null>;
77
+ /** 这次 attempt 的 agent setup 装了什么(Skill / native plugin / MCP / Python plugin);没装扩展的 agent 恒为 null。 */
78
+ agentSetup(): Promise<AgentSetupManifest | null>;
79
+ diff(): Promise<DiffData | null>;
80
+ sources(): Promise<SourceArtifact[] | null>;
81
+ }
82
+ /** 一道题在一个快照里的结果:id + 该题的全部 attempt(重试历史)。 */
83
+ export interface Eval {
84
+ id: string;
85
+ attempts: AttemptHandle[];
86
+ }
87
+ /** 快照 = 单次跑的实验,物理上就是一个快照目录,没有更低一层。 */
88
+ export interface Snapshot {
89
+ /** 权威身份(snapshot.json 字段;实验目录名只是它的清洗投影)。 */
90
+ experimentId: string;
91
+ startedAt: string;
92
+ /** 缺失 = 未收尾(进程中断);已落盘 attempt 照常在下面读到。 */
93
+ completedAt?: string;
94
+ /** 本快照自己的 agent。 */
95
+ agent: string;
96
+ model?: string;
97
+ /** 实验运行配置(flags / runs / budget …),快照内全部 attempt 共享。 */
98
+ experiment?: ExperimentRunInfo;
99
+ /** 谁写的这份结果(niceeval 或第三方 harness)。 */
100
+ producer: Producer;
101
+ /** 结果格式版本(能读进来的恒为当前版本;不兼容的在 skipped)。 */
102
+ schemaVersion: number;
103
+ /** 项目名(来自 config.name),透传给 `niceeval view` 顶部 hero 显示。 */
104
+ name?: LocalizedText;
105
+ /** 每道题一项:{ id, attempts };残缺检测 / 逐题遍历从这里走。 */
106
+ evals: Eval[];
107
+ /** 全部 attempt 平铺(= evals 逐题展开),不关心题目边界的聚合消费用。 */
108
+ attempts: AttemptHandle[];
109
+ /** 快照目录的绝对路径(物理落盘就是快照本身,没有更低一层)。 */
110
+ dir: string;
111
+ /** 写入时刻该实验已知的 eval 并集(可选);copySnapshots 自动补记,writer.snapshot() 也可声明。 */
112
+ knownEvalIds?: string[];
113
+ }
114
+ /** 一个实验的全部历史:同一 experiment id 的历次快照归在一起。 */
115
+ export interface Experiment {
116
+ id: string;
117
+ /** 历次快照,最新在前。 */
118
+ snapshots: Snapshot[];
119
+ /** 最新一次(= snapshots[0])。 */
120
+ latest: Snapshot;
121
+ /** 已知 eval 并集 = 本地历史 ∪ 各快照携带的 knownEvalIds —— 残缺检测的分母。 */
122
+ evalIds: string[];
123
+ }
124
+ /** 目录扫描里读不了、但必须让调用方知道的落盘;无关 JSON 不记(静默忽略)。 */
125
+ export interface SkippedDir {
126
+ /** 落盘目录的绝对路径。 */
127
+ dir: string;
128
+ /**
129
+ * incompatible-version:schemaVersion 与读取器不同(不解析、不迁移、不降级);
130
+ * malformed:snapshot.json(或历史版本的 summary.json)是坏数据;
131
+ * incomplete:有 attempt 落盘、没有 snapshot.json —— 快照目录建好但元数据没写完的极小窗口,
132
+ * 或人为删文件。snapshot.json 是收尾事实,reader 不读无 snapshot.json 的目录;
133
+ * 已完成的 attempt 留在盘上供手工排查。
134
+ */
135
+ reason: "incompatible-version" | "malformed" | "incomplete";
136
+ /** 那份结果声明的 schemaVersion(incomplete 没有 snapshot.json,自然缺失)。 */
137
+ schemaVersion?: number;
138
+ /** 完整的 producer(name + version):只有 name === "niceeval" 才能拼 npx 提示,第三方如实报名字。 */
139
+ producer?: Producer;
140
+ /** malformed:一句英文诊断。 */
141
+ detail?: string;
142
+ }
143
+ /** openResults 的返回:experiments 分层;skipped 不静默丢。 */
144
+ export interface Results {
145
+ /** 每个实验一项,挂着自己的全部历史(id 字典序)。 */
146
+ experiments: Experiment[];
147
+ skipped: SkippedDir[];
148
+ /**
149
+ * 每个实验取最新一次快照,返回 Selection(快照与挑选警告绑在一起走)。
150
+ * `experiments` 是 experiment id 前缀过滤(string | string[]),同 CLI 位置参数语义。
151
+ */
152
+ latest(opts?: {
153
+ experiments?: string | string[];
154
+ }): Selection;
155
+ }
156
+ /**
157
+ * Selection:选出的快照 + 挑选过程算出的警告。渲染与否在消费方,但缺口永远被算出来。
158
+ * 下游(Reports 计算函数、copySnapshots)收 `Selection | Snapshot[]`;
159
+ * 手工挑的裸数组没有挑选过程,自然没有 warnings 可带,也如实。
160
+ */
161
+ export interface Selection {
162
+ snapshots: Snapshot[];
163
+ warnings: SelectionWarning[];
164
+ /**
165
+ * 只删不换:返回新 Selection,快照删减,warnings 按规则修剪 ——
166
+ * experimentId 不在幸存快照中的丢弃,非实验作用域的保留。
167
+ * 「换成上一个完整快照」这类替换式重挑不给方法,回 exp.snapshots 自己挑。
168
+ */
169
+ filter(predicate: (snapshot: Snapshot) => boolean): Selection;
170
+ }
171
+ /**
172
+ * 挑选警告:每种带 kind、可判断的结构化字段和渲染好的英文 message;
173
+ * kind 是契约的一部分,全集与触发条件见 docs/feature/results/library.md「警告 kind 全集」。
174
+ */
175
+ export type SelectionWarning = {
176
+ /** 选中快照的覆盖 < 该实验已知 eval 并集(本地历史 ∪ knownEvalIds)。 */
177
+ kind: "partial-coverage";
178
+ experimentId: string;
179
+ covered: number;
180
+ total: number;
181
+ message: string;
182
+ } | {
183
+ /** 该实验选中的快照早于 Selection 中最新的落盘;无阈值,如实触发,要阈值消费方按字段自比。 */
184
+ kind: "stale-snapshot";
185
+ experimentId: string;
186
+ startedAt: string;
187
+ latestStartedAt: string;
188
+ message: string;
189
+ } | {
190
+ /** 选中快照缺 completedAt(进程中断,未收尾);已落盘 attempt 照常读出,警告提示集合可能不完整。 */
191
+ kind: "unfinished-snapshot";
192
+ experimentId: string;
193
+ startedAt: string;
194
+ /** 该快照目录的绝对路径。 */
195
+ dir: string;
196
+ message: string;
197
+ };
198
+ /** dedupeAttempts 的警告:身份键缺 startedAt,宁可不去重也不误删。 */
199
+ export interface DedupeWarning {
200
+ kind: "missing-startedAt";
201
+ experimentId: string;
202
+ evalId: string;
203
+ message: string;
204
+ }
@@ -0,0 +1,22 @@
1
+ export interface CapturedEvalSource {
2
+ /** 项目相对路径(正斜杠),与 SourceArtifact.path / SourceLoc.file 同一约定。 */
3
+ path: string;
4
+ /** 归一化后的源码文本(去 BOM、CRLF/CR 统一成 LF)。 */
5
+ content: string;
6
+ /** 归一化文本的 SHA-256 十六进制摘要;同一份内容(哪怕跨平台不同换行符)恒相同。 */
7
+ sha256: string;
8
+ }
9
+ /**
10
+ * 读一个 eval 定义文件、归一化文本、算哈希。`filePath` 是绝对路径(discoverEvals() 里
11
+ * `DiscoveredEval.sourcePath` 的形状);`opts.root` 决定 `path` 字段相对谁计算,省略时
12
+ * 用 `process.cwd()`(与 src/source-loc.ts 的 captureLoc()、runner/attempt.ts 的
13
+ * collectSources() 同一约定——项目相对路径永远相对进程 cwd,不是相对 discoverEvals() 的
14
+ * `root` 参数,两者通常相同但不保证,显式传参让调用方按需要覆盖,也让单测不依赖真实 cwd)。
15
+ *
16
+ * 读不到文件(已删除 / 权限问题)如实抛错——发现阶段的源码文件应当总是可读,
17
+ * 与 collectSources() 运行后"读不到就跳过"的降级语义不同:那时源码可能已经在沙箱里、
18
+ * 已被清理;这里读的是刚刚 import 成功的同一个文件,读不到是需要暴露的异常。
19
+ */
20
+ export declare function captureEvalSource(filePath: string, opts?: {
21
+ root?: string;
22
+ }): Promise<CapturedEvalSource>;
@@ -0,0 +1,23 @@
1
+ import type { DiscoveredEval, EvalResult } from "../types.ts";
2
+ import type { AgentRun } from "./types.ts";
3
+ export declare function cacheKey(run: AgentRun, evalId: string): string;
4
+ /**
5
+ * @param sourceCache 按 sourcePath 缓存文件内容:一个矩阵(实验 × eval)会对同一批源文件
6
+ * 反复算指纹,不带缓存会在任何 attempt 起跑前做 E×N 次重复文件读。
7
+ */
8
+ export declare function computeFingerprint(evalDef: DiscoveredEval, run: AgentRun, sourceCache?: Map<string, Promise<string>>): Promise<string>;
9
+ export interface CarryPlan {
10
+ /** `cacheKey(run, evalId)` → 本次规划出的指纹,供调用方按同一口径判断"这条要不要携入"。 */
11
+ plannedFingerprints: Map<string, string>;
12
+ /** 命中携入条件(passed/failed 终态 + 指纹匹配)的 `${experimentId}|${evalId}` 集合。 */
13
+ priorRunKeys: Set<string>;
14
+ /** priorRunKeys 对应的完整结果对象,供 run.ts 直接并入 summary、cli.ts 直接取 verdict 展示。 */
15
+ carriedResults: EvalResult[];
16
+ }
17
+ /**
18
+ * 算出这一批 (agentRun × eval) 的指纹,并据此从 priorResults 里筛出可以携入(跳过重跑)的结果。
19
+ * run.ts 与 cli.ts(live 表格构建)必须共用这同一份计算 —— 否则两边一旦对"哪些携入"的判断
20
+ * 不一致,live 表格就会显示"还在等名额",而 run.ts 其实已经把它筛掉、根本不会调度这个 attempt
21
+ * (见 memory 的 live-carry-row-shows-waiting-forever)。
22
+ */
23
+ export declare function planCarry(evals: DiscoveredEval[], agentRuns: AgentRun[], priorResults: EvalResult[] | undefined): Promise<CarryPlan>;