niceeval 0.5.4 → 0.6.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/INDEX.md +55 -0
- package/dist/agents/types.d.ts +317 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +178 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +618 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +32 -0
- package/dist/report/format.js +126 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +83 -0
- package/dist/report/locale.js +160 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +20 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +10 -0
- package/dist/report/react/ExperimentList.js +15 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +100 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +393 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +503 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +420 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/annotated-source.d.ts +61 -0
- package/dist/results/attempt-evidence.d.ts +69 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +5 -5
- package/docs-site/zh/concepts/assert.mdx +8 -8
- package/docs-site/zh/concepts/evals.mdx +5 -5
- package/docs-site/zh/concepts/experiment.mdx +2 -2
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +2 -2
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -1
- package/docs-site/zh/guides/fixtures.mdx +1 -1
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +3 -3
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +35 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +11 -11
- package/docs-site/zh/guides/viewing-results.mdx +199 -79
- package/docs-site/zh/guides/write-experiment.mdx +9 -1
- package/docs-site/zh/guides/write-send.mdx +2 -2
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +12 -12
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +7 -1
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/package.json +10 -6
- package/src/agents/bub.ts +129 -42
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +9 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +54 -2
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +23 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +8 -8
- package/src/define.ts +11 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +34 -15
- package/src/i18n/zh-CN.ts +37 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +643 -0
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +223 -50
- package/src/report/compute.ts +358 -53
- package/src/report/dual-render.test.tsx +1125 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +50 -2
- package/src/report/index.ts +48 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +206 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +115 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +108 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +136 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +129 -0
- package/src/report/react/fixtures.ts +163 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +22 -6
- package/src/report/react/render.test.tsx +164 -31
- package/src/report/react/styles.css +405 -213
- package/src/report/report.test.ts +403 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +303 -95
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +196 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +48 -13
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +24 -24
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +125 -94
- package/src/show/render.ts +660 -160
- package/src/show/show.test.ts +472 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// loadAttemptEvidence 单测(定稿契约见 plan/attempt-evidence-feedback-loop.md「中性数据准备」、
|
|
2
|
+
// src/results/attempt-evidence.ts 的头注)。用真实 createResultsWriter → openResults 的读写链路
|
|
3
|
+
// 落一份最小 fixture(不手写 JSON 文件,理由同 loadAnnotatedEvalSource 的既有端到端测试:
|
|
4
|
+
// 这条链路本身就是被测对象的一部分),覆盖四种 capability 组合与 identity 正确性。
|
|
5
|
+
|
|
6
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
7
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import {
|
|
11
|
+
createResultsWriter,
|
|
12
|
+
loadAttemptEvidence,
|
|
13
|
+
openResults,
|
|
14
|
+
type AttemptArtifacts,
|
|
15
|
+
type AttemptEntry,
|
|
16
|
+
type AttemptHandle,
|
|
17
|
+
type DiffData,
|
|
18
|
+
type EvalResult,
|
|
19
|
+
type StreamEvent,
|
|
20
|
+
type TraceSpan,
|
|
21
|
+
} from "./index.ts";
|
|
22
|
+
|
|
23
|
+
const roots: string[] = [];
|
|
24
|
+
async function makeRoot(): Promise<string> {
|
|
25
|
+
const root = await mkdtemp(join(tmpdir(), "niceeval-attempt-evidence-"));
|
|
26
|
+
roots.push(root);
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
await Promise.all(roots.splice(0).map((r) => rm(r, { recursive: true, force: true })));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const SOURCE_PATH = "evals/a.eval.ts";
|
|
34
|
+
const SOURCE_CONTENT = 'import { defineEval } from "niceeval";\nexport default defineEval({\n test() {},\n});\n';
|
|
35
|
+
|
|
36
|
+
const ASSERTIONS: EvalResult["assertions"] = [
|
|
37
|
+
{ name: "check-1", passed: true, severity: "gate", score: 1, loc: { file: SOURCE_PATH, line: 3 } },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const EVENTS: StreamEvent[] = [
|
|
41
|
+
{ type: "message", role: "assistant", text: "looking at weather" },
|
|
42
|
+
{ type: "action.called", callId: "c1", name: "get_weather", input: { city: "Brooklyn" } },
|
|
43
|
+
{ type: "action.result", callId: "c1", output: { tempF: 72 }, status: "completed" },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const TRACE: TraceSpan[] = [
|
|
47
|
+
{ traceId: "t1", spanId: "s1", name: "tool.get_weather", startMs: 0, endMs: 10, attributes: { call_id: "c1" } },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const NONEMPTY_DIFF: DiffData = { generatedFiles: { "a.txt": "hello" }, deletedFiles: [] };
|
|
51
|
+
const EMPTY_DIFF: DiffData = { generatedFiles: {}, deletedFiles: [] };
|
|
52
|
+
|
|
53
|
+
/** 起一个 writer,写一条 attempt,finish,再从头 openResults 读回它的 AttemptHandle。 */
|
|
54
|
+
async function seedAttempt(
|
|
55
|
+
root: string,
|
|
56
|
+
entry: Partial<AttemptEntry> & { id: string },
|
|
57
|
+
artifacts?: AttemptArtifacts,
|
|
58
|
+
): Promise<AttemptHandle> {
|
|
59
|
+
const writer = createResultsWriter(root, { producer: { name: "niceeval", version: "1.0.0" } });
|
|
60
|
+
const snap = await writer.snapshot({ experimentId: "compare/bub", agent: "bub", startedAt: "2026-07-01T08:00:00.000Z" });
|
|
61
|
+
await snap.writeAttempt(
|
|
62
|
+
{ verdict: "passed", attempt: 0, durationMs: 1000, assertions: [], ...entry },
|
|
63
|
+
artifacts,
|
|
64
|
+
);
|
|
65
|
+
await writer.finish();
|
|
66
|
+
const results = await openResults(root);
|
|
67
|
+
return results.experiments[0]!.latest.evals.find((e) => e.id === entry.id)!.attempts[0]!;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe("loadAttemptEvidence", () => {
|
|
71
|
+
it("四个 capability 全部具备:eval / execution / timing / diff", async () => {
|
|
72
|
+
const root = await makeRoot();
|
|
73
|
+
const attempt = await seedAttempt(
|
|
74
|
+
root,
|
|
75
|
+
{ id: "weather/brooklyn", assertions: ASSERTIONS },
|
|
76
|
+
{ events: EVENTS, trace: TRACE, diff: NONEMPTY_DIFF, sources: [{ path: SOURCE_PATH, content: SOURCE_CONTENT }] },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const evidence = await loadAttemptEvidence(attempt);
|
|
80
|
+
|
|
81
|
+
expect(evidence.evalSource).not.toBeNull();
|
|
82
|
+
expect(evidence.evalSource!.sourcePath).toBe(SOURCE_PATH);
|
|
83
|
+
expect(evidence.execution).not.toBeNull();
|
|
84
|
+
expect(evidence.execution!.timingAvailable).toBe(true);
|
|
85
|
+
// action 节点唯一关联上了 span(call_id 精确匹配),不是只挂了个 telemetry-only 节点。
|
|
86
|
+
const actionNode = evidence.execution!.nodes.find((n) => n.kind === "action");
|
|
87
|
+
expect(actionNode).toBeDefined();
|
|
88
|
+
expect((actionNode as { span?: TraceSpan }).span).toBeDefined();
|
|
89
|
+
expect(evidence.diff).toEqual(NONEMPTY_DIFF);
|
|
90
|
+
|
|
91
|
+
expect(evidence.capabilities).toEqual({ eval: true, execution: true, timing: true, diff: true });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("四个 capability 全部缺失:没有 source / events / trace / diff,不崩溃", async () => {
|
|
95
|
+
const root = await makeRoot();
|
|
96
|
+
const attempt = await seedAttempt(root, { id: "weather/queens" });
|
|
97
|
+
|
|
98
|
+
const evidence = await loadAttemptEvidence(attempt);
|
|
99
|
+
|
|
100
|
+
expect(evidence.evalSource).toBeNull();
|
|
101
|
+
expect(evidence.execution).toBeNull();
|
|
102
|
+
expect(evidence.diff).toBeNull();
|
|
103
|
+
expect(evidence.capabilities).toEqual({ eval: false, execution: false, timing: false, diff: false });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("有 events 没有 OTel spans:execution 为真、timing 为假", async () => {
|
|
107
|
+
const root = await makeRoot();
|
|
108
|
+
const attempt = await seedAttempt(root, { id: "weather/albany" }, { events: EVENTS });
|
|
109
|
+
|
|
110
|
+
const evidence = await loadAttemptEvidence(attempt);
|
|
111
|
+
|
|
112
|
+
expect(evidence.execution).not.toBeNull();
|
|
113
|
+
expect(evidence.execution!.timingAvailable).toBe(false);
|
|
114
|
+
expect(evidence.execution!.nodes.some((n) => n.kind === "action")).toBe(true);
|
|
115
|
+
expect(evidence.capabilities.execution).toBe(true);
|
|
116
|
+
expect(evidence.capabilities.timing).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("diff 存在但两个数组都空:capabilities.diff 为假,不是真", async () => {
|
|
120
|
+
const root = await makeRoot();
|
|
121
|
+
const attempt = await seedAttempt(root, { id: "weather/rome" }, { diff: EMPTY_DIFF });
|
|
122
|
+
|
|
123
|
+
const evidence = await loadAttemptEvidence(attempt);
|
|
124
|
+
|
|
125
|
+
expect(evidence.diff).toEqual(EMPTY_DIFF);
|
|
126
|
+
expect(evidence.capabilities.diff).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("identity 与源 attempt 的 experimentId / snapshotStartedAt / evalId / attempt 完全一致", async () => {
|
|
130
|
+
const root = await makeRoot();
|
|
131
|
+
const attempt = await seedAttempt(root, { id: "weather/dover", attempt: 0 });
|
|
132
|
+
|
|
133
|
+
const evidence = await loadAttemptEvidence(attempt);
|
|
134
|
+
|
|
135
|
+
expect(evidence.identity).toEqual({
|
|
136
|
+
experimentId: "compare/bub",
|
|
137
|
+
snapshotStartedAt: "2026-07-01T08:00:00.000Z",
|
|
138
|
+
evalId: "weather/dover",
|
|
139
|
+
attempt: 0,
|
|
140
|
+
});
|
|
141
|
+
expect(evidence.identity.experimentId).toBe(attempt.experimentId);
|
|
142
|
+
expect(evidence.identity.evalId).toBe(attempt.evalId);
|
|
143
|
+
expect(evidence.identity.attempt).toBe(attempt.result.attempt);
|
|
144
|
+
expect(evidence.identity.snapshotStartedAt).toBe(attempt.snapshot.startedAt);
|
|
145
|
+
// 真实读取路径:locator 恒有值,且与 attempt.locator 原样一致(不重算)。
|
|
146
|
+
expect(attempt.locator).toBeDefined();
|
|
147
|
+
expect(evidence.locator).toBe(attempt.locator);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("artifactPaths.dir 是这个 attempt 落盘目录的绝对路径", async () => {
|
|
151
|
+
const root = await makeRoot();
|
|
152
|
+
const attempt = await seedAttempt(root, { id: "weather/salem" }, { events: EVENTS });
|
|
153
|
+
|
|
154
|
+
const evidence = await loadAttemptEvidence(attempt);
|
|
155
|
+
|
|
156
|
+
expect(evidence.artifactPaths.dir).toBe(join(attempt.snapshot.dir, attempt.ref.attempt));
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// AttemptEvidence:一次装配一个 Attempt 的全部证据(定稿见 plan/attempt-evidence-feedback-loop.md
|
|
2
|
+
// 「中性数据准备」、docs/concepts.md「Attempt 证据」词条)。
|
|
3
|
+
//
|
|
4
|
+
// 这是 results/evidence 层的装配终点:locator + identity + EvalResult + AnnotatedEvalSource +
|
|
5
|
+
// ExecutionTree + diff + artifact 路径 + capability 位,一次算好。show、view、静态导出、报告列表
|
|
6
|
+
// 共用同一个 loadAttemptEvidence(attempt),不各自重读 artifact 或重算 capability——这正是
|
|
7
|
+
// loadAnnotatedEvalSource(attempt-source.ts)文档里说的"后续阶段的真正 assembler",这里只组合
|
|
8
|
+
// 已有的纯函数/薄壳,不重新实现 Eval 源码标注、ExecutionTree 合并或 diff 读取的任何一条逻辑。
|
|
9
|
+
//
|
|
10
|
+
// capability 位的门槛:每一位只在"数据真的存在且非空"时为 true,不是"artifact 文件存在"——
|
|
11
|
+
// 一份缺内容但存在的 diff.json(两个数组都空)与压根没有 diff.json 都不能点亮 [D]:两者对
|
|
12
|
+
// "有什么可看"这件事产生同一个答案(没有),只是原因不同,原因留给 unavailable 文案区分,
|
|
13
|
+
// 不留给 capability 位区分(capability 位只回答"要不要显示这个证据切面")。
|
|
14
|
+
//
|
|
15
|
+
// eval —— evalSource 非空 AND (至少有一行源码 或 至少有一条断言);理论上
|
|
16
|
+
// buildAnnotatedEvalSource 对空文件也会产出一行 ""(split("\n") 的自然结果),
|
|
17
|
+
// 所以这条件实践中约等于"evalSource 非空",按文档口径原样写出两个子句,
|
|
18
|
+
// 不依赖这个实现细节。
|
|
19
|
+
// execution —— events() 非空且非空数组;ExecutionTree 的骨架完全来自 events,没有事件
|
|
20
|
+
// 就没有骨架可看,不因为 trace 里有 span 就假装有一棵"只剩 telemetry 节点"的树
|
|
21
|
+
// (那种树不是骨架,是误导——见下面 execution 字段的 null 边界)。
|
|
22
|
+
// timing —— execution 能力已经成立,且 ExecutionTree.timingAvailable 为真(spans.length > 0,
|
|
23
|
+
// 语义见 execution-tree.ts 的同名字段注释:代表"这次运行接入过 OTel",不代表
|
|
24
|
+
// 每个节点都关联上了 span——一个 agent 曾经接入过 OTel,人/agent 看到 [⏱] 才会去
|
|
25
|
+
// 展开找时间,这与该字段"整体是否接入过"的语义正好对应)。
|
|
26
|
+
// diff —— diff 非空 AND (generatedFiles 非空 或 deletedFiles 非空);空判定与
|
|
27
|
+
// show/render.ts::diffText 的既有口径(generated.length===0 && deleted.length===0
|
|
28
|
+
// 时打印"no file changes recorded")完全一致,不另立一套空判定。
|
|
29
|
+
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import type { AttemptHandle } from "./types.ts";
|
|
32
|
+
import { type AttemptIdentity, type AttemptLocator, encodeAttemptLocator } from "./locator.ts";
|
|
33
|
+
import { loadAnnotatedEvalSource } from "./attempt-source.ts";
|
|
34
|
+
import type { AnnotatedEvalSource } from "./annotated-source.ts";
|
|
35
|
+
import { buildExecutionTree, type ExecutionTree } from "../o11y/execution-tree.ts";
|
|
36
|
+
import type { DiffData, EvalResult } from "../types.ts";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 一个 Attempt 的 artifact 落盘位置。单一目录足够:一个 attempt 的全部 artifact
|
|
40
|
+
* (events.json / trace.json / o11y.json / diff.json / sources.json,见 ARTIFACT_KINDS)
|
|
41
|
+
* 要么全在它自己的 attempt 目录,要么(--resume 携带条目——本轮没写任何新数据,
|
|
42
|
+
* 见 writer.ts 对携带分支的说明)全在 artifactBase 回退到的原快照 attempt 目录,
|
|
43
|
+
* 从不出现"这个 kind 在自己目录、那个 kind 在 artifactBase"的拆分场景,不需要
|
|
44
|
+
* 按 kind 逐个给路径。想要某一种 artifact 的具体文件,自己拼
|
|
45
|
+
* `join(artifactPaths.dir, "<kind>.json")`(kind 见 `ArtifactKind`)。
|
|
46
|
+
*
|
|
47
|
+
* 与 show/render.ts 的 `attemptArtifactsPath(attempt, cwd)` 是同一份"这个 attempt 的证据在哪"
|
|
48
|
+
* 知识的两种呈现:那边为 CLI 文本展示把它转成 cwd 相对的短路径,这里给 evidence 层的调用方
|
|
49
|
+
* (view / 静态导出 / 报告列表,它们没有统一的"cwd"概念)一份绝对路径,不做相对化。
|
|
50
|
+
*/
|
|
51
|
+
export interface EvidencePaths {
|
|
52
|
+
/** 绝对路径。 */
|
|
53
|
+
dir: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 每一位只在数据真的存在且非空时为 true——从不因为对应的 artifact 文件"存在"就点亮,
|
|
58
|
+
* 见本文件头注逐位的门槛说明。show/view/静态导出/报告列表读这四位来决定要不要显示
|
|
59
|
+
* 对应的证据切面,不自己重新判断"这个 attempt 有没有 X"。
|
|
60
|
+
*/
|
|
61
|
+
export interface AttemptEvidenceCapabilities {
|
|
62
|
+
/** evalSource 非空且有内容(源码行或断言二选一非空)。 */
|
|
63
|
+
eval: boolean;
|
|
64
|
+
/** events 非空数组——有骨架可看。 */
|
|
65
|
+
execution: boolean;
|
|
66
|
+
/** execution 成立且这次运行接入过 OTel(ExecutionTree.timingAvailable)。 */
|
|
67
|
+
timing: boolean;
|
|
68
|
+
/** diff 非空且至少改动/删除了一个文件。 */
|
|
69
|
+
diff: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 一个 Attempt 的完整证据聚合。除 `locator` 外每个字段要么是别的模块已经产出的纯数据类型,
|
|
74
|
+
* 要么是这四样拼出来的判定——本类型自己不发明新的证据形状。
|
|
75
|
+
*/
|
|
76
|
+
export interface AttemptEvidence {
|
|
77
|
+
locator: AttemptLocator;
|
|
78
|
+
/** locator 派生自的不可变身份元组,与 `locator` 编码的是同一份数据,供不想解码 locator
|
|
79
|
+
* 的调用方直接读结构化字段(如按 experimentId 分组、按时间排序)。 */
|
|
80
|
+
identity: AttemptIdentity;
|
|
81
|
+
result: EvalResult;
|
|
82
|
+
/** 运行时保存的 Eval 源码标注(`--eval` 切面);没有 sources() 就是 null,不伪造空文档。 */
|
|
83
|
+
evalSource: AnnotatedEvalSource | null;
|
|
84
|
+
/** 标准事件流 + OTel enrichment(`--execution` 切面);没有非空 events 就是 null,
|
|
85
|
+
* 不产出一棵只剩 telemetry-only 节点、没有真实骨架的误导性树(见头注 execution 门槛)。 */
|
|
86
|
+
execution: ExecutionTree | null;
|
|
87
|
+
/** 被测 Agent 对 Sandbox 工作区的文件变化(`--diff` 切面);原样透传 attempt.diff()。 */
|
|
88
|
+
diff: DiffData | null;
|
|
89
|
+
artifactPaths: EvidencePaths;
|
|
90
|
+
capabilities: AttemptEvidenceCapabilities;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 纯组合:一次性 await 好 attempt 的四类懒加载证据(events / trace / diff / 经
|
|
95
|
+
* loadAnnotatedEvalSource 解引用的 eval 源码),拼成一份 AttemptEvidence。不重新实现
|
|
96
|
+
* Eval 源码标注、ExecutionTree 合并或 diff 语义的任何一条规则——那些规则的家分别在
|
|
97
|
+
* annotated-source.ts / execution-tree.ts / attempt.diff() 自身,这里只调用与判定"够不够
|
|
98
|
+
* 亮起对应的证据切面"。
|
|
99
|
+
*
|
|
100
|
+
* `attempt.locator` 在真实读取路径(openResults() 产出的 handle)恒有值;缺失时按当前身份
|
|
101
|
+
* 元组按 encodeAttemptLocator 兜底算一份,同 open.ts 给 record.locator 做的兜底同一口径——
|
|
102
|
+
* 只服务手工构造 AttemptHandle 的测试场景,不改变真实读取路径的行为。
|
|
103
|
+
*/
|
|
104
|
+
export async function loadAttemptEvidence(attempt: AttemptHandle): Promise<AttemptEvidence> {
|
|
105
|
+
const identity: AttemptIdentity = {
|
|
106
|
+
experimentId: attempt.experimentId,
|
|
107
|
+
snapshotStartedAt: attempt.snapshot.startedAt,
|
|
108
|
+
evalId: attempt.evalId,
|
|
109
|
+
attempt: attempt.result.attempt,
|
|
110
|
+
};
|
|
111
|
+
const locator = attempt.locator ?? encodeAttemptLocator(identity);
|
|
112
|
+
|
|
113
|
+
const [evalSource, events, trace, diff] = await Promise.all([
|
|
114
|
+
loadAnnotatedEvalSource(attempt),
|
|
115
|
+
attempt.events(),
|
|
116
|
+
attempt.trace(),
|
|
117
|
+
attempt.diff(),
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
// execution 的 null 边界:没有非空事件骨架就是 null,即使 trace 里有 span(那些 span 在
|
|
121
|
+
// buildExecutionTree 里只会全部落进 telemetry-only 桶,产出一棵没有骨架、只有遥测的树——
|
|
122
|
+
// 那不是"这个 attempt 的执行记录",是噪音,不比 null 更有用)。
|
|
123
|
+
const hasEvents = events !== null && events.length > 0;
|
|
124
|
+
const execution = hasEvents ? buildExecutionTree(events, trace ?? []) : null;
|
|
125
|
+
|
|
126
|
+
const evalCapable = evalSource !== null && (evalSource.lines.length > 0 || evalSource.summary.totalAssertions > 0);
|
|
127
|
+
// 与 show/render.ts::diffText 的既有空判定同一口径:generatedFiles 与 deletedFiles 都空
|
|
128
|
+
// 才算"没有变化"。
|
|
129
|
+
const diffCapable = diff !== null && (Object.keys(diff.generatedFiles).length > 0 || diff.deletedFiles.length > 0);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
locator,
|
|
133
|
+
identity,
|
|
134
|
+
result: attempt.result,
|
|
135
|
+
evalSource,
|
|
136
|
+
execution,
|
|
137
|
+
diff,
|
|
138
|
+
artifactPaths: { dir: attemptArtifactDir(attempt) },
|
|
139
|
+
capabilities: {
|
|
140
|
+
eval: evalCapable,
|
|
141
|
+
execution: execution !== null,
|
|
142
|
+
timing: execution !== null && execution.timingAvailable,
|
|
143
|
+
diff: diffCapable,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* attempt artifact 目录的绝对路径:与 show/render.ts::attemptArtifactsPath 同一条判定
|
|
150
|
+
* (本 attempt 目录,或 --resume 携带条目回退到的 artifactBase 目录),只是不做 cwd 相对化
|
|
151
|
+
* ——evidence 层没有统一的"当前工作目录"概念,相对化是 CLI 文本渲染层自己的事。
|
|
152
|
+
*/
|
|
153
|
+
function attemptArtifactDir(attempt: AttemptHandle): string {
|
|
154
|
+
const r = attempt.result;
|
|
155
|
+
return r.artifactBase
|
|
156
|
+
? join(attempt.snapshot.dir, "..", "..", r.artifactBase)
|
|
157
|
+
: join(attempt.snapshot.dir, attempt.ref.attempt);
|
|
158
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// 薄组合层:给一个 AttemptHandle,把它的(已去重存储、经 sources() 解引用回来的)eval 源码
|
|
2
|
+
// 与它的 EvalResult.assertions 叠成 AnnotatedEvalSource(annotated-source.ts 的纯函数)。
|
|
3
|
+
//
|
|
4
|
+
// 这不是 AttemptEvidence assembler(那是后续阶段——统一 locator + AnnotatedEvalSource +
|
|
5
|
+
// ExecutionTree + diff 的中性证据聚合)。这里只证明一件事:discovery 时捕获 → 写入面按快照
|
|
6
|
+
// 去重存储 → 读取面经 attempt.sources() 解引用取回 → buildAnnotatedEvalSource 标注,这条链路
|
|
7
|
+
// 端到端能跑通,供后续阶段(text `--eval` renderer、web CodeView、真正的 assembler)直接调用
|
|
8
|
+
// 或参照。
|
|
9
|
+
|
|
10
|
+
import type { AttemptHandle } from "./types.ts";
|
|
11
|
+
import { buildAnnotatedEvalSource, type AnnotatedEvalSource } from "./annotated-source.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 给定一个 attempt,取回它的 eval 源码(经 sources() 解引用,可能来自本快照或
|
|
15
|
+
* artifactBase 回退到的原快照)与它的断言,产出标注好的 AnnotatedEvalSource。
|
|
16
|
+
*
|
|
17
|
+
* 没有 sources()(eval 从没被 `loc` 引用到、或落盘里压根没有 sources.json/artifactBase
|
|
18
|
+
* 已失效)时返回 null——没有源码就没有「标注源码」这回事,不伪造一份空文档。
|
|
19
|
+
*
|
|
20
|
+
* 一个 attempt 的 sources() 理论上可以有多份文件(主 eval 文件 + 罕见的跨文件 loc);
|
|
21
|
+
* 这里按「哪份文件被最多断言的 loc 命中」选一份作为主文件喂给 buildAnnotatedEvalSource——
|
|
22
|
+
* 只服务这条打通链路的验证目的,不是最终选择策略(后续阶段如需要为每份引用到的文件
|
|
23
|
+
* 都出一份 AnnotatedEvalSource,在这基础上很容易扩成返回数组)。
|
|
24
|
+
*/
|
|
25
|
+
export async function loadAnnotatedEvalSource(attempt: AttemptHandle): Promise<AnnotatedEvalSource | null> {
|
|
26
|
+
const sources = await attempt.sources();
|
|
27
|
+
if (!sources || sources.length === 0) return null;
|
|
28
|
+
|
|
29
|
+
const assertions = attempt.result.assertions;
|
|
30
|
+
let primary = sources[0]!;
|
|
31
|
+
if (sources.length > 1) {
|
|
32
|
+
const hits = new Map<string, number>();
|
|
33
|
+
for (const a of assertions) {
|
|
34
|
+
if (!a.loc) continue;
|
|
35
|
+
hits.set(a.loc.file, (hits.get(a.loc.file) ?? 0) + 1);
|
|
36
|
+
}
|
|
37
|
+
let best = -1;
|
|
38
|
+
for (const s of sources) {
|
|
39
|
+
const count = hits.get(s.path) ?? 0;
|
|
40
|
+
if (count > best) {
|
|
41
|
+
best = count;
|
|
42
|
+
primary = s;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return buildAnnotatedEvalSource(primary, assertions);
|
|
48
|
+
}
|