niceeval 0.5.2 → 0.5.3
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.
- package/docs-site/zh/concepts/adapter.mdx +6 -6
- package/docs-site/zh/concepts/experiment.mdx +6 -6
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/guides/ci-integration.mdx +2 -0
- package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
- package/docs-site/zh/guides/custom-reports.mdx +7 -7
- package/docs-site/zh/guides/experiments.mdx +3 -3
- package/docs-site/zh/guides/publish-report.mdx +91 -0
- package/docs-site/zh/guides/report-components.mdx +4 -4
- package/docs-site/zh/guides/reporters.mdx +1 -1
- package/docs-site/zh/guides/results-data.mdx +1 -1
- package/docs-site/zh/guides/runner.mdx +1 -1
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/viewing-results.mdx +3 -19
- package/docs-site/zh/guides/write-experiment.mdx +9 -9
- package/docs-site/zh/guides/write-send.mdx +6 -6
- package/docs-site/zh/index.mdx +1 -1
- package/docs-site/zh/introduction.mdx +1 -1
- package/docs-site/zh/reference/builtin-agents.mdx +4 -0
- package/docs-site/zh/reference/cli.mdx +9 -6
- package/docs-site/zh/reference/define-agent.mdx +7 -3
- package/docs-site/zh/reference/define-config.mdx +3 -1
- package/docs-site/zh/reference/define-eval.mdx +9 -3
- package/docs-site/zh/reference/events.mdx +4 -0
- package/docs-site/zh/reference/expect.mdx +4 -0
- package/package.json +3 -2
- package/src/agents/ai-sdk.test.ts +1 -1
- package/src/agents/ai-sdk.ts +2 -2
- package/src/agents/claude-code.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/streaming.test.ts +1 -1
- package/src/agents/types.ts +2 -2
- package/src/agents/ui-message-stream.test.ts +1 -1
- package/src/cli.ts +38 -14
- package/src/context/context.test.ts +1 -1
- package/src/context/context.ts +3 -3
- package/src/context/session.ts +2 -2
- package/src/context/types.ts +2 -2
- package/src/i18n/en.ts +2 -2
- package/src/i18n/zh-CN.ts +2 -2
- package/src/report/aggregate.ts +16 -16
- package/src/report/components.tsx +1 -1
- package/src/report/compute.ts +7 -7
- package/src/report/dual-face.test.tsx +1 -1
- package/src/report/{param.ts → flag.ts} +6 -6
- package/src/report/index.ts +3 -3
- package/src/report/react/MetricLine.tsx +1 -1
- package/src/report/react/fixtures.ts +1 -1
- package/src/report/report.test.ts +17 -17
- package/src/report/types.ts +11 -11
- package/src/results/results.test.ts +2 -2
- package/src/runner/attempt.ts +3 -3
- package/src/runner/fingerprint.ts +1 -1
- package/src/runner/reporters/braintrust.test.ts +2 -2
- package/src/runner/reporters/braintrust.ts +3 -3
- package/src/runner/run.ts +1 -1
- package/src/runner/types.ts +8 -8
- package/src/view/app/css.d.ts +3 -0
- package/src/view/app/i18n.ts +3 -3
- package/src/view/app/lib/rows.ts +4 -4
- package/src/view/client-dist/app.js +2 -2
- package/src/view/data.test.ts +25 -1
- package/src/view/data.ts +27 -3
|
@@ -56,6 +56,8 @@ description: "NiceEval 内置的 claude-code、codex、bub 适配器分别做到
|
|
|
56
56
|
|
|
57
57
|
{/* GENERATED:BEGIN builtin-agent-config */}
|
|
58
58
|
|
|
59
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
60
|
+
|
|
59
61
|
### `ClaudeCodeConfig`
|
|
60
62
|
|
|
61
63
|
#### `apiKey`
|
|
@@ -196,6 +198,8 @@ export default uiMessageStreamAgent({
|
|
|
196
198
|
|
|
197
199
|
{/* GENERATED:BEGIN ui-message-stream-options */}
|
|
198
200
|
|
|
201
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
202
|
+
|
|
199
203
|
#### `name`
|
|
200
204
|
|
|
201
205
|
```ts
|
|
@@ -4,7 +4,7 @@ sidebarTitle: "命令行"
|
|
|
4
4
|
description: "NiceEval CLI 参考:exp、init、list、clean 和 view 命令,以及 experiment、eval 过滤、sandbox、并发、预算和 JUnit CI 输出。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
[NiceEval](https://niceeval.com/) CLI 是发现、运行和查看 eval 的入口。实际执行 eval 时采用 experiment-first 模型:**`exp` 选择可签入的运行配置**,experiment 后的位置参数才按 eval ID 前缀过滤。agent、model 和
|
|
7
|
+
[NiceEval](https://niceeval.com/) CLI 是发现、运行和查看 eval 的入口。实际执行 eval 时采用 experiment-first 模型:**`exp` 选择可签入的运行配置**,experiment 后的位置参数才按 eval ID 前缀过滤。agent、model 和 flags 写在 `experiments/`,不靠临时 CLI 参数。
|
|
8
8
|
|
|
9
9
|
## 命令
|
|
10
10
|
|
|
@@ -56,10 +56,12 @@ npx niceeval exp compare-models weather
|
|
|
56
56
|
|
|
57
57
|
## 常用 flags
|
|
58
58
|
|
|
59
|
-
下表由 CLI 的 flag
|
|
59
|
+
下表由 CLI 的 flag 解析表生成,表外的 flag 一律按未知 flag 报错并以非零状态退出。**没有**用于选择 sandbox 后端的 CLI flag:sandbox 后端只能写在代码里——在 experiment(`defineExperiment`)里设置 `sandbox`,或者在 `niceeval.config.ts`(`defineConfig`)里设置作为项目级兜底,值来自 `niceeval/sandbox` 的 `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()`。
|
|
60
60
|
|
|
61
61
|
{/* GENERATED:BEGIN cli-flags */}
|
|
62
62
|
|
|
63
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
64
|
+
|
|
63
65
|
| Flag | 类型 | 说明 |
|
|
64
66
|
|---|---|---|
|
|
65
67
|
| `--agent` | string | experiment 运行不支持该 flag。要换 agent,请在 `experiments/` 下新增或复制一个配置文件。 |
|
|
@@ -70,16 +72,17 @@ npx niceeval exp compare-models weather
|
|
|
70
72
|
| `--budget` | number | 整次运行的预算上限(美元)。 |
|
|
71
73
|
| `--tag` | string | 只运行带有该 tag 的 eval(见 `defineEval` 的 `tags`)。 |
|
|
72
74
|
| `--junit` | string | 额外写一份 JUnit XML 报告到指定路径,供 CI 消费。 |
|
|
75
|
+
| `--json` | string | 额外写一份 JSON 结果(`RunSummary` 原样序列化)到指定路径,供 CI 或下游脚本消费。 |
|
|
73
76
|
| `--out` | string | `view` 命令专用:把结果查看器静态导出到指定目录。 |
|
|
74
77
|
| `--port` | number | `view` 命令专用:指定本地服务器监听端口。 |
|
|
75
78
|
| `--transcript` | boolean | `show` 命令专用:渲染单个 eval 的完整对话与工具调用(证据切面)。 |
|
|
76
79
|
| `--trace` | boolean | `show` 命令专用:渲染单个 eval 的 trace 瀑布文本版(证据切面)。 |
|
|
77
80
|
| `--diff` | boolean | `show` 命令专用:sandbox 里的文件改动摘要;`--diff=<文件路径>` 看单个文件的完整改动(路径必须 `=` 连写)。 |
|
|
78
81
|
| `--history` | boolean | `show` 命令专用:跨 run 时间轴,只列真实执行;与 `--report` 互斥。 |
|
|
79
|
-
| `--experiment` | string | `show` 命令专用:选集只留该实验。 |
|
|
82
|
+
| `--experiment` | string | `show` / `view` 命令专用:选集只留该实验。 |
|
|
80
83
|
| `--attempt` | number | `show` 命令专用:指定详情 / 证据切面看第几次 attempt(与展示一致的 1 计序号)。 |
|
|
81
|
-
| `--run` | string | `show` 命令专用:钉死看某一个结果目录(历史 run 或 `copySnapshots` 产物)。 |
|
|
82
|
-
| `--report` | string | `show` 命令专用:把默认榜单整槽换成你的报告文件(默认导出 `defineReport(...)`)。 |
|
|
84
|
+
| `--run` | string | `show` / `view` 命令专用:钉死看某一个结果目录(历史 run 或 `copySnapshots` 产物)。 |
|
|
85
|
+
| `--report` | string | `show` / `view` 命令专用:把默认榜单整槽换成你的报告文件(默认导出 `defineReport(...)`)。 |
|
|
83
86
|
| `--dry` | boolean | 只打印本次会匹配到的 eval × 运行配置,不实际执行。 |
|
|
84
87
|
| `--quiet` | boolean | 关闭控制台 / live 进度输出(reporter 仍会写 artifacts)。 |
|
|
85
88
|
| `--force` | boolean | 忽略上次运行结果,不跳过已通过的 (experiment, eval) 组合,强制全部重跑。 |
|
|
@@ -106,7 +109,7 @@ npx niceeval exp compare-models
|
|
|
106
109
|
npx niceeval exp compare-models weather-tool
|
|
107
110
|
```
|
|
108
111
|
|
|
109
|
-
运行命名 experiment,用矩阵比较 agents、models 或
|
|
112
|
+
运行命名 experiment,用矩阵比较 agents、models 或 flags。第二个参数开始是 eval ID 前缀过滤。
|
|
110
113
|
|
|
111
114
|
## `view`
|
|
112
115
|
|
|
@@ -80,6 +80,8 @@ export default defineExperiment({
|
|
|
80
80
|
|
|
81
81
|
{/* GENERATED:BEGIN agent-def */}
|
|
82
82
|
|
|
83
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
84
|
+
|
|
83
85
|
### `RemoteAgentDef`
|
|
84
86
|
|
|
85
87
|
#### `name`
|
|
@@ -214,13 +216,13 @@ readonly reasoningEffort?: string;
|
|
|
214
216
|
|
|
215
217
|
模型推理努力程度;归属同 model——实验决定,省略时不覆盖 agent 原生默认。
|
|
216
218
|
|
|
217
|
-
#### `
|
|
219
|
+
#### `flags`
|
|
218
220
|
|
|
219
221
|
```ts
|
|
220
|
-
readonly
|
|
222
|
+
readonly flags: Readonly<Record<string, unknown>>;
|
|
221
223
|
```
|
|
222
224
|
|
|
223
|
-
experiment 的 `
|
|
225
|
+
experiment 的 `flags` 字段原样透传,内容和结构完全由 experiment 作者自定义
|
|
224
226
|
(如 `{ webResearch: true }`、`{ systemPrompt: "..." }`)。adapter 按自己的约定
|
|
225
227
|
读取其中的字段;框架本身不解释、不校验它的内容。命名特意避开 CLI 解析出的
|
|
226
228
|
`flag`(跑法层面的 --timeout/--budget 等),两者是不相关的概念。
|
|
@@ -280,6 +282,8 @@ log(msg: string): void;
|
|
|
280
282
|
|
|
281
283
|
{/* GENERATED:BEGIN sandbox-methods */}
|
|
282
284
|
|
|
285
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
286
|
+
|
|
283
287
|
### `Sandbox`
|
|
284
288
|
|
|
285
289
|
#### `workdir`
|
|
@@ -4,7 +4,7 @@ sidebarTitle: "defineConfig"
|
|
|
4
4
|
description: "defineConfig 参考:judge、reporters、并发、超时和 sandbox 默认值。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
`defineConfig` 从根目录的 `niceeval.config.ts` 默认导出,只放项目级默认值。agent、model、
|
|
7
|
+
`defineConfig` 从根目录的 `niceeval.config.ts` 默认导出,只放项目级默认值。agent、model、flags、runs 和实验预算写在 `experiments/` 下的 `defineExperiment` 文件里。
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
import { defineConfig } from "niceeval";
|
|
@@ -20,6 +20,8 @@ export default defineConfig({
|
|
|
20
20
|
|
|
21
21
|
{/* GENERATED:BEGIN config-fields */}
|
|
22
22
|
|
|
23
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
24
|
+
|
|
23
25
|
#### `name`
|
|
24
26
|
|
|
25
27
|
```ts
|
|
@@ -26,6 +26,8 @@ export default defineEval({
|
|
|
26
26
|
|
|
27
27
|
{/* GENERATED:BEGIN defineeval-options */}
|
|
28
28
|
|
|
29
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
30
|
+
|
|
29
31
|
#### `id`
|
|
30
32
|
|
|
31
33
|
```ts
|
|
@@ -108,6 +110,8 @@ eval 主体:拿到 TestContext,驱动对话 / 沙箱操作并就地断言。
|
|
|
108
110
|
|
|
109
111
|
{/* GENERATED:BEGIN test-context */}
|
|
110
112
|
|
|
113
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
114
|
+
|
|
111
115
|
#### `send`
|
|
112
116
|
|
|
113
117
|
```ts
|
|
@@ -207,13 +211,13 @@ readonly reasoningEffort?: string;
|
|
|
207
211
|
|
|
208
212
|
本次 attempt 的推理努力程度(如 "low"/"medium"/"high",取值由 adapter/模型决定)。
|
|
209
213
|
|
|
210
|
-
#### `
|
|
214
|
+
#### `flags`
|
|
211
215
|
|
|
212
216
|
```ts
|
|
213
|
-
readonly
|
|
217
|
+
readonly flags: Readonly<Record<string, unknown>>;
|
|
214
218
|
```
|
|
215
219
|
|
|
216
|
-
本次 attempt
|
|
220
|
+
本次 attempt 生效的实验 flags(experiment.flags 的只读视图;实验条件,非命令行开关)。
|
|
217
221
|
|
|
218
222
|
#### `log`
|
|
219
223
|
|
|
@@ -440,6 +444,8 @@ t.judge.autoevals.summarizes(sourceText, { on: t.reply }).atLeast(0.7);
|
|
|
440
444
|
|
|
441
445
|
{/* GENERATED:BEGIN turn-handle */}
|
|
442
446
|
|
|
447
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
448
|
+
|
|
443
449
|
#### `events`
|
|
444
450
|
|
|
445
451
|
```ts
|
|
@@ -23,6 +23,8 @@ interface Turn {
|
|
|
23
23
|
|
|
24
24
|
{/* GENERATED:BEGIN usage-fields */}
|
|
25
25
|
|
|
26
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
27
|
+
|
|
26
28
|
#### `inputTokens`
|
|
27
29
|
|
|
28
30
|
```ts
|
|
@@ -81,6 +83,8 @@ token 用量或 OTel span 反推得到)。存在时优先于按价格表(`define
|
|
|
81
83
|
|
|
82
84
|
{/* GENERATED:BEGIN stream-events */}
|
|
83
85
|
|
|
86
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
87
|
+
|
|
84
88
|
#### `message`
|
|
85
89
|
|
|
86
90
|
```ts
|
|
@@ -29,6 +29,8 @@ t.require(turn.status, equals("completed"));
|
|
|
29
29
|
|
|
30
30
|
{/* GENERATED:BEGIN expect-matchers */}
|
|
31
31
|
|
|
32
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
33
|
+
|
|
32
34
|
#### `includes`
|
|
33
35
|
|
|
34
36
|
```ts
|
|
@@ -150,6 +152,8 @@ t.check(t.reply, includes("nice to have").atLeast(0.7));
|
|
|
150
152
|
|
|
151
153
|
{/* GENERATED:BEGIN value-assertion */}
|
|
152
154
|
|
|
155
|
+
{/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
|
|
156
|
+
|
|
153
157
|
#### `name`
|
|
154
158
|
|
|
155
159
|
```ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "niceeval",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Agent-native eval tool — eval agents, services, functions, and coding-agent fixtures",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -100,7 +100,8 @@
|
|
|
100
100
|
"shiki": "^4.3.0",
|
|
101
101
|
"tailwind-merge": "^3.6.0",
|
|
102
102
|
"tailwindcss": "^4.3.1",
|
|
103
|
-
"typescript": "
|
|
103
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
104
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
104
105
|
"vite": "^8.1.0",
|
|
105
106
|
"vitest": "^4.1.9"
|
|
106
107
|
},
|
|
@@ -454,7 +454,7 @@ function fakeCtx(opts: { id?: string } = {}): AgentContext {
|
|
|
454
454
|
return {
|
|
455
455
|
signal: new AbortController().signal,
|
|
456
456
|
model: undefined,
|
|
457
|
-
|
|
457
|
+
flags: {},
|
|
458
458
|
sandbox: undefined as unknown as AgentContext["sandbox"],
|
|
459
459
|
session,
|
|
460
460
|
log: () => {},
|
package/src/agents/ai-sdk.ts
CHANGED
|
@@ -367,7 +367,7 @@ export interface AiSdkGenerateContext<M = unknown> {
|
|
|
367
367
|
/** 实验钉的推理努力程度(ctx.reasoningEffort);省略 → 用应用自己的默认。应用自己决定怎么塞进 providerOptions(如 OpenAI 的 reasoningEffort)。 */
|
|
368
368
|
readonly reasoningEffort?: string;
|
|
369
369
|
readonly signal: AbortSignal;
|
|
370
|
-
readonly
|
|
370
|
+
readonly flags: Readonly<Record<string, unknown>>;
|
|
371
371
|
/**
|
|
372
372
|
* 配了 `tracing`(如 `aiSdkOtel()`)才有:直接放进 generateText / streamText 的
|
|
373
373
|
* `telemetry` 选项。OTel provider、per-attempt 端点绑定和轮末 flush 都由工厂做,
|
|
@@ -505,7 +505,7 @@ export function aiSdkAgent<M = unknown>(options: AiSdkAgentOptions<M>): Agent {
|
|
|
505
505
|
model: ctx.model,
|
|
506
506
|
reasoningEffort: ctx.reasoningEffort,
|
|
507
507
|
signal: ctx.signal,
|
|
508
|
-
|
|
508
|
+
flags: ctx.flags,
|
|
509
509
|
telemetry: otel?.settings,
|
|
510
510
|
});
|
|
511
511
|
} catch (error) {
|
|
@@ -94,7 +94,7 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
|
|
|
94
94
|
const args = ["--print", "--dangerously-skip-permissions"];
|
|
95
95
|
if (ctx.model) args.push("--model", ctx.model);
|
|
96
96
|
if (config?.maxTurns != null) args.push("--max-turns", String(config.maxTurns));
|
|
97
|
-
if (ctx.
|
|
97
|
+
if (ctx.flags.webResearch) args.push("--allowedTools", "WebSearch,WebFetch");
|
|
98
98
|
if (ctx.session.id) args.push("--resume", ctx.session.id);
|
|
99
99
|
args.push(input.text);
|
|
100
100
|
|
package/src/agents/codex.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { Agent, McpServer } from "../types.ts";
|
|
|
9
9
|
//
|
|
10
10
|
// 连接方式:在沙箱里 spawn `codex exec --json`,stdout JSONL → parseCodex → 标准事件流。
|
|
11
11
|
// 配置:鉴权本地(config / env),模型交给实验(ctx.model),推理努力程度经 ctx.reasoningEffort
|
|
12
|
-
// (兼容旧的 ctx.
|
|
12
|
+
// (兼容旧的 ctx.flags.effort),其余参数经 ctx.flags。
|
|
13
13
|
// ───────────────────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
15
|
export interface CodexConfig {
|
|
@@ -44,7 +44,7 @@ export function codexAgent(config?: CodexConfig): Agent {
|
|
|
44
44
|
// model 归属:实验决定(ctx.model);省略时不写 model 行,交给 codex CLI 原生默认,
|
|
45
45
|
// 不在 adapter 里硬编码一个会过期的模型名。
|
|
46
46
|
const modelLine = ctx.model ? `model = "${ctx.model}"\n` : "";
|
|
47
|
-
const effort = ctx.reasoningEffort ?? (ctx.
|
|
47
|
+
const effort = ctx.reasoningEffort ?? (ctx.flags.effort as string | undefined) ?? "medium";
|
|
48
48
|
const base = getBaseUrl();
|
|
49
49
|
|
|
50
50
|
if (base) {
|
package/src/agents/types.ts
CHANGED
|
@@ -155,12 +155,12 @@ export interface AgentContext {
|
|
|
155
155
|
/** 模型推理努力程度;归属同 model——实验决定,省略时不覆盖 agent 原生默认。 */
|
|
156
156
|
readonly reasoningEffort?: string;
|
|
157
157
|
/**
|
|
158
|
-
* experiment 的 `
|
|
158
|
+
* experiment 的 `flags` 字段原样透传,内容和结构完全由 experiment 作者自定义
|
|
159
159
|
* (如 `{ webResearch: true }`、`{ systemPrompt: "..." }`)。adapter 按自己的约定
|
|
160
160
|
* 读取其中的字段;框架本身不解释、不校验它的内容。命名特意避开 CLI 解析出的
|
|
161
161
|
* `flag`(跑法层面的 --timeout/--budget 等),两者是不相关的概念。
|
|
162
162
|
*/
|
|
163
|
-
readonly
|
|
163
|
+
readonly flags: Readonly<Record<string, unknown>>;
|
|
164
164
|
/** 仅沙箱型 agent 有(运行器按 --sandbox 备好)。 */
|
|
165
165
|
readonly sandbox: Sandbox;
|
|
166
166
|
readonly session: AgentSession;
|
|
@@ -73,7 +73,7 @@ function ctx(overrides: Partial<{ model: string }> = {}): AgentContext {
|
|
|
73
73
|
return {
|
|
74
74
|
signal: new AbortController().signal,
|
|
75
75
|
model: overrides.model,
|
|
76
|
-
|
|
76
|
+
flags: {},
|
|
77
77
|
sandbox: undefined as never,
|
|
78
78
|
// 同一个 ctx 重复用 = 同一条会话线(续接,同一个 ctx.session);新造 = 新线。
|
|
79
79
|
session: createAgentSession(),
|
package/src/cli.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { stopAllSandboxes, liveSandboxCount } from "./sandbox/registry.ts";
|
|
|
17
17
|
import { evalLevelStats } from "./shared/outcome.ts";
|
|
18
18
|
import { sandboxRecommendedConcurrency } from "./sandbox/resolve.ts";
|
|
19
19
|
import { Console as ConsoleReporter } from "./runner/reporters/console.ts";
|
|
20
|
-
import { JUnit } from "./runner/reporters/json.ts";
|
|
20
|
+
import { Json, JUnit } from "./runner/reporters/json.ts";
|
|
21
21
|
import { Live as LiveReporter, type LiveRow } from "./runner/reporters/live.ts";
|
|
22
22
|
import { Artifacts as ArtifactsReporter } from "./runner/reporters/artifacts.ts";
|
|
23
23
|
import {
|
|
@@ -60,6 +60,7 @@ interface Flags {
|
|
|
60
60
|
budget?: number;
|
|
61
61
|
tag?: string;
|
|
62
62
|
junit?: string;
|
|
63
|
+
json?: string;
|
|
63
64
|
open?: boolean;
|
|
64
65
|
out?: string;
|
|
65
66
|
port?: number;
|
|
@@ -80,43 +81,69 @@ interface Flags {
|
|
|
80
81
|
|
|
81
82
|
// 表驱动的 flag 定义(node:util parseArgs)。--no-x 显式声明,不依赖 allowNegative(需 Node 20.14+,
|
|
82
83
|
// engines 是 >=18)。未知 flag 由 strict 模式报清晰错误,不再静默吞掉后面的位置参数。
|
|
84
|
+
//
|
|
85
|
+
// 每个 flag 的 JSDoc 就是它在 docs-site/zh/reference/cli.mdx flag 表里的说明,由
|
|
86
|
+
// scripts/generate-reference.ts 提取渲染——改 flag 语义时改这里的注释即可,不用碰生成脚本。
|
|
87
|
+
// 负向 flag(no-x)与正向 flag 合并成一行展示,不需要单独写 JSDoc。
|
|
83
88
|
const FLAG_OPTIONS = {
|
|
89
|
+
/** experiment 运行不支持该 flag。要换 agent,请在 `experiments/` 下新增或复制一个配置文件。 */
|
|
84
90
|
agent: { type: "string" },
|
|
91
|
+
/** experiment 运行不支持该 flag。要换模型,请新增或复制一个 experiment 文件并修改 `model`。 */
|
|
85
92
|
model: { type: "string" },
|
|
93
|
+
/** 每个 eval 运行多少次,常用于 pass@N。 */
|
|
86
94
|
runs: { type: "string" },
|
|
95
|
+
/** 设置同时运行的 eval 数量。 */
|
|
87
96
|
"max-concurrency": { type: "string" },
|
|
97
|
+
/** 单个 attempt 的超时时间,单位毫秒。 */
|
|
88
98
|
timeout: { type: "string" },
|
|
99
|
+
/** 整次运行的预算上限(美元)。 */
|
|
89
100
|
budget: { type: "string" },
|
|
101
|
+
/** 只运行带有该 tag 的 eval(见 `defineEval` 的 `tags`)。 */
|
|
90
102
|
tag: { type: "string" },
|
|
103
|
+
/** 额外写一份 JUnit XML 报告到指定路径,供 CI 消费。 */
|
|
91
104
|
junit: { type: "string" },
|
|
105
|
+
/** 额外写一份 JSON 结果(`RunSummary` 原样序列化)到指定路径,供 CI 或下游脚本消费。 */
|
|
106
|
+
json: { type: "string" },
|
|
107
|
+
/** `view` 命令专用:把结果查看器静态导出到指定目录。 */
|
|
92
108
|
out: { type: "string" },
|
|
109
|
+
/** `view` 命令专用:指定本地服务器监听端口。 */
|
|
93
110
|
port: { type: "string" },
|
|
94
|
-
// --sandbox 已移除(sandbox 归 config/experiment);留着解析是为了给出迁移提示而非「未知 flag」。
|
|
95
|
-
sandbox: { type: "string" },
|
|
96
|
-
// --watch / --json:文档曾接受、尚未实现的 flag。容忍(no-op)而不是硬报「未知 flag」,
|
|
97
|
-
// 免得照旧文档写的脚本直接崩;真正实现前不接任何行为。
|
|
98
|
-
watch: { type: "boolean" },
|
|
99
|
-
json: { type: "boolean" },
|
|
100
111
|
// show 的证据切面 / 时间轴 / 报告装载(docs-site/zh/guides/viewing-results.mdx)。
|
|
112
|
+
/** `show` 命令专用:渲染单个 eval 的完整对话与工具调用(证据切面)。 */
|
|
101
113
|
transcript: { type: "boolean" },
|
|
114
|
+
/** `show` 命令专用:渲染单个 eval 的 trace 瀑布文本版(证据切面)。 */
|
|
102
115
|
trace: { type: "boolean" },
|
|
103
116
|
// --diff 是布尔;--diff=<路径> 在 parseArgs 前预扫成 diffPath(路径必须 = 连写,
|
|
104
117
|
// 空格形式的下一个 token 仍是位置参数 = eval id 前缀,与文档一致)。
|
|
118
|
+
/** `show` 命令专用:sandbox 里的文件改动摘要;`--diff=<文件路径>` 看单个文件的完整改动(路径必须 `=` 连写)。 */
|
|
105
119
|
diff: { type: "boolean" },
|
|
120
|
+
/** `show` 命令专用:跨 run 时间轴,只列真实执行;与 `--report` 互斥。 */
|
|
106
121
|
history: { type: "boolean" },
|
|
122
|
+
/** `show` / `view` 命令专用:选集只留该实验。 */
|
|
107
123
|
experiment: { type: "string" },
|
|
124
|
+
/** `show` 命令专用:指定详情 / 证据切面看第几次 attempt(与展示一致的 1 计序号)。 */
|
|
108
125
|
attempt: { type: "string" },
|
|
126
|
+
/** `show` / `view` 命令专用:钉死看某一个结果目录(历史 run 或 `copySnapshots` 产物)。 */
|
|
109
127
|
run: { type: "string" },
|
|
128
|
+
/** `show` / `view` 命令专用:把默认榜单整槽换成你的报告文件(默认导出 `defineReport(...)`)。 */
|
|
110
129
|
report: { type: "string" },
|
|
130
|
+
/** 只打印本次会匹配到的 eval × 运行配置,不实际执行。 */
|
|
111
131
|
dry: { type: "boolean" },
|
|
132
|
+
/** 关闭控制台 / live 进度输出(reporter 仍会写 artifacts)。 */
|
|
112
133
|
quiet: { type: "boolean" },
|
|
134
|
+
/** 忽略上次运行结果,不跳过已通过的 (experiment, eval) 组合,强制全部重跑。 */
|
|
113
135
|
force: { type: "boolean" },
|
|
136
|
+
/** CI 中推荐使用:让软阈值(`soft`)失败也计入整条 eval 的 outcome。 */
|
|
114
137
|
strict: { type: "boolean" },
|
|
138
|
+
/** 某个 eval 的一次 attempt 通过后,停止该 eval 剩余的 attempts。 */
|
|
115
139
|
"early-exit": { type: "boolean" },
|
|
116
140
|
"no-early-exit": { type: "boolean" },
|
|
141
|
+
/** `view` 命令专用:启动后自动打开浏览器(默认行为)。 */
|
|
117
142
|
open: { type: "boolean" },
|
|
118
143
|
"no-open": { type: "boolean" },
|
|
144
|
+
/** 打印用法说明并退出。 */
|
|
119
145
|
help: { type: "boolean", short: "h" },
|
|
146
|
+
/** 打印 niceeval 的版本号并退出。 */
|
|
120
147
|
version: { type: "boolean", short: "v" },
|
|
121
148
|
} as const;
|
|
122
149
|
|
|
@@ -155,11 +182,6 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
|
|
|
155
182
|
process.exit(1);
|
|
156
183
|
}
|
|
157
184
|
|
|
158
|
-
if (values.sandbox !== undefined) {
|
|
159
|
-
process.stderr.write(t("cli.sandboxFlagRemoved"));
|
|
160
|
-
process.exit(1);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
185
|
// 第一个位置参数若是已知命令,则为命令;其余是 eval id 前缀 / view 输入。
|
|
164
186
|
const commands = new Set(["exp", "show", "list", "view", "clean", "init", "watch", "run"]);
|
|
165
187
|
let command = "run";
|
|
@@ -178,6 +200,7 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
|
|
|
178
200
|
budget: numberFlag("budget", values.budget as string | undefined),
|
|
179
201
|
tag: values.tag as string | undefined,
|
|
180
202
|
junit: values.junit as string | undefined,
|
|
203
|
+
json: values.json as string | undefined,
|
|
181
204
|
out: values.out as string | undefined,
|
|
182
205
|
port: numberFlag("port", values.port as string | undefined),
|
|
183
206
|
dry: values.dry === true,
|
|
@@ -449,7 +472,7 @@ async function main(): Promise<void> {
|
|
|
449
472
|
agent: exp.agent,
|
|
450
473
|
model: exp.model,
|
|
451
474
|
reasoningEffort: exp.reasoningEffort,
|
|
452
|
-
|
|
475
|
+
flags: exp.flags ?? {},
|
|
453
476
|
runs: flags.runs ?? envNumber("NICEEVAL_RUNS") ?? exp.runs ?? 1,
|
|
454
477
|
earlyExit: flags.earlyExit ?? exp.earlyExit ?? true,
|
|
455
478
|
sandbox: exp.sandbox ?? config.sandbox,
|
|
@@ -465,7 +488,7 @@ async function main(): Promise<void> {
|
|
|
465
488
|
}
|
|
466
489
|
} else {
|
|
467
490
|
// 裸 run / `niceeval <eval>` 不再执行。运行配置必须来自 experiments/,
|
|
468
|
-
// 这样 agent/model/
|
|
491
|
+
// 这样 agent/model/flags/runs/budget 与结果聚合都有可签入的身份。
|
|
469
492
|
const experiments = await discoverExperiments(cwd);
|
|
470
493
|
const asExp = experiments.filter((e) =>
|
|
471
494
|
positionals.some((p) => e.group === p || e.id === p || e.id.startsWith(p + "/")),
|
|
@@ -526,6 +549,7 @@ async function main(): Promise<void> {
|
|
|
526
549
|
const artifacts = ArtifactsReporter();
|
|
527
550
|
reporters.push(artifacts);
|
|
528
551
|
if (flags.junit) reporters.push(JUnit(flags.junit));
|
|
552
|
+
if (flags.json) reporters.push(Json(flags.json));
|
|
529
553
|
reporters.push(...(config.reporters ?? []));
|
|
530
554
|
|
|
531
555
|
// Ctrl+C / kill 的三级响应,核心目标:任何情况下都不留下孤儿沙箱。
|
package/src/context/context.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface ContextDeps {
|
|
|
61
61
|
sandbox: Sandbox;
|
|
62
62
|
model?: string;
|
|
63
63
|
reasoningEffort?: string;
|
|
64
|
-
|
|
64
|
+
flags: Record<string, unknown>;
|
|
65
65
|
signal: AbortSignal;
|
|
66
66
|
log(msg: string): void;
|
|
67
67
|
judge: JudgeConfig | undefined;
|
|
@@ -92,7 +92,7 @@ export function createEvalContext(deps: ContextDeps): { context: TestContext; st
|
|
|
92
92
|
sandbox: deps.sandbox,
|
|
93
93
|
model: deps.model,
|
|
94
94
|
reasoningEffort: deps.reasoningEffort,
|
|
95
|
-
|
|
95
|
+
flags: deps.flags,
|
|
96
96
|
signal: deps.signal,
|
|
97
97
|
log: deps.log,
|
|
98
98
|
telemetry: deps.telemetry,
|
|
@@ -284,7 +284,7 @@ export function createEvalContext(deps: ContextDeps): { context: TestContext; st
|
|
|
284
284
|
signal: deps.signal,
|
|
285
285
|
model: deps.model,
|
|
286
286
|
reasoningEffort: deps.reasoningEffort,
|
|
287
|
-
|
|
287
|
+
flags: deps.flags,
|
|
288
288
|
log: deps.log,
|
|
289
289
|
skip: (reason: string) => {
|
|
290
290
|
if (reason.trim().length === 0) throw new Error(t("context.skipEmpty"));
|
package/src/context/session.ts
CHANGED
|
@@ -88,7 +88,7 @@ export interface SessionDeps {
|
|
|
88
88
|
sandbox: Sandbox;
|
|
89
89
|
model?: string;
|
|
90
90
|
reasoningEffort?: string;
|
|
91
|
-
|
|
91
|
+
flags: Record<string, unknown>;
|
|
92
92
|
signal: AbortSignal;
|
|
93
93
|
log(msg: string): void;
|
|
94
94
|
/** tracing agent 的 OTLP 端点(经 send ctx 透给 adapter,用于注入导出 env)。 */
|
|
@@ -138,7 +138,7 @@ export class SessionManager {
|
|
|
138
138
|
signal: this.deps.signal,
|
|
139
139
|
model: this.deps.model,
|
|
140
140
|
reasoningEffort: this.deps.reasoningEffort,
|
|
141
|
-
|
|
141
|
+
flags: this.deps.flags,
|
|
142
142
|
sandbox: this.deps.sandbox,
|
|
143
143
|
session,
|
|
144
144
|
telemetry: this.deps.telemetry,
|
package/src/context/types.ts
CHANGED
|
@@ -274,8 +274,8 @@ export interface TestContext {
|
|
|
274
274
|
readonly model?: string;
|
|
275
275
|
/** 本次 attempt 的推理努力程度(如 "low"/"medium"/"high",取值由 adapter/模型决定)。 */
|
|
276
276
|
readonly reasoningEffort?: string;
|
|
277
|
-
/** 本次 attempt
|
|
278
|
-
readonly
|
|
277
|
+
/** 本次 attempt 生效的实验 flags(experiment.flags 的只读视图;实验条件,非命令行开关)。 */
|
|
278
|
+
readonly flags: Readonly<Record<string, unknown>>;
|
|
279
279
|
/** 打一行调试日志;有 live 进度回调时走该回调,否则落到 stderr,不出现在最终结果里。 */
|
|
280
280
|
log(msg: string): void;
|
|
281
281
|
/** 立即中止本 eval 并标记为 skipped(outcome / EvalResult.skipReason),reason 不能为空。 */
|
package/src/i18n/en.ts
CHANGED
|
@@ -59,7 +59,7 @@ export const en = {
|
|
|
59
59
|
"Flags:\n" +
|
|
60
60
|
" --runs n --max-concurrency n --timeout ms --budget usd --tag t\n" +
|
|
61
61
|
" --early-exit / --no-early-exit --strict --force --dry --quiet\n" +
|
|
62
|
-
" --junit path --out dir --port n --open / --no-open -h, --help -v, --version\n\n" +
|
|
62
|
+
" --junit path --json path --out dir --port n --open / --no-open -h, --help -v, --version\n\n" +
|
|
63
63
|
"Positional args only select which evals to run (id prefixes); which agent and\n" +
|
|
64
64
|
"how to run come from experiments/ + flags. Env overrides (flag > env > config):\n" +
|
|
65
65
|
" NICEEVAL_RUNS NICEEVAL_MAX_CONCURRENCY NICEEVAL_TIMEOUT NICEEVAL_BUDGET\n",
|
|
@@ -94,10 +94,10 @@ export const en = {
|
|
|
94
94
|
" Docs: node_modules/niceeval/docs-site/zh/guides/write-experiment.mdx\n",
|
|
95
95
|
"cli.run.experimentRequiredHint": "Hint: \"{{pattern}}\" is an experiment{{kind}}; you probably meant: niceeval exp {{pattern}}\n",
|
|
96
96
|
"cli.run.experimentRequiredKnown": "Discovered experiments: {{experiments}}\n",
|
|
97
|
-
"cli.sandboxFlagRemoved": "`--sandbox` is not a CLI flag. Set `sandbox` in the experiment (or `niceeval.config.ts` as a project-wide fallback) to dockerSandbox() / vercelSandbox() / e2bSandbox() (import from \"niceeval/sandbox\").\n",
|
|
98
97
|
"cli.unimplemented": "Command \"{{command}}\" is not implemented yet (MVP).\n",
|
|
99
98
|
"cli.view.exportedDir": "Exported static report site: {{out}} (serve the whole directory with any static host; opening index.html via file:// cannot fetch artifacts)\n",
|
|
100
99
|
"cli.view.incompatible": "{{dir}}: written by niceeval {{producer}} (schemaVersion {{schemaVersion}}); this CLI reads schemaVersion {{supported}}.\nRun `{{command}}` to view it.\n",
|
|
100
|
+
"cli.view.noResults": "No results found under {{root}}. Run `niceeval exp` first, then `niceeval view`.\n",
|
|
101
101
|
"cli.view.incompatibleForeign": "{{dir}}: written by {{name}} {{version}} (schemaVersion {{schemaVersion}}); this CLI reads schemaVersion {{supported}}.\nOpen this report with the tool that produced it.\n",
|
|
102
102
|
"cli.view.url": "niceeval view: {{url}}\n",
|
|
103
103
|
"context.capabilityMissing":
|
package/src/i18n/zh-CN.ts
CHANGED
|
@@ -55,7 +55,7 @@ export const zhCN = {
|
|
|
55
55
|
"标志:\n" +
|
|
56
56
|
" --runs n --max-concurrency n --timeout ms --budget usd --tag t\n" +
|
|
57
57
|
" --early-exit / --no-early-exit --strict --force --dry --quiet\n" +
|
|
58
|
-
" --junit path --out dir --port n --open / --no-open -h, --help -v, --version\n\n" +
|
|
58
|
+
" --junit path --json path --out dir --port n --open / --no-open -h, --help -v, --version\n\n" +
|
|
59
59
|
"位置参数只选「跑哪些 eval」(id 前缀);对着哪个 agent、怎么跑来自 experiments/ 与\n" +
|
|
60
60
|
"标志。环境变量覆盖(标志 > 环境变量 > config):\n" +
|
|
61
61
|
" NICEEVAL_RUNS NICEEVAL_MAX_CONCURRENCY NICEEVAL_TIMEOUT NICEEVAL_BUDGET\n",
|
|
@@ -91,10 +91,10 @@ export const zhCN = {
|
|
|
91
91
|
" 文档:node_modules/niceeval/docs-site/zh/guides/write-experiment.mdx\n",
|
|
92
92
|
"cli.run.experimentRequiredHint": "提示:\"{{pattern}}\" 是实验{{kind}},你大概想跑:niceeval exp {{pattern}}\n",
|
|
93
93
|
"cli.run.experimentRequiredKnown": "已发现实验:{{experiments}}\n",
|
|
94
|
-
"cli.sandboxFlagRemoved": "`--sandbox` 不是 CLI flag。请在 experiment(或 niceeval.config.ts 做全项目兜底)里把 sandbox 设成 dockerSandbox() / vercelSandbox() / e2bSandbox()(从 \"niceeval/sandbox\" 导入)。\n",
|
|
95
94
|
"cli.unimplemented": "命令 \"{{command}}\" 暂未实现(MVP)。\n",
|
|
96
95
|
"cli.view.exportedDir": "已导出静态查看站:{{out}}(整个目录可直接静态托管;本地打开 {{out}}/index.html 需经 http 服务,file:// 下工件 fetch 不可用)\n",
|
|
97
96
|
"cli.view.incompatible": "{{dir}}: 由 niceeval {{producer}} 写入(schemaVersion {{schemaVersion}}),当前 CLI 只读 schemaVersion {{supported}}。\n运行 `{{command}}` 查看这份报告。\n",
|
|
97
|
+
"cli.view.noResults": "{{root}} 下没有结果。先 `niceeval exp` 跑一轮,再 `niceeval view`。\n",
|
|
98
98
|
"cli.view.incompatibleForeign": "{{dir}}: 由 {{name}} {{version}} 写入(schemaVersion {{schemaVersion}}),当前 CLI 只读 schemaVersion {{supported}}。\n请用写出它的那个工具查看这份报告。\n",
|
|
99
99
|
"cli.view.url": "niceeval view: {{url}}\n",
|
|
100
100
|
"context.capabilityMissing":
|
package/src/report/aggregate.ts
CHANGED
|
@@ -18,15 +18,15 @@ import type {
|
|
|
18
18
|
Metric,
|
|
19
19
|
MetricCell,
|
|
20
20
|
MetricColumn,
|
|
21
|
-
|
|
21
|
+
FlagRef,
|
|
22
22
|
} from "./types.ts";
|
|
23
23
|
import { formatMetricValue } from "./format.ts";
|
|
24
24
|
|
|
25
25
|
// 复合键分隔符:NUL 不会出现在 eval id / experimentId / ISO 时间里,拼接键不会串味
|
|
26
26
|
const KEY_SEP = "\u0000";
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
export const
|
|
28
|
+
/** flag 未声明时的组名:不猜,如实归一组。 */
|
|
29
|
+
export const FLAG_UNSET = "(unset)";
|
|
30
30
|
|
|
31
31
|
/** 计算函数的第一参:选集(warnings 随行)或手工挑的快照数组(没有挑选过程,自然无警告)。 */
|
|
32
32
|
export type SnapshotsInput = Selection | Snapshot[];
|
|
@@ -105,33 +105,33 @@ export function evalGroupOf(id: string): string {
|
|
|
105
105
|
return slash === -1 ? id : id.slice(0, slash);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function
|
|
109
|
-
return typeof dimension === "object" && "kind" in dimension && dimension.kind === "
|
|
108
|
+
function isFlagRef(dimension: DimensionInput): dimension is FlagRef {
|
|
109
|
+
return typeof dimension === "object" && "kind" in dimension && dimension.kind === "flag";
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/** experiment 声明的
|
|
113
|
-
function
|
|
114
|
-
return attempt.result.experiment?.
|
|
112
|
+
/** experiment 声明的 flags(经 runner 原样透传进持久化字段 ExperimentRunInfo.flags)。 */
|
|
113
|
+
function flagsOf(attempt: AttemptHandle): Record<string, unknown> | undefined {
|
|
114
|
+
return attempt.result.experiment?.flags;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/**
|
|
118
|
-
export function
|
|
119
|
-
const value =
|
|
120
|
-
if (value === undefined) return
|
|
117
|
+
/** flag 声明值 → 组标签:label 函数优先,其余 String();未声明 → FLAG_UNSET。 */
|
|
118
|
+
export function flagGroupKey(ref: FlagRef, item: Item): string {
|
|
119
|
+
const value = flagsOf(item.attempt)?.[ref.name];
|
|
120
|
+
if (value === undefined) return FLAG_UNSET;
|
|
121
121
|
// 持久化字段是 Record<string, unknown>;声明侧的合法值就是这三种标量
|
|
122
122
|
if (typeof ref.label === "function") return ref.label(value as string | number | boolean);
|
|
123
123
|
return String(value);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
/**
|
|
127
|
-
export function
|
|
128
|
-
const value =
|
|
126
|
+
/** flag 作轴:要求数值;未声明或非数值 → null(点不画,注脚报数)。 */
|
|
127
|
+
export function flagAxisValue(ref: FlagRef, item: Item): number | null {
|
|
128
|
+
const value = flagsOf(item.attempt)?.[ref.name];
|
|
129
129
|
return typeof value === "number" ? value : null;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
export function dimensionKey(dimension: DimensionInput, item: Item): string {
|
|
133
133
|
if (typeof dimension !== "string") {
|
|
134
|
-
if (
|
|
134
|
+
if (isFlagRef(dimension)) return flagGroupKey(dimension, item);
|
|
135
135
|
return dimension.of(item.attempt);
|
|
136
136
|
}
|
|
137
137
|
const result = item.attempt.result;
|