@catheadowl/dsh-eval 0.1.0 → 0.2.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.
- package/README.i18n.yaml +6 -0
- package/README.md +59 -52
- package/README.zh.md +132 -0
- package/bin/dsh-eval.mjs +5 -5
- package/bin/dsh-review.mjs +1 -1
- package/docs/README.md +5 -2
- package/docs/disablerows.md +1 -1
- package/docs/experimental.md +35 -0
- package/docs/host-wiring.md +2 -2
- package/docs/matchers.md +15 -1
- package/docs/review.md +3 -3
- package/docs/rowconfig.md +39 -0
- package/docs/runner-api.md +42 -0
- package/package.json +13 -3
- package/src/adapters/dsh/review.mjs +157 -192
- package/src/cli.mjs +8 -8
- package/src/config.mjs +1 -1
- package/src/discovery.mjs +55 -4
- package/src/experimental.mjs +39 -0
- package/src/index.mjs +34 -49
- package/src/overlay.mjs +96 -0
- package/src/report.mjs +1 -1
- package/src/runner.mjs +216 -373
- package/src/sandbox.mjs +201 -0
- package/src/adapters/dsh/index.mjs +0 -7
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual README pairing record (adapted from deepseek-harness docs/i18n): the sha256 of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
|
+
# editing either side, bring the other along and re-record with:
|
|
4
|
+
# node scripts/verify-readme-i18n.mjs --write
|
|
5
|
+
README.md: 063d8659bc607bbefcc4a09aefa3c5b9280628ae249e150e41952a6b9f83239e
|
|
6
|
+
README.zh.md: 4938e021edcf5fb49232c96b780ed121d51e00abe4a89b3583a17ecbbb2f14b1
|
package/README.md
CHANGED
|
@@ -1,39 +1,41 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: '@catheadowl/dsh-eval — dsh-native agent evaluation layer for plugin authors
|
|
2
|
+
description: '@catheadowl/dsh-eval — a dsh-native agent evaluation layer for plugin authors: behavior cases run against real headless dsh traces, review experiments test whether fresh models understand plugin outputs'
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# @catheadowl/dsh-eval
|
|
6
6
|
|
|
7
|
+
English | [中文](README.zh.md)
|
|
8
|
+
|
|
7
9
|
**A dsh-native agent evaluation layer for plugin authors**: behavior cases run against real headless dsh traces, while review experiments test whether fresh models understand plugin outputs.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
It evaluates the **assembled agent harness** (the graph a plugin + profile + patch + tool registry form inside a real dsh headless run), not isolated functions; verdicts come from dsh-native session-trace projections and matchers (contract assertions), not metric scores. It is not a general agent-eval platform (no dashboard / dataset hosting / metric catalog, no benchmark ranking) and not a DeepEval / OpenAI Evals replacement — those projects proved the problem space; this package picks the dsh-native vertical solution.
|
|
10
12
|
|
|
11
|
-
>
|
|
13
|
+
> Documentation is Chinese-first; deep contracts live in [docs/](docs/README.md) (matchers / boundary contracts / review / report structure / host wiring / known issues).
|
|
12
14
|
|
|
13
|
-
##
|
|
15
|
+
## Why it exists
|
|
14
16
|
|
|
15
|
-
|
|
|
17
|
+
| Layer | Question | Verdict | Execution |
|
|
16
18
|
|---|---|---|---|
|
|
17
|
-
|
|
|
18
|
-
| behavior real |
|
|
19
|
-
| behavior mock |
|
|
20
|
-
| comprehension review |
|
|
19
|
+
| unit / shape test | are deterministic fields and values correct | automatic | the plugin's own `node:test` |
|
|
20
|
+
| behavior real | does natural-language intent pick the right tool | trace matcher | dsh + real model |
|
|
21
|
+
| behavior mock | is the tool pipeline and write round-trip stable | trace matcher + workspace inspect | dsh + scripted mock LLM |
|
|
22
|
+
| comprehension review | can a fresh model understand the output and the next step | manual rubric, converged over runs | abstract review experiment + replaceable executor |
|
|
21
23
|
|
|
22
|
-
dsh
|
|
24
|
+
A dsh plugin is correct when the assembled graph really wires tools, steers, prompts, and gates together — plugin unit tests cover only part of that, and "is the output understandable" is not a string regression at all. This package turns both layers into replayable evidence instead of manual trial runs.
|
|
23
25
|
|
|
24
26
|
```text
|
|
25
27
|
plugin-owned experiment shared framework
|
|
26
28
|
fixtures + prompt + rubric + observe ──► experiment/review.mjs
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
│ task
|
|
30
|
+
▼
|
|
31
|
+
adapters/dsh/review.mjs ──► dsh headless
|
|
30
32
|
|
|
31
33
|
behavior *.eval.mjs ───────────────────► dsh behavior runner (trace + mock)
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
- `src/experiment/`
|
|
35
|
-
- `src/adapters/dsh/`
|
|
36
|
-
-
|
|
36
|
+
- `src/experiment/` is the model- and runtime-agnostic experiment layer: blind review, live observation, byte-identical evidence across reviewers. It does not import dsh.
|
|
37
|
+
- `src/adapters/dsh/` is the landing layer: hands the abstract task to an isolated dsh headless run.
|
|
38
|
+
- Your `eval/` keeps only domain fixtures, projections/observe, prompts, rubrics, and cases — no runner duplication.
|
|
37
39
|
|
|
38
40
|
## Install
|
|
39
41
|
|
|
@@ -41,15 +43,15 @@ behavior *.eval.mjs ───────────────────►
|
|
|
41
43
|
npm i -D @catheadowl/dsh-eval
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
**Requirements
|
|
46
|
+
**Requirements** (wiring details and failure self-diagnostics in [docs/host-wiring.md](docs/host-wiring.md)):
|
|
45
47
|
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- peer
|
|
48
|
+
- a built deepseek-harness checkout (`apps/cli/lib/bin.js`);
|
|
49
|
+
- the plugin under test installed into a dsh profile;
|
|
50
|
+
- the peer dependency `@deepseek-ai/dsh-llm` must be wired manually (npm auto-installs an incompatible antique version; replace it with a link pointing at the host checkout).
|
|
49
51
|
|
|
50
52
|
## Quickstart
|
|
51
53
|
|
|
52
|
-
`<plugin>/eval/behavior/mock/smoke.eval.mjs
|
|
54
|
+
`<plugin>/eval/behavior/mock/smoke.eval.mjs`:
|
|
53
55
|
|
|
54
56
|
```js
|
|
55
57
|
import { firstTool, toolCalled, toolCallStep, textStep } from '@catheadowl/dsh-eval'
|
|
@@ -57,8 +59,8 @@ import { firstTool, toolCalled, toolCallStep, textStep } from '@catheadowl/dsh-e
|
|
|
57
59
|
export default {
|
|
58
60
|
id: 'my-first-case',
|
|
59
61
|
mode: 'mock',
|
|
60
|
-
task: '
|
|
61
|
-
async prepare(workspace) { /*
|
|
62
|
+
task: 'rename guide.md to intro.md',
|
|
63
|
+
async prepare(workspace) { /* seed fixture files */ },
|
|
62
64
|
script: { steps: [toolCallStep('md_rename', { oldPath: 'guide.md', newPath: 'intro.md' }), textStep('done')] },
|
|
63
65
|
expect: [toolCalled('md_rename')],
|
|
64
66
|
}
|
|
@@ -66,60 +68,65 @@ export default {
|
|
|
66
68
|
|
|
67
69
|
```bash
|
|
68
70
|
dsh-eval run --mode mock eval/behavior/mock
|
|
69
|
-
dsh-review --dry-run eval/comprehension # review
|
|
71
|
+
dsh-review --dry-run eval/comprehension # model-free dry run of the review layer
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
The command needs to know which dsh profile to use: pass `--profile <name>` explicitly, or drop a `dsh-eval.config.mjs` at the package root (see "Unified config" below).
|
|
75
|
+
|
|
76
|
+
Real runs use `dsh-eval run --profile <p> --repo <harness checkout> <case path>`; all flags (`--mode/--keep-artifacts/--fail-on-skip/--format/--report`) are documented in [docs/report.md](docs/report.md). Real cases auto-skip without credentials (dsh resolves credentials itself); mock and dry runs need no credentials.
|
|
73
77
|
|
|
74
|
-
##
|
|
78
|
+
## Canonical layout
|
|
75
79
|
|
|
76
80
|
```text
|
|
77
81
|
<plugin>/eval/
|
|
78
|
-
.gitignore # .runs
|
|
82
|
+
.gitignore # .runs/ (no path prefix)
|
|
79
83
|
README.md
|
|
80
|
-
behavior/ #
|
|
84
|
+
behavior/ # optional
|
|
81
85
|
real/*.eval.mjs
|
|
82
86
|
mock/*.eval.mjs
|
|
83
87
|
_fixtures/
|
|
84
|
-
comprehension/ #
|
|
88
|
+
comprehension/ # optional
|
|
85
89
|
<name>.review.mjs
|
|
86
90
|
fixtures.json
|
|
87
91
|
prompt.md
|
|
88
92
|
rubric.md
|
|
89
93
|
```
|
|
90
94
|
|
|
91
|
-
##
|
|
95
|
+
## Unified config dsh-eval.config.mjs
|
|
92
96
|
|
|
93
|
-
|
|
97
|
+
Drop one at the consumer package root; both CLIs walk upward from the working directory, and flags always override config:
|
|
94
98
|
|
|
95
99
|
```js
|
|
96
100
|
export default {
|
|
97
101
|
profile: 'headless', // dsh profile
|
|
98
|
-
repo: '../../deepseek-harness', //
|
|
99
|
-
mode: 'mock', // behavior CLI
|
|
100
|
-
failOnSkip: false, // behavior CI
|
|
101
|
-
report: 'eval-report.json', // --report
|
|
102
|
-
disableRows: ['gates'], //
|
|
103
|
-
//
|
|
102
|
+
repo: '../../deepseek-harness', // relative, anchored at the config file's directory
|
|
103
|
+
mode: 'mock', // behavior CLI's --mode default (review has none)
|
|
104
|
+
failOnSkip: false, // behavior CI gate default
|
|
105
|
+
report: 'eval-report.json', // --report default (anchored at the config dir)
|
|
106
|
+
disableRows: ['gates'], // plugin rows disabled by default; case-level declarations win
|
|
107
|
+
// (explicit [] = all enabled, for gate-interaction cases)
|
|
104
108
|
}
|
|
105
109
|
```
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
Unknown keys fail loudly (typos never degrade silently). The `disableRows` semantics and the turn-close gate boundary contract are in [docs/disablerows.md](docs/disablerows.md).
|
|
108
112
|
|
|
109
113
|
## Docs
|
|
110
114
|
|
|
111
|
-
|
|
|
115
|
+
| Doc | Topic |
|
|
112
116
|
|---|---|
|
|
113
|
-
| [host-wiring](docs/host-wiring.md) | peer
|
|
114
|
-
| [review](docs/review.md) | comprehension review
|
|
115
|
-
| [matchers](docs/matchers.md) | trace
|
|
116
|
-
| [disablerows](docs/disablerows.md) | `disableRows`
|
|
117
|
-
| [
|
|
118
|
-
| [
|
|
119
|
-
| [
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
| [host-wiring](docs/host-wiring.md) | peer wiring (incl. the npm antique-peer trap), building the CLI, profiles, credentials, spawn requirements |
|
|
118
|
+
| [review](docs/review.md) | comprehension review: experiment definition, sterile profile, artifacts, the six review rules |
|
|
119
|
+
| [matchers](docs/matchers.md) | the full trace-matcher and mock-helper set (tool face / text face / model-visible face) |
|
|
120
|
+
| [disablerows](docs/disablerows.md) | `disableRows` and the turn-close gate boundary contract |
|
|
121
|
+
| [rowconfig](docs/rowconfig.md) | the `rowConfig` per-row config override contract (whole-segment replacement, restate needed keys) |
|
|
122
|
+
| [intent-cases](docs/intent-cases.md) | real intent-case spec: when to write one, assertion face, guards, CI semantics |
|
|
123
|
+
| [report](docs/report.md) | machine-readable report structure (`--format json` / `--report`) |
|
|
124
|
+
| [known-issues](docs/known-issues.md) | known issues and workarounds (e.g. REQUEST_EXTENSION in staged homes) |
|
|
125
|
+
| [runner-api](docs/runner-api.md) | programmatic runner API: `runEvalCase` options contract, EvalRunResult fields, crossing tiers for `cliPath` |
|
|
126
|
+
| [experimental](docs/experimental.md) | `experimental` subpath symbol list (escape hatch, no compatibility promise) |
|
|
127
|
+
|
|
128
|
+
## Runtime guarantees
|
|
129
|
+
|
|
130
|
+
The runner uses `try/finally` so temp directories and links are cleaned up on every path (`prepare` throwing, mock validation failure, spawn errors) — the real profile store is never polluted. The behavior and review CLIs share directory scanning (skipping `.runs` and `node_modules`); the behavior CLI validates case shapes and detects cross-file duplicate ids at load time, failing as early as possible.
|
|
131
|
+
|
|
132
|
+
License: MIT. The framework's own tests and release self-checks are carried by the repository CI and do not ship with the package.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: '@catheadowl/dsh-eval 中文主页——dsh-native agent 评测层:behavior case 跑真实 headless dsh trace,review experiment 测 fresh model 能否理解插件输出'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# @catheadowl/dsh-eval
|
|
6
|
+
|
|
7
|
+
[English](README.md) | 中文
|
|
8
|
+
|
|
9
|
+
**面向插件作者的 dsh-native agent 评测层**:behavior case 跑真实 headless dsh trace,review experiment 测 fresh model 能否理解插件输出。
|
|
10
|
+
|
|
11
|
+
它评测的是**装配后的 agent harness**(插件 + profile + patch + 工具注册表在真实 dsh headless 里接成的那张图),不是孤立函数;判定走 dsh 原生的 session trace 投影与 matcher(契约断言),不是 metric 分数。它不是通用 agent eval 平台(无 dashboard / dataset hosting / metric catalog,也不做 benchmark 排名),也不是 DeepEval / OpenAI Evals 的替代品——那些项目证明了这个问题空间成立,本包选择 dsh-native 的垂直解法。
|
|
12
|
+
|
|
13
|
+
> 文档以中文为主;深度契约在 [docs/](docs/README.md)(matchers / 边界契约 / review / 报告结构 / 宿主接线 / 已知问题)。
|
|
14
|
+
|
|
15
|
+
## 为什么需要它
|
|
16
|
+
|
|
17
|
+
| 类型 | 问题 | 判定 | 执行 |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| 单元/shape test | 确定性字段和值是否正确 | 自动 | plugin 自己的 `node:test` |
|
|
20
|
+
| behavior real | 自然语言意图是否选到正确工具 | trace matcher | dsh + 真实模型 |
|
|
21
|
+
| behavior mock | 工具管线与写入 round-trip 是否稳定 | trace matcher + workspace inspect | dsh + 脚本化 mock LLM |
|
|
22
|
+
| comprehension review | 一个 fresh model 能否从输出理解含义和下一步 | 人工对照 rubric,多次收敛 | 抽象 review experiment + 可替换 executor |
|
|
23
|
+
|
|
24
|
+
dsh 插件的正确性来自「装配出的图是否真的把工具、steer、prompt、gate 接到一起」——这类问题插件自己的单测只能覆盖一部分;而「输出能否被理解」根本不是字符串回归。本包把这两层从手动试跑变成可复跑证据。
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
plugin-owned experiment shared framework
|
|
28
|
+
fixtures + prompt + rubric + observe ──► experiment/review.mjs
|
|
29
|
+
│ task
|
|
30
|
+
▼
|
|
31
|
+
adapters/dsh/review.mjs ──► dsh headless
|
|
32
|
+
|
|
33
|
+
behavior *.eval.mjs ───────────────────► dsh behavior runner (trace + mock)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `src/experiment/` 是模型与 runtime 无关的试验设计层:blind review、实时观测、多次 reviewer 字节一致证据。它不 import dsh。
|
|
37
|
+
- `src/adapters/dsh/` 是落地层:把抽象任务交给隔离的 dsh headless。
|
|
38
|
+
- 你的 `eval/` 只保留领域 fixture、projection/observe、prompt、rubric 与 case,不复制 runner。
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm i -D @catheadowl/dsh-eval
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Requirements**(接线细节与失败自诊断见 [docs/host-wiring.md](docs/host-wiring.md)):
|
|
47
|
+
|
|
48
|
+
- 一个已构建的 deepseek-harness 检出(`apps/cli/lib/bin.js`);
|
|
49
|
+
- 被测插件已装进某个 dsh profile;
|
|
50
|
+
- peer 依赖 `@deepseek-ai/dsh-llm` 需手工接线(npm 会自动装到不兼容的古董版,须替换为指向宿主检出的链接)。
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
`<plugin>/eval/behavior/mock/smoke.eval.mjs`:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
import { firstTool, toolCalled, toolCallStep, textStep } from '@catheadowl/dsh-eval'
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
id: 'my-first-case',
|
|
61
|
+
mode: 'mock',
|
|
62
|
+
task: '把 guide.md 重命名为 intro.md',
|
|
63
|
+
async prepare(workspace) { /* 播种 fixture 文件 */ },
|
|
64
|
+
script: { steps: [toolCallStep('md_rename', { oldPath: 'guide.md', newPath: 'intro.md' }), textStep('done')] },
|
|
65
|
+
expect: [toolCalled('md_rename')],
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
dsh-eval run --mode mock eval/behavior/mock
|
|
71
|
+
dsh-review --dry-run eval/comprehension # review 层的免模型预演
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
命令需要知道用哪个 dsh profile:显式传 `--profile <name>`,或放一份 `dsh-eval.config.mjs` 到包根(见下节「统一配置」)。
|
|
75
|
+
|
|
76
|
+
真实运行用 `dsh-eval run --profile <p> --repo <harness 检出> <case 路径>`;全部 flags(`--mode/--keep-artifacts/--fail-on-skip/--format/--report`)见 [docs/report.md](docs/report.md)。real case 无凭证时 auto-skip(dsh 自己解析凭证),mock 与 dry-run 不需要任何凭证。
|
|
77
|
+
|
|
78
|
+
## 规范目录
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
<plugin>/eval/
|
|
82
|
+
.gitignore # .runs/(无路径前缀)
|
|
83
|
+
README.md
|
|
84
|
+
behavior/ # 可选
|
|
85
|
+
real/*.eval.mjs
|
|
86
|
+
mock/*.eval.mjs
|
|
87
|
+
_fixtures/
|
|
88
|
+
comprehension/ # 可选
|
|
89
|
+
<name>.review.mjs
|
|
90
|
+
fixtures.json
|
|
91
|
+
prompt.md
|
|
92
|
+
rubric.md
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 统一配置 dsh-eval.config.mjs
|
|
96
|
+
|
|
97
|
+
消费者包根放一份,两个 CLI 从工作目录向上查找,flags 永远覆盖 config:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
export default {
|
|
101
|
+
profile: 'headless', // dsh profile
|
|
102
|
+
repo: '../../deepseek-harness', // 相对路径锚定 config 文件所在目录
|
|
103
|
+
mode: 'mock', // behavior CLI 的 --mode 默认(review 无此项)
|
|
104
|
+
failOnSkip: false, // behavior CI 门禁默认
|
|
105
|
+
report: 'eval-report.json', // --report 默认(锚定 config 目录)
|
|
106
|
+
disableRows: ['gates'], // case 默认禁用的插件行;case 级声明覆盖
|
|
107
|
+
// (显式 [] = 全启用,gate 交互 case 用)
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
未知 key 直接报错(拼写错误不静默退化)。`disableRows` 的语义与 turn-close 门禁边界契约见 [docs/disablerows.md](docs/disablerows.md)。
|
|
112
|
+
|
|
113
|
+
## Docs
|
|
114
|
+
|
|
115
|
+
| 文档 | 主题 |
|
|
116
|
+
|---|---|
|
|
117
|
+
| [host-wiring](docs/host-wiring.md) | peer 接线(含 npm 古董 peer 坑)、构建 CLI、profile、凭证、spawn 要求 |
|
|
118
|
+
| [review](docs/review.md) | comprehension review:实验定义、sterile profile、产物、六条评审规则 |
|
|
119
|
+
| [matchers](docs/matchers.md) | trace matcher 与 mock helper 全集(工具面 / 文本面 / 模型可见面) |
|
|
120
|
+
| [disablerows](docs/disablerows.md) | `disableRows` 与 turn-close 门禁边界契约 |
|
|
121
|
+
| [rowconfig](docs/rowconfig.md) | `rowConfig` 行 config 覆写契约(整段替换、重述所需键) |
|
|
122
|
+
| [intent-cases](docs/intent-cases.md) | real 意图 case 规约:何时写、断言面、守卫、CI 语义 |
|
|
123
|
+
| [report](docs/report.md) | 机器可读报告(`--format json` / `--report`)结构 |
|
|
124
|
+
| [known-issues](docs/known-issues.md) | 已知问题与规避(如 staged home 的 REQUEST_EXTENSION) |
|
|
125
|
+
| [runner-api](docs/runner-api.md) | 程序化 runner API:`runEvalCase` options 契约、EvalRunResult 字段、跨档取 `cliPath` |
|
|
126
|
+
| [experimental](docs/experimental.md) | `experimental` 子路径符号清单(逃生面,无兼容承诺) |
|
|
127
|
+
|
|
128
|
+
## 运行保障
|
|
129
|
+
|
|
130
|
+
runner 用 `try/finally` 保证临时目录与链接在任何路径(`prepare` 抛错、mock 校验失败、spawn 错误)都被清理,不污染真实 profile store。behavior 与 review CLI 共享目录扫描(跳过 `.runs` 与 `node_modules`);behavior CLI 在加载期做 case shape 校验与跨文件重复 id 检测,尽早失败。
|
|
131
|
+
|
|
132
|
+
License: MIT。框架自身的测试与发布自检由仓库 CI 承接,不随包发布。
|
package/bin/dsh-eval.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* the exit code is 1 when any run fails. Failures keep their artifacts under
|
|
16
16
|
* `<case file dir>/.runs/<case id>/`.
|
|
17
17
|
*
|
|
18
|
-
* Output formats
|
|
18
|
+
* Output formats:
|
|
19
19
|
* - `--format text` (default): unchanged human output on stdout/stderr.
|
|
20
20
|
* - `--format json`: all progress and failure chatter moves to stderr;
|
|
21
21
|
* stdout receives exactly one JSON report object (see src/report.mjs).
|
|
@@ -147,7 +147,7 @@ const startedAt = new Date().toISOString()
|
|
|
147
147
|
const { options, paths } = parseArgs(process.argv.slice(2))
|
|
148
148
|
const jsonFormat = options.format === 'json'
|
|
149
149
|
|
|
150
|
-
// Config merge
|
|
150
|
+
// Config merge: a `dsh-eval.config.mjs` reachable from cwd
|
|
151
151
|
// supplies defaults; explicit flags always win. Required-ness is only
|
|
152
152
|
// decided after the merge, so config-only invocations work.
|
|
153
153
|
const { config } = await loadEvalConfig(process.cwd())
|
|
@@ -214,7 +214,7 @@ for (const file of files.sort()) {
|
|
|
214
214
|
if (hasDuplicate) continue
|
|
215
215
|
for (const c of cases) seenIds.set(c.id, file)
|
|
216
216
|
for (const rawCase of cases) {
|
|
217
|
-
// Row-disable precedence
|
|
217
|
+
// Row-disable precedence: a case's own `disableRows` —
|
|
218
218
|
// including an explicit `[]` ("disable nothing") — overrides the
|
|
219
219
|
// config-level default; only an undeclared field inherits it.
|
|
220
220
|
const evalCase = rawCase.disableRows === undefined && config.disableRows !== undefined
|
|
@@ -283,8 +283,8 @@ for (const file of files.sort()) {
|
|
|
283
283
|
say(`PASS ${evalCase.id}`)
|
|
284
284
|
} else {
|
|
285
285
|
const artifactsDir = writeArtifacts(evalCase, result, mode)
|
|
286
|
-
// Self-explaining failure for broken mock determinism
|
|
287
|
-
//
|
|
286
|
+
// Self-explaining failure for broken mock determinism: when non-host
|
|
287
|
+
// plugin injections are visible in the
|
|
288
288
|
// trace, the failure names them and the two framework-native exits —
|
|
289
289
|
// consumers stop rediscovering the mechanism from raw traces.
|
|
290
290
|
let hint
|
package/bin/dsh-review.mjs
CHANGED
|
@@ -86,7 +86,7 @@ function writeMaterialized(experiment, materialized, extra = {}, reviewResult =
|
|
|
86
86
|
|
|
87
87
|
const { options, paths } = parseArgs(process.argv.slice(2))
|
|
88
88
|
|
|
89
|
-
// Config merge
|
|
89
|
+
// Config merge: flags win over a `dsh-eval.config.mjs` found
|
|
90
90
|
// upward from cwd; profile falls back to the sterile default `headless`.
|
|
91
91
|
const { config } = await loadEvalConfig(process.cwd())
|
|
92
92
|
const profile = options.profile ?? config.profile ?? 'headless'
|
package/docs/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: dsh-eval 文档索引——安装与宿主接线、review、matcher 全集、disableRows 契约、intent case
|
|
2
|
+
description: dsh-eval 文档索引——安装与宿主接线、review、matcher 全集、disableRows/rowConfig 契约、intent case 规约、报告结构与已知问题的路由表
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# dsh-eval · docs index
|
|
@@ -7,9 +7,12 @@ description: dsh-eval 文档索引——安装与宿主接线、review、matcher
|
|
|
7
7
|
| 文档 | 主题 |
|
|
8
8
|
|---|---|
|
|
9
9
|
| [host-wiring.md](host-wiring.md) | 安装与宿主接线:peer 依赖(dsh-llm)、构建 CLI、profile、凭证、spawn 要求 |
|
|
10
|
-
| [review.md](review.md) | comprehension review:实验定义、sterile profile
|
|
10
|
+
| [review.md](review.md) | comprehension review:实验定义、sterile profile、产物清单、六条评审规则 |
|
|
11
11
|
| [matchers.md](matchers.md) | trace matcher 与 mock helper 全集 |
|
|
12
12
|
| [disablerows.md](disablerows.md) | `disableRows` 与 turn-close 门禁边界契约 |
|
|
13
|
+
| [rowconfig.md](rowconfig.md) | `rowConfig` 行 config 覆写契约(整段替换语义、形状限制、与 disableRows 分工) |
|
|
13
14
|
| [intent-cases.md](intent-cases.md) | real 意图 case 规约:何时写、断言面、守卫、CI 语义 |
|
|
14
15
|
| [report.md](report.md) | 机器可读报告(`--format json` / `--report`)结构 |
|
|
16
|
+
| [runner-api.md](runner-api.md) | 程序化 runner API:`runEvalCase` options 契约、EvalRunResult 字段、跨档取 `cliPath` |
|
|
15
17
|
| [known-issues.md](known-issues.md) | 已知问题与规避 |
|
|
18
|
+
| [experimental.md](experimental.md) | `experimental` 子路径符号清单(逃生面,无兼容承诺) |
|
package/docs/disablerows.md
CHANGED
|
@@ -15,7 +15,7 @@ eval 的临时工作区通常**不是 git 仓库**——doc-link 类 gate 在其
|
|
|
15
15
|
## 契约
|
|
16
16
|
|
|
17
17
|
- 默认**不声明** = 所选 profile 装载的插件照常运行(gate 交互 case——如断言 gate steer 的 `userMessageTextIncludes`——依赖此默认)。
|
|
18
|
-
- 声明 `disableRows: ['gates']`(case 级或 config 级)= 本次 run 禁用 gates 插件行(行 id 权威:`@catheadowl/dsh-extras` 包的 `cordis.patch.yml` `- id: gates
|
|
18
|
+
- 声明 `disableRows: ['gates']`(case 级或 config 级)= 本次 run 禁用 gates 插件行(行 id 权威:`@catheadowl/dsh-extras` 包的 `cordis.patch.yml` `- id: gates`——其他 dsh 插件包),终态违规不再触发 splice,`finalText` 保持「脚本终步文本」的确定性语义。禁用其他插件行同理,行 id 以该插件包的 patch 声明为准。
|
|
19
19
|
- gate 交互 case 在默认禁用的包里声明 `disableRows: []` 显式恢复装载。
|
|
20
20
|
- 不依赖插件开关的断言出口:`assistantTextIncludes`(断言脚本台词出现过,不要求是最终文本)。终态干净时仍应优先 `finalText*`。
|
|
21
21
|
- per-gate 白名单(如只关某个 gate)暂不支持:per-gate disable 需要 gate 框架侧先提供 config 面。
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: experimental 子路径符号清单——机制原语(沙箱/overlay/trace、review 执行器、CLI 解析链)的逃生面;无兼容承诺,随 minor 变更
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# experimental 子路径(逃生面)
|
|
6
|
+
|
|
7
|
+
<!-- experimental-tier-warning -->
|
|
8
|
+
|
|
9
|
+
> **⚠️ 无兼容承诺**:本入口的全部符号可在任意 minor 版本变更、移动或移除,不另行弃用周期。case 作者请使用包根入口(断言 DSL、`textStep` / `toolCallStep`、`defineReviewExperiment`、`runEvalCase`);只有自建执行面 / ad-hoc 诊断脚本才应 import 本入口,并自行承担跟进成本。
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { resolveDshCliChain } from '@catheadowl/dsh-eval/experimental'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 符号清单
|
|
16
|
+
|
|
17
|
+
| 符号 | 用途 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `resolveDshCliChain` | 现代三段式 dsh CLI 解析链(`--repo` 旗标 → 解析层 node_modules → config `repo`),返回 `{ cli, repo, source }`;解析失败同步抛错(指引见 [host-wiring.md](host-wiring.md)) |
|
|
20
|
+
| `stageProfileStore` | 把真实 profile store junction 感知地暂存进沙箱 home(沙箱机制) |
|
|
21
|
+
| `buildOverlayYaml` | 由片段拼装 dsh overlay YAML(整段发射器) |
|
|
22
|
+
| `overlayDisableRows` | 生成 `disabled: true` 的行禁用 overlay 片段 |
|
|
23
|
+
| `parseSessionLog` | 解析一条未压缩 JSONL session artifact 为 `{ header, events }` |
|
|
24
|
+
| `buildTrace` | 把 session 事件投影为 matcher 使用的 trace 对象 |
|
|
25
|
+
| `loadTraceDir` | 从 run 目录装载并解析 trace(无日志时返回 `undefined`) |
|
|
26
|
+
| `executeReviewExperiment` | 用给定 executor 执行抽象 review 实验 |
|
|
27
|
+
| `materializeReviewExperiment` | 把实验定义物化为产物目录 |
|
|
28
|
+
| `renderObservationSections` | 标准 observation renderer(自定义 executor 用) |
|
|
29
|
+
| `OBSERVATIONS_PLACEHOLDER` | prompt 中的观测占位符常量(必须恰好出现一次) |
|
|
30
|
+
| `createDshHeadlessReviewExecutor` | 构造 dsh headless review executor |
|
|
31
|
+
| `runDshReviewExperiment` | 端到端跑一个 dsh review 实验 |
|
|
32
|
+
| `validateToolBoundary` | 校验 trace 满足 turn-close 工具边界契约 |
|
|
33
|
+
| `renderToolBoundaryEvidence` | 渲染边界校验的机器可读证据 |
|
|
34
|
+
|
|
35
|
+
新公开能力先进本入口;稳定后经明确决策才升入包根入口(升入即接受 semver 义务)。
|
package/docs/host-wiring.md
CHANGED
|
@@ -45,7 +45,7 @@ node -e "console.log(require('./node_modules/@deepseek-ai/dsh-llm/package.json')
|
|
|
45
45
|
dsh CLI 的定位按以下顺序,先中先得:
|
|
46
46
|
|
|
47
47
|
1. **显式 flag**:`--repo <host-checkout>`(检出须已构建,`apps/cli/lib/bin.js` 存在);
|
|
48
|
-
2. **解析层**:`node_modules/@deepseek-ai/dsh/lib/bin.js`(开发态由 junction
|
|
48
|
+
2. **解析层**:`node_modules/@deepseek-ai/dsh/lib/bin.js`(开发态由 junction 指到宿主检出,junction 失效时须重建为指回本地宿主检出;消费态由安装树提供);
|
|
49
49
|
3. **config `repo` 键**:legacy,已从各包入库 config 退役。
|
|
50
50
|
|
|
51
51
|
三层全缺时 fail-loud(报错含占位符修法指引)。自诊断:
|
|
@@ -54,7 +54,7 @@ dsh CLI 的定位按以下顺序,先中先得:
|
|
|
54
54
|
node -e "console.log(require('fs').existsSync('node_modules/@deepseek-ai/dsh/lib/bin.js'))"
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
`false` = 解析层缺 CLI
|
|
57
|
+
`false` = 解析层缺 CLI:先把上述 junction 重建为指向宿主检出;仍 `false` 则宿主检出未构建(先构建宿主)。这类 junction 维护是机器相关的开发环境事务,不入库,由各开发环境自行承接(同上文 peer 接线的 gitignore 纪律)。behavior 与 review 的真实运行都从定位到的 CLI spawn dsh 本体。
|
|
58
58
|
|
|
59
59
|
## 环境面:profile 与插件安装
|
|
60
60
|
|
package/docs/matchers.md
CHANGED
|
@@ -6,7 +6,21 @@ description: trace matcher 与 mock helper 全集——工具面/文本面/输
|
|
|
6
6
|
|
|
7
7
|
全部从包根导入:`import { toolCalled, … } from '@catheadowl/dsh-eval'`。
|
|
8
8
|
|
|
9
|
-
断言对象是 dsh session 事件投影(`
|
|
9
|
+
断言对象是 dsh session 事件投影(`EvalTrace`),不只是「模型产出」:`requestHeaders` 投影模型被挂载的工具与 system prompt(输入面),`userMessages` 投影 user-role 的模型可见输入面(任务 prompt、插件 steer、注入上下文)——这让 mock 能断言插件的**驱动级 steer**,而不只断工具选择或最终文本。
|
|
10
|
+
|
|
11
|
+
## EvalTrace 形状(谓词与 `result.trace` 共用)
|
|
12
|
+
|
|
13
|
+
| 字段 | 形状 |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `toolCalls` | `{ seq, turn, step, callId, name, arguments, parsedArguments }[]`(`arguments` 原文,`parsedArguments` 已 JSON 解析) |
|
|
16
|
+
| `toolResults` | `{ seq, turn, step, callId, text, error?, isError? }[]`(与 `toolCalls` 按 `callId` 配对) |
|
|
17
|
+
| `assistantTexts` | `string[]` 非空组装 assistant 文本,按日志序 |
|
|
18
|
+
| `finalText` | 最后一个组装 assistant 文本(无则 `''`) |
|
|
19
|
+
| `userMessages` | `{ seq, source, text }[]`(`source` 原样透传:任务 prompt `{ kind: 'user' }`,插件 steer `{ kind: 'plugin', plugin }`) |
|
|
20
|
+
| `requestHeaders` | `{ seq, reason, system, toolNames }[]`(组装后 system prompt + 挂载工具名) |
|
|
21
|
+
| `sessions` / `sessionId` | 原始解析结果 `{ header, events }[]` 与主 session id |
|
|
22
|
+
|
|
23
|
+
`runEvalCase` 返回的 `result.trace` 即此形状(无 session 日志时为 `undefined`;字段语义见 [runner-api.md](runner-api.md))。
|
|
10
24
|
|
|
11
25
|
## 工具面
|
|
12
26
|
|
package/docs/review.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: comprehension review 指南——defineReviewExperiment 实验定义、sterile profile 盲评运行、.runs 产物与 review-report
|
|
2
|
+
description: comprehension review 指南——defineReviewExperiment 实验定义、sterile profile 盲评运行、.runs 产物与 review-report 判读模板、六条评审规则
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Comprehension review
|
|
@@ -34,7 +34,7 @@ export default defineReviewExperiment({
|
|
|
34
34
|
})
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
抽象层公开 `defineReviewExperiment
|
|
37
|
+
抽象层公开 `defineReviewExperiment`(稳定根入口)。实验执行与渲染原语——`materializeReviewExperiment`、`executeReviewExperiment`、标准 observation renderer(`renderObservationSections`;prompt 里的观测占位符常量是 `OBSERVATIONS_PLACEHOLDER`,必须恰好出现一次)——在 `@catheadowl/dsh-eval/experimental` 逃生面(见 [experimental.md](experimental.md),无兼容承诺)。自定义 executor 只需实现 `(task, context) => result`;接入其他 agent runtime 不需要改试验定义。
|
|
38
38
|
|
|
39
39
|
## CLI
|
|
40
40
|
|
|
@@ -67,7 +67,7 @@ dsh-review \
|
|
|
67
67
|
- `run.json`:experiment、rubric、adapter、profile 与 run 数;
|
|
68
68
|
- `review-report.md`:判读报告骨架——机器字段自动填(experiment/adapter/ profile/runs、observations 指纹、rubric 位置、每轮 reviewer 原文),三个 **人工判读栏目**留白待填:intentional design 命中项、新 red flag、下一步(改输出 / 改 rubric / 改 behavior case / 不处理)。刻意不做自动评分——review 层的价值在人工判断,报告只把判断物化成可归档、可对比的工程证据(dry-run 也会生成,runs 记 0)。
|
|
69
69
|
|
|
70
|
-
##
|
|
70
|
+
## 六条评审规则
|
|
71
71
|
|
|
72
72
|
1. **冻结输入,实时投影输出。** fixture 保存 raw SDK result、合成知识库或调用参数;`observe()` 必须调用当前构建产物。不要提交一份会随实现漂移的 projected-output 快照。
|
|
73
73
|
2. **盲 prompt 与隐藏 rubric 分离。** `prompt.md` 只能含问题和 `{{EVAL_OBSERVATIONS}}`;答案键、预期 next action、intentional design 只在 `rubric.md`。
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: rowConfig 边界契约——case 级按 loader 行 id 覆写行 config 的机制、整段替换语义与「重述所需键」义务、叶值形状限制、与 disableRows 的分工
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# rowConfig:case 级行 config 覆写
|
|
6
|
+
|
|
7
|
+
`rowConfig: Record<rowId, config对象>` 让一个 case 在**本次 run 的 overlay** 里覆写插件行的 config。框架对行 id 与键语义零内置知识(键由目标插件自定义),只做形状校验。典型用例是臂式实验:同一 case 跑两臂,差异臂用 `rowConfig: { prompt: { disabledProviders: ['<provider>'] } }` 关掉单个 provider,而不用 `disableRows` 禁掉整行(那会连工具面一起摘掉,制造混淆变量)。
|
|
8
|
+
|
|
9
|
+
## 机制
|
|
10
|
+
|
|
11
|
+
- 序列化为 overlay 条目 `- id: <row>` + `config:` 键值块(`buildOverlayYaml`),与 `session-persistence-jsonl` 重根、`persona`、`disableRows` 走同一条 per-run overlay 通道;
|
|
12
|
+
- 取值优先级同 overlay 语义:覆盖 bundle/patch 层为该行声明的 config。
|
|
13
|
+
|
|
14
|
+
## 整段替换语义(最大的坑)
|
|
15
|
+
|
|
16
|
+
cordis patch 层的 config 覆写是**整段替换**,不是深合并:`rowConfig` 声明的键集合就是该行生效的**全部** config。行原本带的其他键(如 prompt 行的 `providerTimeoutMs` / `totalTimeoutMs` / `renderBudgetChars`)不会自动保留——**用到哪个键就在 rowConfig 里重述哪个**。这与「惰性只写差异」的直觉相反,是宿主 patch 语义的直接推论(参见 dsh 宿主文档对 patch 的说明:按 id 定位行、整段替换其 config)。
|
|
17
|
+
|
|
18
|
+
## 形状限制
|
|
19
|
+
|
|
20
|
+
- 叶值只支持**标量**(string / number / boolean)或**标量数组**(如 `disabledProviders: ['a', 'b']`,YAML flow 序列发射);
|
|
21
|
+
- 嵌套对象不支持(`validateRowConfig` 拒绝)——需要嵌套 config 的行请走自己的 profile patch,不进 case;
|
|
22
|
+
- 校验双点:discovery 加载期与 `runEvalCase` 执行期同一份 `validateRowConfig`。
|
|
23
|
+
|
|
24
|
+
## 与 disableRows 的分工
|
|
25
|
+
|
|
26
|
+
| | `disableRows` | `rowConfig` |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| 效果 | 整行禁用(插件完全不装载) | 行照常装载,config 被覆写 |
|
|
29
|
+
| 适用 | 该 case 不需要该插件的任何面 | 该 case 需要插件但想改其行为参数 |
|
|
30
|
+
| 组合 | 同一行同时出现在两处是矛盾声明(禁用的行无 config 可言),避免 | |
|
|
31
|
+
|
|
32
|
+
## 校验示例
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
// 合法
|
|
36
|
+
rowConfig: { prompt: { disabledProviders: ['breadcrumb-description-enricher'], totalTimeoutMs: 5000 } }
|
|
37
|
+
// 非法:嵌套对象
|
|
38
|
+
rowConfig: { prompt: { a: { b: 1 } } } // validateRowConfig 拒绝
|
|
39
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 程序化 runner API——runEvalCase 的 options 契约(SDK 承诺)、EvalRunResult 字段、DSH_EVAL_KEEP_TMP 逃生、与 experimental 档 resolveDshCliChain 的跨档关系
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# 程序化 runner API
|
|
6
|
+
|
|
7
|
+
`runEvalCase` 从包根导入,是 SDK 稳定面的一部分(semver 承诺):
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { runEvalCase } from '@catheadowl/dsh-eval'
|
|
11
|
+
|
|
12
|
+
const result = await runEvalCase(evalCase, { profile: 'headless', cliPath, mode: 'real' })
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## options(SDK 承诺)
|
|
16
|
+
|
|
17
|
+
| 键 | 类型 | 语义 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `profile` | `string` | 必填。承载被测插件的 dsh profile(沙箱会暂存其 store,不污染真实 home)。 |
|
|
20
|
+
| `cliPath` | `string` | 编译好的 dsh CLI 入口(`apps/cli/lib/bin.js`)绝对路径;优先于 `dshRepoDir`。程序化取值见下节。 |
|
|
21
|
+
| `mode` | `'real' \| 'mock'` | 覆写 case 自带的 mode;mock 需 `script.steps`。 |
|
|
22
|
+
| `artifactsDir` | `string` | 提供则把 stdout/stderr/trace/session 日志拷贝到该目录(自动创建)。 |
|
|
23
|
+
| `dshRepoDir` | `string` | **已弃用**:宿主 checkout 目录(从中拼出 CLI 路径)。下个 minor 删除——迁移到 `cliPath`。 |
|
|
24
|
+
|
|
25
|
+
## `cliPath` 从哪来:跨档关系(读我)
|
|
26
|
+
|
|
27
|
+
稳定档不提供 CLI 定位器;程序化解析走 experimental 档的 `resolveDshCliChain`(`import '@catheadowl/dsh-eval/experimental'`,见 [experimental.md](experimental.md))。**该档无兼容承诺**:若你的脚本不能接受随 minor 跟进,可自行传入 `cliPath`(如来自你自己的部署清单),`runEvalCase` 不假设来源。
|
|
28
|
+
|
|
29
|
+
## EvalRunResult 字段
|
|
30
|
+
|
|
31
|
+
| 字段 | 语义 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `caseId` / `mode` / `task` | 回显 case 标识。 |
|
|
34
|
+
| `exitCode` | headless CLI 退出码(0 = turn 完成)。 |
|
|
35
|
+
| `timedOut` | 是否超时被杀。 |
|
|
36
|
+
| `stdout` / `stderr` | CLI 原始输出(stdout 含最终 assistant 文本与启动 chatter)。 |
|
|
37
|
+
| `trace` | session 事件投影(形状见 [matchers.md](matchers.md) 的 trace 形状表);无日志时为 `undefined`。 |
|
|
38
|
+
| `sessionLogs` | 清理前的原始 session artifact 文本数组。 |
|
|
39
|
+
| `inspectError` | case 的 `inspect` 抛错时的错误文本。 |
|
|
40
|
+
| `runDir` | 本次运行的沙箱目录——**默认清理**;设环境变量 `DSH_EVAL_KEEP_TMP=1` 保留(失败诊断用)。 |
|
|
41
|
+
|
|
42
|
+
清理是全路径 `try/finally`:`prepare` 抛错、mock 校验失败、spawn 失败都会清理,真实 profile store 永不被污染。
|