pi-mono/packages/evals/test/report.test.ts
Christian Klotz 9628c84809
feat(coding-agent): add prompt template documentation eval (#10261)
## Summary

- add live documentation comparisons for project-scoped and user-scoped
`/current-time` prompt templates
- require the generated command to expand to the exact requested prompt
text
- select the requested case from multi-case Vitest reports so skipped
siblings do not invalidate observations
- strengthen documentation-audit evidence and move the expensive audit
behind an explicit `eval:audit` command
- remove redundant eval oracles and test-only exports found during the
eval-suite audit

## Testing

- `npm run check`
- `./test.sh`
- `npm --prefix packages/evals test` (54 passed, also covered by
`./test.sh`)
- failed-status mutation: focused report test passed before the fixture
repair, failed after it, then passed with production restored
- prompt-template documentation eval, three repetitions per variant (6/6
pairs eligible)
- project scope: with docs 66.7%, without docs 100%; negative delta and
treatment flakiness
  - user scope: with docs 100%, without docs 100%; no lift
- model documentation eval (1/1 pair eligible; treatment 100%, control
0%)
- representative live documentation audit (`cli.md` passed)
2026-10-28 12:12:19 +02:00

211 lines
7.1 KiB
TypeScript

import { mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import type { EvalTask } from "../src/plan.ts";
import { PI_SESSION_SNAPSHOT_ARTIFACT, readTaskObservation } from "../src/report.ts";
const temporaryDirectories: string[] = [];
afterEach(async () => {
await Promise.all(temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true, force: true })));
});
const task: EvalTask = {
file: "evals/example.docs.eval.ts",
fullName: "Example workflow > handles the case",
evalSet: "Example workflow",
caseId: "handles the case",
variant: "without_docs",
model: "fixture/model",
runNumber: 1,
};
const SESSION = '{"type":"session"}\n';
type ReportAssertion = {
caseId?: string;
status?: "passed" | "skipped" | "pending" | "todo" | "disabled" | "failed";
meta?: Record<string, unknown>;
};
async function writeTaskReport(assertions: ReportAssertion[]) {
const directory = await mkdtemp(join(tmpdir(), "pi-eval-report-test-"));
temporaryDirectories.push(directory);
const reportPath = join(directory, "vitest.json");
const results = assertions.map(({ caseId = task.caseId, status = "passed", meta = {} }) => ({
ancestorTitles: [task.evalSet],
fullName: `${task.evalSet} ${caseId}`,
status,
title: caseId,
failureMessages: [],
meta,
}));
const count = (status: ReportAssertion["status"]): number =>
results.filter((result) => result.status === status).length;
await writeFile(
reportPath,
JSON.stringify({
numFailedTests: count("failed"),
numPassedTests: count("passed"),
numPendingTests: count("pending") + count("skipped") + count("disabled"),
numTodoTests: count("todo"),
numTotalTests: results.length,
startTime: 0,
success: count("failed") === 0,
testResults: [
{
message: "",
name: "/repo/packages/evals/evals/example.docs.eval.ts",
status: count("failed") === 0 ? "passed" : "failed",
assertionResults: results,
},
],
}),
);
return { directory, reportPath };
}
function scoredMeta(overrides?: {
avgScore?: number | null;
model?: string;
errors?: Array<Record<string, string>>;
artifacts?: Record<string, string>;
}): Record<string, unknown> {
return {
eval: {
avgScore: overrides && "avgScore" in overrides ? overrides.avgScore : 0.5,
scores: [{ name: "StructuredOutputJudge", score: 0.5 }],
thresholdFailed: false,
},
harness: {
name: "without_docs",
run: {
output: { ok: true },
session: { events: [{ type: "message", role: "user", content: "prompt" }] },
usage: {
provider: "fixture",
model: overrides?.model ?? "model",
inputTokens: 10,
outputTokens: 5,
totalTokens: 15,
toolCalls: 1,
metadata: { cacheReadTokens: 2, cacheWriteTokens: 3, estimatedCostUsd: 0.01 },
},
timings: { totalMs: 1234 },
artifacts: overrides?.artifacts ?? { runId: "run-1", [PI_SESSION_SNAPSHOT_ARTIFACT]: SESSION },
errors: overrides?.errors ?? [],
},
},
};
}
async function readObservation(assertion: ReportAssertion = {}) {
const { directory, reportPath } = await writeTaskReport([assertion]);
return { directory, observation: await readTaskObservation(task, reportPath, directory) };
}
describe("readTaskObservation", () => {
it.each(["skipped", "todo", "disabled"] as const)(
"preserves a skipped outcome for %s cases without harness runs",
async (status) => {
const { observation } = await readObservation({ status });
expect(observation).toMatchObject({ outcome: "skipped" });
},
);
it("preserves a pending outcome when no harness run exists", async () => {
const { observation } = await readObservation({ status: "pending" });
expect(observation).toMatchObject({ outcome: "pending" });
});
it("preserves metrics from a failed eval with a partial harness run", async () => {
const { directory, observation } = await readObservation({
status: "failed",
meta: scoredMeta(),
});
expect(observation).toEqual({
evalSet: task.evalSet,
caseId: task.caseId,
variant: task.variant,
model: task.model,
runNumber: task.runNumber,
outcome: "errored",
inputTokens: 10,
outputTokens: 5,
cacheReadTokens: 2,
cacheWriteTokens: 3,
totalTokens: 15,
toolCalls: 1,
totalMs: 1234,
estimatedCostUsd: 0.01,
});
const hashes = await readdir(join(directory, task.variant, "sessions"));
await expect(
readFile(join(directory, task.variant, "sessions", hashes[0], "session.jsonl"), "utf8"),
).resolves.toBe(SESSION);
});
it("records an errored outcome when a passed eval has no harness run", async () => {
const { observation } = await readObservation({ status: "passed" });
expect(observation).toMatchObject({ outcome: "errored" });
});
it("reads a scored harness run and persists the session artifact", async () => {
const { directory, observation } = await readObservation({ meta: scoredMeta() });
expect(observation).toEqual({
evalSet: task.evalSet,
caseId: task.caseId,
variant: task.variant,
model: task.model,
runNumber: task.runNumber,
outcome: "scored",
score: 0.5,
inputTokens: 10,
outputTokens: 5,
cacheReadTokens: 2,
cacheWriteTokens: 3,
totalTokens: 15,
toolCalls: 1,
totalMs: 1234,
estimatedCostUsd: 0.01,
});
const hashes = await readdir(join(directory, task.variant, "sessions"));
expect(hashes).toHaveLength(1);
await expect(
readFile(join(directory, task.variant, "sessions", hashes[0], "session.jsonl"), "utf8"),
).resolves.toBe(SESSION);
});
it("selects the requested case when a sibling case is skipped", async () => {
const { directory, reportPath } = await writeTaskReport([
{ caseId: "sibling case", status: "skipped" },
{ caseId: task.caseId, meta: scoredMeta() },
]);
const observation = await readTaskObservation(task, reportPath, directory);
expect(observation).toMatchObject({ outcome: "scored", score: 0.5, totalTokens: 15 });
});
it("treats a zero score as scored data", async () => {
const { observation } = await readObservation({ meta: scoredMeta({ avgScore: 0 }) });
expect(observation).toMatchObject({ outcome: "scored", score: 0 });
});
it("records an unscored outcome when a completed eval has no score", async () => {
const { observation } = await readObservation({ meta: scoredMeta({ avgScore: null }) });
expect(observation).toMatchObject({ outcome: "unscored" });
});
it("records an errored outcome when the reported model does not match the task", async () => {
const { observation } = await readObservation({ meta: scoredMeta({ model: "other" }) });
expect(observation).toMatchObject({ outcome: "errored" });
});
it("records an errored outcome when a completed harness run contains errors", async () => {
const { observation } = await readObservation({ meta: scoredMeta({ errors: [{ message: "boom" }] }) });
expect(observation).toMatchObject({ outcome: "errored" });
});
it("still scores a completed eval when the session artifact is missing", async () => {
const { observation } = await readObservation({ meta: scoredMeta({ artifacts: { runId: "run-1" } }) });
expect(observation).toMatchObject({ outcome: "scored", score: 0.5 });
});
});