@hawkeyexl/manni 4.1.0 → 4.3.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.
Files changed (51) hide show
  1. package/README.md +2 -2
  2. package/dist/batch-7JJWXWSF.js +18 -0
  3. package/dist/batch-7JJWXWSF.js.map +1 -0
  4. package/dist/calibrate-IA2ARFMH.js +13 -0
  5. package/dist/calibrate-IA2ARFMH.js.map +1 -0
  6. package/dist/capture-C32JXIFN.js +15 -0
  7. package/dist/capture-C32JXIFN.js.map +1 -0
  8. package/dist/chunk-3R23C5DT.js +120 -0
  9. package/dist/chunk-3R23C5DT.js.map +1 -0
  10. package/dist/chunk-CI2GOU3A.js +4447 -0
  11. package/dist/chunk-CI2GOU3A.js.map +1 -0
  12. package/dist/{chunk-I57KBF45.js → chunk-DP2ZA3WH.js} +1203 -142
  13. package/dist/chunk-DP2ZA3WH.js.map +1 -0
  14. package/dist/chunk-ESJ5W7EV.js +297 -0
  15. package/dist/chunk-ESJ5W7EV.js.map +1 -0
  16. package/dist/chunk-IOYXPBCL.js +37 -0
  17. package/dist/chunk-IOYXPBCL.js.map +1 -0
  18. package/dist/chunk-KTSJ2ZHA.js +40 -0
  19. package/dist/chunk-KTSJ2ZHA.js.map +1 -0
  20. package/dist/{chunk-JODRT4NF.js → chunk-MFDD3YGU.js} +8 -37
  21. package/dist/chunk-MFDD3YGU.js.map +1 -0
  22. package/dist/chunk-MOQXWXSG.js +1941 -0
  23. package/dist/chunk-MOQXWXSG.js.map +1 -0
  24. package/dist/{chunk-S3NDEIOW.js → chunk-O6RVQKK6.js} +10502 -3017
  25. package/dist/chunk-O6RVQKK6.js.map +1 -0
  26. package/dist/chunk-QROCFW5S.js +174 -0
  27. package/dist/chunk-QROCFW5S.js.map +1 -0
  28. package/dist/chunk-RVVCA4WL.js +379 -0
  29. package/dist/chunk-RVVCA4WL.js.map +1 -0
  30. package/dist/cli.js +1293 -140
  31. package/dist/cli.js.map +1 -1
  32. package/dist/docmeta.js +3 -2
  33. package/dist/docmeta.js.map +1 -1
  34. package/dist/graph/embed.d.ts +154 -0
  35. package/dist/graph/embed.js +137 -0
  36. package/dist/graph/embed.js.map +1 -0
  37. package/dist/graph/runtime.d.ts +751 -0
  38. package/dist/graph/runtime.js +3067 -0
  39. package/dist/graph/runtime.js.map +1 -0
  40. package/dist/index.d.ts +6149 -799
  41. package/dist/index.js +682 -7
  42. package/dist/index.js.map +1 -1
  43. package/dist/picker-6HRMETE7.js +12 -0
  44. package/dist/picker-6HRMETE7.js.map +1 -0
  45. package/package.json +32 -4
  46. package/plugin/tracevals/.claude-plugin/plugin.json +9 -0
  47. package/plugin/tracevals/hooks/hooks.json +17 -0
  48. package/shapes/graph/shapes-1.0.0.ttl +689 -0
  49. package/dist/chunk-I57KBF45.js.map +0 -1
  50. package/dist/chunk-JODRT4NF.js.map +0 -1
  51. package/dist/chunk-S3NDEIOW.js.map +0 -1
@@ -0,0 +1,4447 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ NOT_GRANTED_REASON,
4
+ applyAvailabilityRecord,
5
+ declaredAt,
6
+ discoverConfig,
7
+ extractEvals,
8
+ findManifest,
9
+ foldBranchAvailability,
10
+ grantsFor,
11
+ isExecutionGrant,
12
+ makeRedactor,
13
+ newAvailability,
14
+ newReplay,
15
+ remapAvailability,
16
+ resolveArtifacts,
17
+ unknownGrantsMessage
18
+ } from "./chunk-MOQXWXSG.js";
19
+ import {
20
+ DocmetaError,
21
+ PATH_JOIN,
22
+ assertKnownProvider,
23
+ assertLocalFlag,
24
+ assertModelHasProvider,
25
+ classifyRef,
26
+ escapeWorkflowCommandMessage,
27
+ escapeWorkflowCommandProperty,
28
+ externalMetadataJoin,
29
+ extractFrontmatter,
30
+ familyMarks,
31
+ hasPagePlaceholder,
32
+ loadExternalMetadata,
33
+ localNotice,
34
+ memberOf,
35
+ mergeWithMarks,
36
+ noticeOnce,
37
+ ownsKey,
38
+ palette,
39
+ providerSpecFor,
40
+ resolveIdentity,
41
+ selectProvider,
42
+ warn,
43
+ xmlEscape
44
+ } from "./chunk-DP2ZA3WH.js";
45
+ import {
46
+ TracevalsError,
47
+ discoverTraces
48
+ } from "./chunk-3R23C5DT.js";
49
+ import {
50
+ errorMessage
51
+ } from "./chunk-IOYXPBCL.js";
52
+
53
+ // src/tracevals/commands/batch.ts
54
+ import { writeFile as writeFile2 } from "node:fs/promises";
55
+
56
+ // src/docevals/judge/budget.ts
57
+ var TURN_BUDGET_SKIP = "turn budget";
58
+ function turnBudgetSkipReason(maxTurns) {
59
+ return `judge ${TURN_BUDGET_SKIP} exhausted (${maxTurns})`;
60
+ }
61
+ function isTurnBudgetSkip(reason) {
62
+ return reason !== void 0 && reason.includes(TURN_BUDGET_SKIP);
63
+ }
64
+
65
+ // src/tracevals/aggregate.ts
66
+ var zero = () => ({
67
+ pass: 0,
68
+ fail: 0,
69
+ error: 0,
70
+ needsReview: 0,
71
+ skipped: 0
72
+ });
73
+ function tally(counts2, outcome) {
74
+ if (outcome === "needs-review") counts2.needsReview += 1;
75
+ else counts2[outcome] += 1;
76
+ }
77
+ var byKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
78
+ function accumulate(into, key, result, file, withEvalName) {
79
+ let row = into.get(key);
80
+ if (row === void 0) {
81
+ row = {
82
+ key,
83
+ artifactName: result.artifactName,
84
+ artifactType: result.artifactType,
85
+ ...withEvalName ? { evalName: result.evalName } : {},
86
+ artifacts: /* @__PURE__ */ new Set(),
87
+ graders: /* @__PURE__ */ new Set(),
88
+ traces: /* @__PURE__ */ new Set(),
89
+ counts: zero(),
90
+ failingTraces: [],
91
+ reviewTraces: [],
92
+ skipReasons: /* @__PURE__ */ new Set()
93
+ };
94
+ into.set(key, row);
95
+ }
96
+ row.artifacts.add(result.artifact);
97
+ row.graders.add(result.grader);
98
+ row.traces.add(file);
99
+ tally(row.counts, result.outcome);
100
+ if (result.outcome === "fail" || result.outcome === "error") {
101
+ if (!row.failingTraces.includes(file)) row.failingTraces.push(file);
102
+ }
103
+ if (result.outcome === "needs-review" && !row.reviewTraces.includes(file)) {
104
+ row.reviewTraces.push(file);
105
+ }
106
+ if (result.outcome === "skipped" && result.skipReason !== void 0) {
107
+ row.skipReasons.add(result.skipReason);
108
+ }
109
+ }
110
+ function finish(row, withGraders) {
111
+ const c = row.counts;
112
+ const total = c.pass + c.fail + c.error + c.needsReview + c.skipped;
113
+ const graded = total - c.skipped;
114
+ return {
115
+ key: row.key,
116
+ artifactName: row.artifactName,
117
+ artifactType: row.artifactType,
118
+ artifacts: [...row.artifacts].sort(),
119
+ ...row.evalName !== void 0 ? { evalName: row.evalName } : {},
120
+ ...withGraders ? { graders: [...row.graders].sort() } : {},
121
+ traces: row.traces.size,
122
+ total,
123
+ counts: { ...c },
124
+ graded,
125
+ passRate: graded > 0 ? c.pass / graded : null,
126
+ failingTraces: [...row.failingTraces],
127
+ reviewTraces: [...row.reviewTraces],
128
+ skipReasons: [...row.skipReasons].sort()
129
+ };
130
+ }
131
+ var artifactKey = (r) => `${r.artifactType}:${r.artifactName}`;
132
+ function aggregate(outcomes, options) {
133
+ const artifactRows = /* @__PURE__ */ new Map();
134
+ const evalRows = /* @__PURE__ */ new Map();
135
+ const traces = [];
136
+ let budgetSkips = 0;
137
+ const budgetTraces = /* @__PURE__ */ new Set();
138
+ let budgetReason = "";
139
+ const summary = {
140
+ total: 0,
141
+ pass: 0,
142
+ fail: 0,
143
+ error: 0,
144
+ needsReview: 0,
145
+ skipped: 0,
146
+ passRate: 1,
147
+ traces: outcomes.length,
148
+ tracesPassed: 0,
149
+ tracesFailed: 0,
150
+ tracesErrored: 0
151
+ };
152
+ let turns = 0;
153
+ let gradedWeight = 0;
154
+ let passedWeight = 0;
155
+ for (const outcome of outcomes) {
156
+ if ("error" in outcome) {
157
+ traces.push({
158
+ file: outcome.file,
159
+ error: outcome.error,
160
+ warnings: [],
161
+ exitCode: 1,
162
+ turns: 0,
163
+ durationMs: outcome.durationMs
164
+ });
165
+ summary.tracesErrored += 1;
166
+ continue;
167
+ }
168
+ const { report } = outcome;
169
+ traces.push({
170
+ file: outcome.file,
171
+ ...report.trace.sessionId !== void 0 ? { sessionId: report.trace.sessionId } : {},
172
+ summary: report.summary,
173
+ warnings: report.warnings,
174
+ exitCode: report.exitCode,
175
+ turns: report.turns,
176
+ durationMs: report.durationMs
177
+ });
178
+ if (report.exitCode === 1) summary.tracesFailed += 1;
179
+ else summary.tracesPassed += 1;
180
+ turns += report.turns;
181
+ summary.total += report.summary.total;
182
+ summary.pass += report.summary.pass;
183
+ summary.fail += report.summary.fail;
184
+ summary.error += report.summary.error;
185
+ summary.needsReview += report.summary.needsReview;
186
+ summary.skipped += report.summary.skipped;
187
+ for (const result of report.evalResults) {
188
+ if (result.outcome === "skipped" && isTurnBudgetSkip(result.skipReason)) {
189
+ budgetSkips += 1;
190
+ budgetTraces.add(outcome.file);
191
+ budgetReason = result.skipReason;
192
+ }
193
+ if (result.outcome === "pass" || result.outcome === "fail" || result.outcome === "error") {
194
+ const weight = result.weight ?? 1;
195
+ gradedWeight += weight;
196
+ if (result.outcome === "pass") passedWeight += weight;
197
+ }
198
+ const base = artifactKey(result);
199
+ accumulate(artifactRows, base, result, outcome.file, false);
200
+ accumulate(
201
+ evalRows,
202
+ `${base}::${result.evalName}`,
203
+ result,
204
+ outcome.file,
205
+ true
206
+ );
207
+ }
208
+ }
209
+ summary.passRate = gradedWeight === 0 ? 1 : passedWeight / gradedWeight;
210
+ const budget = budgetSkips > 0 ? {
211
+ skippedEvals: budgetSkips,
212
+ traces: budgetTraces.size,
213
+ reason: budgetReason
214
+ } : void 0;
215
+ const warnings = [...options.warnings ?? []];
216
+ if (budget !== void 0) {
217
+ warnings.push(
218
+ `${budget.reason}: ${budget.skippedEvals} eval(s) across ${budget.traces} of ${outcomes.length} trace(s) were never judged \u2014 raise judge.maxTurns or narrow the corpus`
219
+ );
220
+ }
221
+ return {
222
+ traces,
223
+ artifacts: [...artifactRows.values()].map((r) => finish(r, false)).sort(byKey),
224
+ evals: [...evalRows.values()].map((r) => finish(r, true)).sort(byKey),
225
+ summary,
226
+ warnings,
227
+ // A batch that ran out of money mid-corpus did not measure the corpus, and
228
+ // "did not measure" must never render as "measured clean". Exit 1 rather
229
+ // than 2: unlike an empty selector, the report is real and worth keeping —
230
+ // the traces that were judged carry genuine verdicts, and throwing them
231
+ // away to raise an operational error would cost more than it says.
232
+ exitCode: summary.tracesFailed > 0 || summary.tracesErrored > 0 || budget !== void 0 ? 1 : 0,
233
+ ...budget !== void 0 ? { budget } : {},
234
+ turns,
235
+ durationMs: options.durationMs
236
+ };
237
+ }
238
+
239
+ // src/shared/duration.ts
240
+ var UNITS = {
241
+ m: 6e4,
242
+ h: 60 * 6e4,
243
+ d: 24 * 60 * 6e4,
244
+ w: 7 * 24 * 60 * 6e4
245
+ };
246
+ function durationMessage(value) {
247
+ return `--newer-than must be a duration such as 30m, 24h, 7d or 2w, got "${value}"`;
248
+ }
249
+ function parseDuration(text, makeError) {
250
+ const match = /^(\d+(?:\.\d+)?)([mhdw])$/.exec(text.trim());
251
+ const count = match?.[1];
252
+ const unit = match?.[2];
253
+ const ms = unit === void 0 ? void 0 : UNITS[unit];
254
+ if (count === void 0 || ms === void 0) {
255
+ throw makeError(durationMessage(text));
256
+ }
257
+ return Number(count) * ms;
258
+ }
259
+
260
+ // src/tracevals/reporters/batch.ts
261
+ function budgetLine(report) {
262
+ const b = report.budget;
263
+ if (b === void 0) return void 0;
264
+ return `${b.skippedEvals} eval(s) across ${b.traces} trace(s) were never judged \u2014 ${b.reason}`;
265
+ }
266
+ var cell = (value) => value.replace(/\|/g, "\\|");
267
+ function rate(row) {
268
+ return row.passRate === null ? "\u2014" : `${Math.round(row.passRate * 1e3) / 10}%`;
269
+ }
270
+ function detail(row) {
271
+ const parts = [];
272
+ const c = row.counts;
273
+ parts.push(
274
+ `${c.pass} pass, ${c.fail} fail, ${c.error} error, ${c.needsReview} review, ${c.skipped} skip`
275
+ );
276
+ return parts.join(" \xB7 ");
277
+ }
278
+ function names(files, limit = 3) {
279
+ const shown = files.slice(0, limit).map((f) => basename(f));
280
+ return files.length > limit ? `${shown.join(", ")} +${files.length - limit} more` : shown.join(", ");
281
+ }
282
+ function basename(file) {
283
+ const parts = file.split(/[\\/]/);
284
+ return parts[parts.length - 1] ?? file;
285
+ }
286
+ function outliers(row) {
287
+ const parts = [];
288
+ if (row.failingTraces.length > 0) {
289
+ parts.push(`fail: ${names(row.failingTraces)}`);
290
+ }
291
+ if (row.reviewTraces.length > 0) {
292
+ parts.push(`review: ${names(row.reviewTraces)}`);
293
+ }
294
+ return parts.join("; ");
295
+ }
296
+ function renderBatchPretty(report, opts = {}) {
297
+ const pc = palette(opts.color === true);
298
+ const lines = [];
299
+ const s = report.summary;
300
+ lines.push(pc.bold(`manni tracevals \u2014 ${s.traces} trace(s)`));
301
+ lines.push(
302
+ pc.dim(
303
+ `${s.tracesPassed} passed \xB7 ${s.tracesFailed} failed \xB7 ${s.tracesErrored} unreadable`
304
+ )
305
+ );
306
+ const cutShort = budgetLine(report);
307
+ if (cutShort !== void 0) {
308
+ lines.push(pc.red(`${pc.bold("BUDGET EXHAUSTED")} ${cutShort}`));
309
+ }
310
+ lines.push("");
311
+ lines.push(pc.bold("Eval pass rates"));
312
+ if (report.evals.length === 0) {
313
+ lines.push(pc.dim(" no evals were planned for any trace"));
314
+ }
315
+ for (const row of report.evals) {
316
+ const label = row.passRate === null ? pc.dim(rate(row).padStart(6)) : row.passRate === 1 ? pc.green(rate(row).padStart(6)) : pc.red(rate(row).padStart(6));
317
+ lines.push(
318
+ ` ${label} ${row.artifactName} \u203A ${row.evalName ?? ""} ${pc.dim(`(${row.traces} trace(s))`)}`
319
+ );
320
+ lines.push(` ${pc.dim(detail(row))}`);
321
+ if (row.failingTraces.length > 0) {
322
+ lines.push(` ${pc.red(`failing: ${names(row.failingTraces)}`)}`);
323
+ }
324
+ if (row.reviewTraces.length > 0) {
325
+ lines.push(
326
+ ` ${pc.yellow(`review: ${names(row.reviewTraces)}`)}`
327
+ );
328
+ }
329
+ for (const reason of row.skipReasons) {
330
+ lines.push(` ${pc.dim(`skipped: ${reason}`)}`);
331
+ }
332
+ }
333
+ lines.push("");
334
+ lines.push(pc.bold("Artifact pass rates"));
335
+ for (const row of report.artifacts) {
336
+ lines.push(
337
+ ` ${rate(row).padStart(6)} ${row.artifactType}: ${row.artifactName} ${pc.dim(detail(row))}`
338
+ );
339
+ }
340
+ lines.push("");
341
+ lines.push(pc.bold("Traces"));
342
+ for (const t of report.traces) {
343
+ if (t.error !== void 0) {
344
+ lines.push(` ${pc.red("ERROR ")} ${basename(t.file)}`);
345
+ lines.push(` ${pc.red(t.error)}`);
346
+ continue;
347
+ }
348
+ const mark = t.exitCode === 0 ? pc.green("PASS ") : pc.red("FAIL ");
349
+ const ts = t.summary;
350
+ lines.push(
351
+ ` ${mark} ${basename(t.file)} ${pc.dim(
352
+ `${ts?.pass ?? 0} pass, ${ts?.fail ?? 0} fail, ${ts?.error ?? 0} error, ${ts?.needsReview ?? 0} review, ${ts?.skipped ?? 0} skip`
353
+ )}`
354
+ );
355
+ }
356
+ if (report.warnings.length > 0) {
357
+ lines.push("");
358
+ lines.push(pc.bold("Warnings"));
359
+ for (const warning of report.warnings) {
360
+ lines.push(` ${pc.yellow("!")} ${warning}`);
361
+ }
362
+ }
363
+ lines.push("");
364
+ lines.push(
365
+ pc.bold(
366
+ `${s.total} eval(s) across ${s.traces} trace(s): ${pc.green(`${s.pass} pass`)}, ${s.fail > 0 ? pc.red(`${s.fail} fail`) : `${s.fail} fail`}, ${s.error} error, ${s.needsReview} needs-review, ${s.skipped} skipped`
367
+ )
368
+ );
369
+ if (report.turns > 0) {
370
+ lines.push(pc.dim(`judge turns ${report.turns}`));
371
+ }
372
+ return lines.join("\n");
373
+ }
374
+ function renderBatchMarkdown(report) {
375
+ const lines = [];
376
+ const s = report.summary;
377
+ lines.push(`# manni tracevals batch report`);
378
+ lines.push("");
379
+ lines.push(`- **Traces**: ${s.traces}`);
380
+ lines.push(
381
+ `- **Outcome**: ${s.tracesPassed} passed, ${s.tracesFailed} failed, ${s.tracesErrored} unreadable`
382
+ );
383
+ const cutShort = budgetLine(report);
384
+ if (cutShort !== void 0) lines.push(`- **Budget**: ${cutShort}`);
385
+ lines.push("");
386
+ lines.push(`## Eval pass rates`);
387
+ lines.push("");
388
+ lines.push(`| Rate | Artifact | Eval | Grader | Traces | Outcomes | Outliers |`);
389
+ lines.push(`|---|---|---|---|---|---|---|`);
390
+ for (const row of report.evals) {
391
+ lines.push(
392
+ `| ${rate(row)} | ${cell(row.artifactName)} | ${cell(row.evalName ?? "")} | ${cell(
393
+ (row.graders ?? []).join(", ")
394
+ )} | ${row.traces} | ${cell(detail(row))} | ${cell(outliers(row))} |`
395
+ );
396
+ }
397
+ lines.push("");
398
+ lines.push(`## Artifact pass rates`);
399
+ lines.push("");
400
+ lines.push(`| Rate | Kind | Artifact | Traces | Outcomes |`);
401
+ lines.push(`|---|---|---|---|---|`);
402
+ for (const row of report.artifacts) {
403
+ lines.push(
404
+ `| ${rate(row)} | ${cell(row.artifactType)} | ${cell(row.artifactName)} | ${row.traces} | ${cell(detail(row))} |`
405
+ );
406
+ }
407
+ lines.push("");
408
+ lines.push(`## Traces`);
409
+ lines.push("");
410
+ lines.push(`| Result | Trace | Evals | Detail |`);
411
+ lines.push(`|---|---|---|---|`);
412
+ for (const t of report.traces) {
413
+ const result = t.error !== void 0 ? "error" : t.exitCode === 0 ? "pass" : "fail";
414
+ const evals = t.summary === void 0 ? "" : `${t.summary.pass}/${t.summary.total} pass`;
415
+ lines.push(
416
+ `| ${result} | \`${cell(t.file)}\` | ${evals} | ${cell(t.error ?? "")} |`
417
+ );
418
+ }
419
+ lines.push("");
420
+ if (report.warnings.length > 0) {
421
+ lines.push(`## Warnings`);
422
+ lines.push("");
423
+ for (const warning of report.warnings) lines.push(`- ${warning}`);
424
+ lines.push("");
425
+ }
426
+ lines.push(
427
+ `**${s.total} eval(s)** across ${s.traces} trace(s) \u2014 ${s.pass} pass, ${s.fail} fail, ${s.error} error, ${s.needsReview} needs-review, ${s.skipped} skipped.`
428
+ );
429
+ return lines.join("\n");
430
+ }
431
+
432
+ // src/tracevals/reporters/calibration.ts
433
+ import { isAbsolute, relative } from "node:path";
434
+ var cell2 = (value) => value.replace(/\|/g, "\\|");
435
+ function basename2(file) {
436
+ const parts = file.split(/[\\/]/);
437
+ return parts[parts.length - 1] ?? file;
438
+ }
439
+ function display(file) {
440
+ const rel = relative(process.cwd(), file);
441
+ return rel === "" || rel.startsWith("..") || isAbsolute(rel) ? file : rel;
442
+ }
443
+ var pct = (value) => value === null ? "\u2014" : `${Math.round(value * 1e3) / 10}%`;
444
+ var settingOf = (c) => `runs=${c.setting.ensembleRuns} autoPass=${c.setting.zones.autoPass} autoFail=${c.setting.zones.autoFail}`;
445
+ var SWEEP_COLUMNS = [
446
+ { label: "scored", width: 6, of: (c) => c.scored },
447
+ { label: "agree", width: 5, of: (c) => c.agree },
448
+ { label: "false-pass", width: 10, of: (c) => c.falsePass },
449
+ { label: "false-fail", width: 10, of: (c) => c.falseFail },
450
+ { label: "review", width: 6, of: (c) => c.review },
451
+ { label: "reviewVol", width: 9, of: (c) => c.reviewVolume },
452
+ { label: "insuff", width: 6, of: (c) => c.insufficient }
453
+ ];
454
+ var sweepRow = (axis, setting, cells) => ` ${axis.padEnd(20)} ${setting.padEnd(38)} ${cells.join(" ")}`;
455
+ function evidence(d) {
456
+ if (d.consensus === void 0) return "";
457
+ const v = d.consensus.votes;
458
+ return `${v.pass}p/${v.fail}f/${v.partial}?/${v.error}e over ${d.consensus.runs} run(s), confidence ${d.consensus.meanConfidence.toFixed(2)}`;
459
+ }
460
+ var KIND_LABEL = {
461
+ agree: "AGREE",
462
+ "false-pass": "FALSE-PASS",
463
+ "false-fail": "FALSE-FAIL",
464
+ review: "REVIEW",
465
+ "missed-review": "MISSED-REVIEW",
466
+ error: "ERROR",
467
+ skipped: "SKIPPED"
468
+ };
469
+ function renderCalibrationPretty(report, opts = {}) {
470
+ const pc = palette(opts.color === true);
471
+ const lines = [];
472
+ const c = report.counts;
473
+ lines.push(
474
+ pc.bold(
475
+ `manni tracevals calibrate \u2014 ${report.corpus.length} trace(s), ${c.labels} label(s)`
476
+ )
477
+ );
478
+ lines.push(
479
+ pc.dim(
480
+ `runs ${report.setting.ensembleRuns} \xB7 autoPass ${report.setting.zones.autoPass} \xB7 autoFail ${report.setting.zones.autoFail} \xB7 labels ${display(report.labelsFile)}`
481
+ )
482
+ );
483
+ lines.push("");
484
+ lines.push(
485
+ pc.bold(
486
+ `Agreement ${c.agree}/${c.scored} (${pct(c.agreement)})`
487
+ )
488
+ );
489
+ const headline = (count, label, gloss, colour) => {
490
+ const text = ` ${String(count).padStart(4)} ${label.padEnd(14)} ${pc.dim(gloss)}`;
491
+ lines.push(count > 0 ? colour(text) : pc.dim(text));
492
+ };
493
+ headline(c.falsePass, "false passes", "labelled fail, judged pass \u2014 the expensive error", pc.red);
494
+ headline(c.falseFail, "false fails", "labelled pass, judged fail \u2014 erodes trust fastest", pc.red);
495
+ headline(c.reviewVolume, "needs-review", "across the whole corpus \u2014 the review volume", pc.yellow);
496
+ headline(c.review, "deferred", "labelled pass or fail, routed to a human instead", pc.yellow);
497
+ headline(c.missedReview, "missed reviews", "labelled ambiguous, decided anyway", pc.yellow);
498
+ headline(c.errored, "errors", "the eval could not be graded at all", pc.red);
499
+ headline(c.skipped, "unscored", "labelled, but the eval never armed", (s) => s);
500
+ if (report.disagreements.length > 0) {
501
+ lines.push("");
502
+ lines.push(pc.bold("Disagreements"));
503
+ for (const d of report.disagreements) {
504
+ const tag = KIND_LABEL[d.kind];
505
+ const colour = d.kind === "false-pass" || d.kind === "false-fail" || d.kind === "error" ? pc.red : pc.yellow;
506
+ lines.push(
507
+ ` ${colour(tag.padEnd(14))} ${basename2(d.trace)} ${d.artifactName} \u203A ${d.evalName} ${pc.dim(`(${d.grader})`)}`
508
+ );
509
+ lines.push(
510
+ ` ${pc.dim(`expected ${d.expected}, got ${d.actual}`)}${evidence(d) === "" ? "" : pc.dim(` \xB7 ${evidence(d)}`)}`
511
+ );
512
+ if (d.note !== void 0) {
513
+ lines.push(` ${pc.dim(`note: ${d.note}`)}`);
514
+ }
515
+ }
516
+ }
517
+ if (report.unscored.length > 0) {
518
+ lines.push("");
519
+ lines.push(pc.bold("Unscored (no evidence either way)"));
520
+ for (const d of report.unscored) {
521
+ lines.push(
522
+ ` ${pc.dim("SKIPPED".padEnd(14))} ${basename2(d.trace)} ${d.artifactName} \u203A ${d.evalName}`
523
+ );
524
+ if (d.skipReason !== void 0) {
525
+ lines.push(` ${pc.dim(d.skipReason)}`);
526
+ }
527
+ }
528
+ }
529
+ if (report.sweep !== void 0) {
530
+ lines.push("");
531
+ lines.push(pc.bold("Sweep"));
532
+ lines.push(
533
+ pc.dim(
534
+ " re-scored from the verdicts this run already produced \u2014 no further model calls"
535
+ )
536
+ );
537
+ lines.push(
538
+ pc.dim(
539
+ sweepRow(
540
+ "axis",
541
+ "setting",
542
+ SWEEP_COLUMNS.map((c2) => c2.label.padStart(c2.width))
543
+ )
544
+ )
545
+ );
546
+ for (const s of report.sweep) {
547
+ lines.push(
548
+ sweepRow(
549
+ s.axis,
550
+ settingOf(s),
551
+ SWEEP_COLUMNS.map((c2) => String(c2.of(s.counts)).padStart(c2.width))
552
+ )
553
+ );
554
+ }
555
+ }
556
+ if (report.gates.length > 0) {
557
+ lines.push("");
558
+ lines.push(pc.bold("Thresholds"));
559
+ for (const g of report.gates) {
560
+ const text = ` ${g.name}: ${g.actual} of at most ${g.limit}`;
561
+ lines.push(g.exceeded ? pc.red(`${text} \u2014 exceeded`) : pc.green(text));
562
+ }
563
+ }
564
+ if (report.warnings.length > 0) {
565
+ lines.push("");
566
+ lines.push(pc.bold("Warnings"));
567
+ for (const w of report.warnings) lines.push(` ${pc.yellow("!")} ${w}`);
568
+ }
569
+ lines.push("");
570
+ lines.push(pc.dim(verdictLine(report)));
571
+ if (report.turns > 0) {
572
+ lines.push(pc.dim(`judge turns ${report.turns}`));
573
+ }
574
+ return lines.join("\n");
575
+ }
576
+ function countsRow(counts2) {
577
+ return `${counts2.scored} | ${counts2.agree} | ${counts2.falsePass} | ${counts2.falseFail} | ${counts2.review} | ${counts2.missedReview} | ${counts2.reviewVolume} | ${counts2.insufficient}`;
578
+ }
579
+ function verdictLine(report) {
580
+ if (report.counts.scored === 0) {
581
+ return "Exit 1: nothing was scored \u2014 every label joined to a skipped result, so agreement and every threshold above rest on an empty denominator.";
582
+ }
583
+ return report.exitCode === 0 ? "Measured cleanly. Disagreement is the finding, not a failure \u2014 exit 0." : "Exit 1: a threshold was exceeded, or the measurement is incomplete \u2014 a trace could not be evaluated, or the judge budget cut the corpus short.";
584
+ }
585
+ function renderCalibrationMarkdown(report) {
586
+ const lines = [];
587
+ const c = report.counts;
588
+ lines.push("# manni tracevals calibration report");
589
+ lines.push("");
590
+ lines.push(`- **Corpus**: ${report.corpus.length} trace(s), ${c.labels} label(s)`);
591
+ lines.push(
592
+ `- **Setting**: \`ensembleRuns\` ${report.setting.ensembleRuns}, \`zones.autoPass\` ${report.setting.zones.autoPass}, \`zones.autoFail\` ${report.setting.zones.autoFail}`
593
+ );
594
+ lines.push(`- **Agreement**: ${c.agree}/${c.scored} (${pct(c.agreement)})`);
595
+ lines.push(`- **Labels**: \`${cell2(display(report.labelsFile))}\``);
596
+ lines.push("");
597
+ lines.push("| Measure | Count | What it means |");
598
+ lines.push("|---|---|---|");
599
+ lines.push(`| False passes | ${c.falsePass} | Labelled fail, judged pass \u2014 the expensive error. |`);
600
+ lines.push(`| False fails | ${c.falseFail} | Labelled pass, judged fail \u2014 erodes trust fastest. |`);
601
+ lines.push(`| Review volume | ${c.reviewVolume} | \`needs-review\` across the whole corpus. |`);
602
+ lines.push(`| Deferred labels | ${c.review} | Labelled pass or fail, routed to a human instead. |`);
603
+ lines.push(`| Missed reviews | ${c.missedReview} | Labelled ambiguous, decided anyway. |`);
604
+ lines.push(`| Errors | ${c.errored} | The eval could not be graded at all. |`);
605
+ lines.push(`| Unscored | ${c.skipped} | Labelled, but the eval never armed. |`);
606
+ lines.push("");
607
+ lines.push("## Disagreements");
608
+ lines.push("");
609
+ if (report.disagreements.length === 0) {
610
+ lines.push("None \u2014 every scored label agreed.");
611
+ } else {
612
+ lines.push("| Kind | Trace | Artifact | Eval | Grader | Expected | Got | Evidence | Note |");
613
+ lines.push("|---|---|---|---|---|---|---|---|---|");
614
+ for (const d of report.disagreements) {
615
+ lines.push(
616
+ `| ${KIND_LABEL[d.kind]} | \`${cell2(basename2(d.trace))}\` | ${cell2(d.artifactName)} | ${cell2(
617
+ d.evalName
618
+ )} | ${cell2(d.grader)} | ${d.expected} | ${d.actual} | ${cell2(evidence(d))} | ${cell2(d.note ?? "")} |`
619
+ );
620
+ }
621
+ }
622
+ lines.push("");
623
+ if (report.unscored.length > 0) {
624
+ lines.push("## Unscored");
625
+ lines.push("");
626
+ lines.push("| Trace | Artifact | Eval | Reason |");
627
+ lines.push("|---|---|---|---|");
628
+ for (const d of report.unscored) {
629
+ lines.push(
630
+ `| \`${cell2(basename2(d.trace))}\` | ${cell2(d.artifactName)} | ${cell2(d.evalName)} | ${cell2(
631
+ d.skipReason ?? ""
632
+ )} |`
633
+ );
634
+ }
635
+ lines.push("");
636
+ }
637
+ if (report.sweep !== void 0) {
638
+ lines.push("## Sweep");
639
+ lines.push("");
640
+ lines.push(
641
+ "Re-scored from the verdicts this run already produced \u2014 no further model calls."
642
+ );
643
+ lines.push("");
644
+ lines.push(
645
+ "| Axis | Setting | Scored | Agree | False passes | False fails | Deferred | Missed reviews | Review volume | Insufficient |"
646
+ );
647
+ lines.push("|---|---|---|---|---|---|---|---|---|---|");
648
+ for (const s of report.sweep) {
649
+ lines.push(`| ${s.axis} | ${cell2(settingOf(s))} | ${countsRow(s.counts)} |`);
650
+ }
651
+ lines.push("");
652
+ }
653
+ if (report.gates.length > 0) {
654
+ lines.push("## Thresholds");
655
+ lines.push("");
656
+ lines.push("| Threshold | Limit | Actual | Result |");
657
+ lines.push("|---|---|---|---|");
658
+ for (const g of report.gates) {
659
+ lines.push(
660
+ `| ${g.name} | ${g.limit} | ${g.actual} | ${g.exceeded ? "exceeded" : "ok"} |`
661
+ );
662
+ }
663
+ lines.push("");
664
+ }
665
+ if (report.warnings.length > 0) {
666
+ lines.push("## Warnings");
667
+ lines.push("");
668
+ for (const w of report.warnings) lines.push(`- ${w}`);
669
+ lines.push("");
670
+ }
671
+ lines.push(
672
+ report.exitCode === 0 ? verdictLine(report) : `**${verdictLine(report)}**`
673
+ );
674
+ return lines.join("\n");
675
+ }
676
+
677
+ // src/shared/sarif-location.ts
678
+ var URI_BASE_ID = "SRCROOT";
679
+ var DRIVE = /^[A-Za-z]:\//;
680
+ function toPosix(p) {
681
+ return p.replace(/\\/g, "/");
682
+ }
683
+ function isAbsolutePath(posix) {
684
+ return posix.startsWith("/") || DRIVE.test(posix);
685
+ }
686
+ function isWindowsPath(posix) {
687
+ return DRIVE.test(posix) || posix.startsWith("//");
688
+ }
689
+ function underRoot(posix, root) {
690
+ if (posix.startsWith(root)) return posix.slice(root.length);
691
+ if (isWindowsPath(posix) && isWindowsPath(root)) {
692
+ if (posix.toLowerCase().startsWith(root.toLowerCase())) {
693
+ return posix.slice(root.length);
694
+ }
695
+ }
696
+ return null;
697
+ }
698
+ function encodePath(p) {
699
+ return encodeURI(p).replace(/#/g, "%23").replace(/\?/g, "%3F");
700
+ }
701
+ function fileUri(posix) {
702
+ const encoded = encodePath(posix);
703
+ if (posix.startsWith("//")) return `file:${encoded}`;
704
+ if (posix.startsWith("/")) return `file://${encoded}`;
705
+ return `file:///${encoded}`;
706
+ }
707
+ function escapesRoot(posix) {
708
+ return posix === ".." || posix.startsWith("../");
709
+ }
710
+ function resolveFromRoot(root, rel) {
711
+ const parts = root.replace(/\/+$/, "").split("/");
712
+ for (const segment of rel.split("/")) {
713
+ if (segment === "" || segment === ".") continue;
714
+ if (segment === "..") {
715
+ if (parts.length > 1) parts.pop();
716
+ continue;
717
+ }
718
+ parts.push(segment);
719
+ }
720
+ const joined = parts.join("/");
721
+ return joined === "" ? "/" : joined;
722
+ }
723
+ function normalizeRoot(root) {
724
+ const posix = toPosix(root);
725
+ return posix.endsWith("/") ? posix : `${posix}/`;
726
+ }
727
+ function artifactLocation(file, root) {
728
+ const posix = toPosix(file);
729
+ if (!isAbsolutePath(posix)) {
730
+ if (escapesRoot(posix)) return { uri: fileUri(resolveFromRoot(root, posix)) };
731
+ return { uri: encodePath(posix), uriBaseId: URI_BASE_ID };
732
+ }
733
+ const rel = underRoot(posix, root);
734
+ if (rel !== null) return { uri: encodePath(rel), uriBaseId: URI_BASE_ID };
735
+ return { uri: fileUri(posix) };
736
+ }
737
+
738
+ // src/tracevals/reporters/ci.ts
739
+ function ciInputFromRun(report, opts = {}) {
740
+ return {
741
+ runs: [report],
742
+ traceErrors: [],
743
+ warnings: [],
744
+ root: normalizeRoot(opts.root ?? process.cwd())
745
+ };
746
+ }
747
+ function ciInputFromBatch(batch, runs, opts = {}) {
748
+ const traceErrors = [];
749
+ for (const t of batch.traces) {
750
+ if (t.error !== void 0) {
751
+ traceErrors.push({ file: t.file, error: t.error, durationMs: t.durationMs });
752
+ }
753
+ }
754
+ return {
755
+ runs,
756
+ traceErrors,
757
+ warnings: batch.warnings,
758
+ root: normalizeRoot(opts.root ?? process.cwd())
759
+ };
760
+ }
761
+ function displayPath(file, root) {
762
+ const posix = toPosix(file);
763
+ return underRoot(posix, root) ?? posix;
764
+ }
765
+ var ABSOLUTE = /^(?:[A-Za-z]:)?\//;
766
+ function locationPath(location, root) {
767
+ if (classifyRef(location.file).kind === "url") return location.file;
768
+ const posix = toPosix(location.file);
769
+ const abs = ABSOLUTE.test(posix) ? posix : `${normalizeRoot(process.cwd())}${posix}`;
770
+ return underRoot(abs, root) ?? abs;
771
+ }
772
+ function locationLabel(location) {
773
+ return location.line === void 0 ? location.file : `${location.file}:${String(location.line)}`;
774
+ }
775
+ function ruleIdFor(grader) {
776
+ return `tracevals/${grader}`;
777
+ }
778
+ var TRACE_RULE_ID = "tracevals/trace";
779
+ function traceErrorText(error) {
780
+ return `trace could not be evaluated: ${error}`;
781
+ }
782
+ function judgeFailText(result) {
783
+ if (result.outcome !== "fail" || !result.consensus) return void 0;
784
+ const reasoning = result.consensus.runs.find((run) => run.verdict)?.verdict?.reasoning ?? "";
785
+ return `AI judge: fail (confidence ${result.consensus.meanConfidence.toFixed(2)}). ${reasoning}`.trim();
786
+ }
787
+ function errorText(result) {
788
+ return result.error ?? result.skipReason ?? "eval errored";
789
+ }
790
+
791
+ // src/tracevals/reporters/github.ts
792
+ var TITLE = "manni tracevals";
793
+ function annotation(level, message, props) {
794
+ const parts = [
795
+ props.file !== void 0 ? `file=${escapeWorkflowCommandProperty(props.file)}` : void 0,
796
+ props.line !== void 0 ? `line=${String(props.line)}` : void 0,
797
+ `title=${escapeWorkflowCommandProperty(props.title)}`
798
+ ].filter((p) => p !== void 0);
799
+ return `::${level} ${parts.join(",")}::${escapeWorkflowCommandMessage(message)}`;
800
+ }
801
+ function renderGithub(input, summary) {
802
+ const lines = [];
803
+ for (const run of input.runs) {
804
+ const trace = ` (trace: ${displayPath(run.trace.file, input.root)})`;
805
+ for (const r of run.evalResults) {
806
+ const at = {
807
+ file: locationPath(r.location, input.root),
808
+ ...r.location.line === void 0 ? {} : { line: r.location.line },
809
+ title: `${TITLE}: ${r.evalName}`
810
+ };
811
+ for (const f of r.findings ?? []) {
812
+ lines.push(annotation(f.severity, f.message + trace, at));
813
+ }
814
+ const judged = judgeFailText(r);
815
+ if (judged !== void 0) {
816
+ lines.push(annotation("error", judged + trace, at));
817
+ }
818
+ if (r.outcome === "error") {
819
+ lines.push(annotation("error", errorText(r) + trace, at));
820
+ }
821
+ }
822
+ for (const w of run.warnings) {
823
+ if (input.warnings.includes(w)) continue;
824
+ lines.push(annotation("warning", w + trace, { title: TITLE }));
825
+ }
826
+ }
827
+ for (const t of input.traceErrors) {
828
+ lines.push(
829
+ annotation("error", traceErrorText(t.error), {
830
+ file: displayPath(t.file, input.root),
831
+ title: TITLE
832
+ })
833
+ );
834
+ }
835
+ for (const w of input.warnings) {
836
+ lines.push(annotation("warning", w, { title: TITLE }));
837
+ }
838
+ lines.push("", summary);
839
+ return lines.join("\n");
840
+ }
841
+
842
+ // src/tracevals/reporters/junit.ts
843
+ function attr(name, value) {
844
+ return `${name}="${xmlEscape(String(value))}"`;
845
+ }
846
+ var seconds = (ms) => (ms / 1e3).toFixed(3);
847
+ function failureText(result) {
848
+ const findings = (result.findings ?? []).map((f) => f.message);
849
+ if (findings.length > 0) return findings.join("\n");
850
+ return judgeFailText(result) ?? `${result.evalName} failed`;
851
+ }
852
+ function testcase(result, root) {
853
+ const line = result.location.line;
854
+ const open = ` <testcase ${attr("classname", displayPath(result.artifact, root))} ${attr(
855
+ "name",
856
+ result.evalName
857
+ )} ${attr("file", locationPath(result.location, root))}${line === void 0 ? "" : ` ${attr("line", line)}`} ${attr("time", seconds(result.durationMs))}`;
858
+ const type = attr("type", ruleIdFor(result.grader));
859
+ switch (result.outcome) {
860
+ case "pass":
861
+ return `${open} />`;
862
+ case "skipped":
863
+ return `${open}>
864
+ <skipped ${attr(
865
+ "message",
866
+ result.skipReason ?? "skipped"
867
+ )} />
868
+ </testcase>`;
869
+ case "error":
870
+ return `${open}>
871
+ <error ${attr(
872
+ "message",
873
+ `${result.evalName} errored`
874
+ )} ${type}>${xmlEscape(errorText(result))}</error>
875
+ </testcase>`;
876
+ case "needs-review":
877
+ return `${open}>
878
+ <skipped ${attr(
879
+ "message",
880
+ "needs human review"
881
+ )} />
882
+ </testcase>`;
883
+ case "fail":
884
+ return `${open}>
885
+ <failure ${attr(
886
+ "message",
887
+ `${result.evalName} failed`
888
+ )} ${type}>${xmlEscape(failureText(result))}</failure>
889
+ </testcase>`;
890
+ }
891
+ }
892
+ function counts(run) {
893
+ const r = run.evalResults;
894
+ return {
895
+ tests: r.length,
896
+ failures: r.filter((x) => x.outcome === "fail").length,
897
+ errors: r.filter((x) => x.outcome === "error").length,
898
+ skipped: r.filter((x) => x.outcome === "skipped" || x.outcome === "needs-review").length
899
+ };
900
+ }
901
+ function suiteOpen(name, c, ms) {
902
+ return ` <testsuite ${attr("name", name)} ${attr("tests", c.tests)} ${attr(
903
+ "failures",
904
+ c.failures
905
+ )} ${attr("errors", c.errors)} ${attr("skipped", c.skipped)} ${attr(
906
+ "time",
907
+ seconds(ms)
908
+ )}>`;
909
+ }
910
+ function renderJunit(input) {
911
+ const total = { tests: 0, failures: 0, errors: 0, skipped: 0 };
912
+ const suites = [];
913
+ for (const run of input.runs) {
914
+ const c = counts(run);
915
+ total.tests += c.tests;
916
+ total.failures += c.failures;
917
+ total.errors += c.errors;
918
+ suites.push(
919
+ [
920
+ suiteOpen(displayPath(run.trace.file, input.root), c, run.durationMs),
921
+ ...run.evalResults.map((r) => testcase(r, input.root)),
922
+ " </testsuite>"
923
+ ].join("\n")
924
+ );
925
+ }
926
+ for (const t of input.traceErrors) {
927
+ total.tests += 1;
928
+ total.errors += 1;
929
+ const name = displayPath(t.file, input.root);
930
+ suites.push(
931
+ [
932
+ suiteOpen(name, { tests: 1, failures: 0, errors: 1, skipped: 0 }, t.durationMs),
933
+ ` <testcase ${attr("classname", name)} ${attr("name", "trace")} ${attr(
934
+ "time",
935
+ seconds(t.durationMs)
936
+ )}>`,
937
+ ` <error ${attr("message", "trace could not be evaluated")} ${attr(
938
+ "type",
939
+ TRACE_RULE_ID
940
+ )}>${xmlEscape(t.error)}</error>`,
941
+ " </testcase>",
942
+ " </testsuite>"
943
+ ].join("\n")
944
+ );
945
+ }
946
+ return [
947
+ '<?xml version="1.0" encoding="UTF-8"?>',
948
+ `<testsuites ${attr("name", "manni-tracevals")} ${attr("tests", total.tests)} ${attr(
949
+ "failures",
950
+ total.failures
951
+ )} ${attr("errors", total.errors)}>`,
952
+ ...suites,
953
+ "</testsuites>",
954
+ ""
955
+ ].join("\n");
956
+ }
957
+
958
+ // src/tracevals/reporters/coverage.ts
959
+ function coverageLocation(entry) {
960
+ if (entry.resolved) return entry.path ?? entry.note ?? "";
961
+ if (entry.availability === "offered-not-used") {
962
+ return entry.note ?? "offered, never used";
963
+ }
964
+ return entry.note ?? `not found (${entry.tried.length} location(s) tried)`;
965
+ }
966
+ function availabilityTag(entry) {
967
+ if (entry.availability === "not-offered") return "not offered";
968
+ if (entry.availability === "offered-not-used") return "offered, unused";
969
+ return void 0;
970
+ }
971
+ function availabilityLines(report) {
972
+ if (!report.recorded) {
973
+ return [
974
+ "no availability roster in this trace, so what the session was offered is unknown"
975
+ ];
976
+ }
977
+ const lines = [
978
+ `${report.skills.offered} skill(s) offered, ${report.skills.used} used, ${report.skills.unused} never used`,
979
+ `${report.agents.offered} agent(s) offered, ${report.agents.used} used, ${report.agents.unused} never used`
980
+ ];
981
+ if (!report.listed && report.skills.unused + report.agents.unused > 0) {
982
+ lines.push("pass --report-unused-artifacts to list them");
983
+ }
984
+ return lines;
985
+ }
986
+ function coverageStaleness(entry) {
987
+ if (entry.stale !== true) return "";
988
+ if (entry.contentCheck?.status === "mismatch") {
989
+ return "changed since the session started (session manifest sha256)";
990
+ }
991
+ return entry.modifiedAt !== void 0 ? `modified after the session ended (${entry.modifiedAt})` : "modified after the session ended";
992
+ }
993
+ function manifestLines(report) {
994
+ const lines = [
995
+ `${report.path} captured ${report.capturedAt}${report.gitSha !== void 0 ? ` at ${report.gitSha.slice(0, 12)}` : ""}`,
996
+ `${report.matched} artifact(s) unchanged, ${report.changed} changed, ${report.unrecorded} not recorded`
997
+ ];
998
+ if (report.unrecorded > 0) {
999
+ lines.push(
1000
+ "artifacts the manifest did not record keep the mtime heuristic"
1001
+ );
1002
+ }
1003
+ return lines;
1004
+ }
1005
+
1006
+ // src/tracevals/reporters/pretty.ts
1007
+ var OUTCOME_LABEL = {
1008
+ pass: "PASS",
1009
+ fail: "FAIL",
1010
+ error: "ERROR",
1011
+ "needs-review": "REVIEW",
1012
+ skipped: "SKIP"
1013
+ };
1014
+ function paintFor(pc, outcome) {
1015
+ switch (outcome) {
1016
+ case "pass":
1017
+ return pc.green;
1018
+ case "fail":
1019
+ case "error":
1020
+ return pc.red;
1021
+ case "needs-review":
1022
+ return pc.yellow;
1023
+ default:
1024
+ return pc.dim;
1025
+ }
1026
+ }
1027
+ function coverageMark(pc, entry) {
1028
+ if (entry.resolved) return pc.green("\u2713");
1029
+ if (entry.availability === "offered-not-used") return pc.dim("\xB7");
1030
+ return pc.yellow("\u25CB");
1031
+ }
1032
+ function renderPretty(report, opts = {}) {
1033
+ const pc = palette(opts.color === true);
1034
+ const lines = [];
1035
+ const t = report.trace;
1036
+ lines.push(pc.bold(`manni tracevals \u2014 ${t.file}`));
1037
+ lines.push(
1038
+ pc.dim(
1039
+ `${t.source} \xB7 ${t.model ?? "unknown model"} \xB7 ${t.turnCount} turn(s) \xB7 ${t.cwd}`
1040
+ )
1041
+ );
1042
+ lines.push("");
1043
+ for (const result of report.evalResults) {
1044
+ const paint = paintFor(pc, result.outcome);
1045
+ const label = paint(OUTCOME_LABEL[result.outcome].padEnd(6));
1046
+ const name = `${result.artifactName} \u203A ${result.evalName}`;
1047
+ lines.push(
1048
+ ` ${label} ${name}${result.implicit ? pc.dim(" (implicit)") : ""} ${pc.dim(locationLabel(result.location))}`
1049
+ );
1050
+ for (const finding2 of result.findings ?? []) {
1051
+ if (result.outcome !== "pass" || finding2.severity !== "error") {
1052
+ lines.push(` ${pc.dim(`[${finding2.severity}]`)} ${finding2.message}`);
1053
+ }
1054
+ }
1055
+ if (result.error) lines.push(` ${pc.red(result.error)}`);
1056
+ if (result.skipReason) lines.push(` ${pc.dim(result.skipReason)}`);
1057
+ if (result.consensus && result.outcome !== "pass") {
1058
+ const v = result.consensus.votes;
1059
+ lines.push(
1060
+ ` ${pc.dim(`votes pass:${v.pass} fail:${v.fail} partial:${v.partial} error:${v.error} \xB7 confidence ${result.consensus.meanConfidence.toFixed(2)}`)}`
1061
+ );
1062
+ const observed = result.consensus.runs.find((r) => r.verdict)?.verdict?.observed;
1063
+ if (observed) lines.push(` ${pc.dim(observed)}`);
1064
+ }
1065
+ }
1066
+ lines.push("");
1067
+ lines.push(pc.bold("Artifact coverage"));
1068
+ for (const entry of report.coverage) {
1069
+ const tag = availabilityTag(entry);
1070
+ const location = coverageLocation(entry);
1071
+ const stale = coverageStaleness(entry);
1072
+ lines.push(
1073
+ [
1074
+ ` ${coverageMark(pc, entry)} ${entry.kind}: ${entry.ref}`,
1075
+ tag === void 0 ? "" : pc.yellow(`[${tag}]`),
1076
+ location ? pc.dim(location) : "",
1077
+ stale ? pc.yellow(`\u26A0 ${stale}`) : ""
1078
+ ].filter((part) => part !== "").join(" ")
1079
+ );
1080
+ }
1081
+ if (report.manifest !== void 0) {
1082
+ lines.push("");
1083
+ lines.push(pc.bold("Session manifest"));
1084
+ for (const line of manifestLines(report.manifest)) {
1085
+ lines.push(` ${pc.dim(line)}`);
1086
+ }
1087
+ }
1088
+ lines.push("");
1089
+ lines.push(pc.bold("Availability"));
1090
+ for (const line of availabilityLines(report.availability)) {
1091
+ lines.push(` ${pc.dim(line)}`);
1092
+ }
1093
+ if (report.warnings.length > 0) {
1094
+ lines.push("");
1095
+ lines.push(pc.bold("Warnings"));
1096
+ for (const warning of report.warnings) {
1097
+ lines.push(` ${pc.yellow("!")} ${warning}`);
1098
+ }
1099
+ }
1100
+ const s = report.summary;
1101
+ lines.push("");
1102
+ lines.push(
1103
+ pc.bold(
1104
+ `${s.total} eval(s): ${pc.green(`${s.pass} pass`)}, ${s.fail > 0 ? pc.red(`${s.fail} fail`) : `${s.fail} fail`}, ${s.error} error, ${s.needsReview} needs-review, ${s.skipped} skipped`
1105
+ )
1106
+ );
1107
+ const weighted = report.evalResults.some(
1108
+ (r) => r.weight !== void 0 && r.weight !== 1
1109
+ );
1110
+ if (weighted) {
1111
+ lines.push(
1112
+ pc.dim(`weighted pass rate ${(s.passRate * 100).toFixed(0)}%`)
1113
+ );
1114
+ }
1115
+ if (report.turns > 0) {
1116
+ lines.push(pc.dim(`judge turns ${report.turns}`));
1117
+ }
1118
+ return lines.join("\n");
1119
+ }
1120
+
1121
+ // src/tracevals/reporters/markdown.ts
1122
+ var cell3 = (value) => value.replace(/\|/g, "\\|");
1123
+ function renderMarkdown(report) {
1124
+ const lines = [];
1125
+ const t = report.trace;
1126
+ lines.push(`# manni tracevals report`);
1127
+ lines.push("");
1128
+ lines.push(`- **Trace**: \`${t.file}\``);
1129
+ lines.push(`- **Source**: ${t.source} (${t.model ?? "unknown model"})`);
1130
+ lines.push(`- **Project**: \`${t.cwd}\``);
1131
+ lines.push(`- **Turns**: ${t.turnCount}`);
1132
+ lines.push("");
1133
+ lines.push(`## Evals`);
1134
+ lines.push("");
1135
+ lines.push(`| Outcome | Artifact | Eval | Grader | Detail |`);
1136
+ lines.push(`|---|---|---|---|---|`);
1137
+ for (const r of report.evalResults) {
1138
+ const detail2 = r.error ?? r.skipReason ?? r.findings?.map((f) => f.message).join("; ") ?? (r.consensus ? `votes p:${r.consensus.votes.pass} f:${r.consensus.votes.fail} e:${r.consensus.votes.error}` : "");
1139
+ lines.push(
1140
+ `| ${r.outcome} | ${cell3(r.artifactName)} | ${cell3(r.evalName)}${r.implicit ? " (implicit)" : ""} \`${cell3(locationLabel(r.location))}\` | ${cell3(r.grader)} | ${cell3(detail2)} |`
1141
+ );
1142
+ }
1143
+ lines.push("");
1144
+ lines.push(`## Artifact coverage`);
1145
+ lines.push("");
1146
+ lines.push(`| Resolved | Kind | Ref | Availability | Where |`);
1147
+ lines.push(`|---|---|---|---|---|`);
1148
+ for (const entry of report.coverage) {
1149
+ const location = cell3(coverageLocation(entry));
1150
+ const where = entry.resolved && entry.path !== void 0 ? `\`${location}\`` : location;
1151
+ const resolved = entry.availability === "offered-not-used" ? "n/a" : entry.resolved ? "yes" : "no";
1152
+ const stale = coverageStaleness(entry);
1153
+ const cellText = stale ? `${where}${where ? " \u2014 " : ""}\u26A0 ${cell3(stale)}` : where;
1154
+ lines.push(
1155
+ `| ${resolved} | ${cell3(entry.kind)} | ${cell3(entry.ref)} | ${cell3(entry.availability ?? "")} | ${cellText} |`
1156
+ );
1157
+ }
1158
+ lines.push("");
1159
+ if (report.manifest !== void 0) {
1160
+ lines.push(`## Session manifest`);
1161
+ lines.push("");
1162
+ for (const line of manifestLines(report.manifest)) {
1163
+ lines.push(`- ${cell3(line)}`);
1164
+ }
1165
+ lines.push("");
1166
+ }
1167
+ lines.push(`## Availability`);
1168
+ lines.push("");
1169
+ for (const line of availabilityLines(report.availability)) {
1170
+ lines.push(`- ${line}`);
1171
+ }
1172
+ lines.push("");
1173
+ if (report.warnings.length > 0) {
1174
+ lines.push(`## Warnings`);
1175
+ lines.push("");
1176
+ for (const warning of report.warnings) lines.push(`- ${warning}`);
1177
+ lines.push("");
1178
+ }
1179
+ const s = report.summary;
1180
+ lines.push(
1181
+ `**${s.total} eval(s)** \u2014 ${s.pass} pass, ${s.fail} fail, ${s.error} error, ${s.needsReview} needs-review, ${s.skipped} skipped.`
1182
+ );
1183
+ return lines.join("\n");
1184
+ }
1185
+
1186
+ // src/tracevals/reporters/sarif.ts
1187
+ var SARIF_SCHEMA = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json";
1188
+ var LEVELS = {
1189
+ error: "error",
1190
+ warning: "warning",
1191
+ notice: "note"
1192
+ };
1193
+ function declaringLocation(r, root) {
1194
+ if (classifyRef(r.location.file).kind === "url") return { uri: r.location.file };
1195
+ return artifactLocation(locationPath(r.location, root), root);
1196
+ }
1197
+ function renderSarif(input) {
1198
+ const rules = /* @__PURE__ */ new Map();
1199
+ const results = [];
1200
+ const declare = (id, name, text, level) => {
1201
+ if (!rules.has(id)) {
1202
+ rules.set(id, {
1203
+ id,
1204
+ name,
1205
+ shortDescription: { text },
1206
+ defaultConfiguration: { level }
1207
+ });
1208
+ }
1209
+ };
1210
+ const push = (run, r, level, text) => {
1211
+ const id = ruleIdFor(r.grader);
1212
+ declare(
1213
+ id,
1214
+ r.grader,
1215
+ r.grader === "ai" ? "manni tracevals AI judge" : `manni tracevals ${r.grader} grader`,
1216
+ level
1217
+ );
1218
+ results.push({
1219
+ ruleId: id,
1220
+ level,
1221
+ message: { text },
1222
+ locations: [
1223
+ {
1224
+ physicalLocation: {
1225
+ artifactLocation: declaringLocation(r, input.root),
1226
+ // SARIF regions are 1-based and need `startLine`, so a location
1227
+ // with no line has no region rather than an invented line 1.
1228
+ ...r.location.line === void 0 ? {} : { region: { startLine: r.location.line } }
1229
+ }
1230
+ }
1231
+ ],
1232
+ properties: {
1233
+ eval: r.evalName,
1234
+ artifactName: r.artifactName,
1235
+ artifactType: r.artifactType,
1236
+ trace: displayPath(run.trace.file, input.root),
1237
+ ...run.trace.sessionId !== void 0 ? { sessionId: run.trace.sessionId } : {}
1238
+ }
1239
+ });
1240
+ };
1241
+ for (const run of input.runs) {
1242
+ for (const r of run.evalResults) {
1243
+ for (const f of r.findings ?? []) {
1244
+ push(run, r, LEVELS[f.severity], f.message);
1245
+ }
1246
+ const judged = judgeFailText(r);
1247
+ if (judged !== void 0) push(run, r, "error", judged);
1248
+ if (r.outcome === "error") push(run, r, "error", errorText(r));
1249
+ }
1250
+ }
1251
+ for (const t of input.traceErrors) {
1252
+ declare(TRACE_RULE_ID, "trace", "manni tracevals could not read the trace", "error");
1253
+ results.push({
1254
+ ruleId: TRACE_RULE_ID,
1255
+ level: "error",
1256
+ message: { text: traceErrorText(t.error) },
1257
+ locations: [
1258
+ {
1259
+ physicalLocation: {
1260
+ artifactLocation: artifactLocation(t.file, input.root)
1261
+ }
1262
+ }
1263
+ ]
1264
+ });
1265
+ }
1266
+ const log = {
1267
+ $schema: SARIF_SCHEMA,
1268
+ version: "2.1.0",
1269
+ runs: [
1270
+ {
1271
+ tool: {
1272
+ driver: {
1273
+ name: "manni-tracevals",
1274
+ informationUri: "https://hawkeyexl.github.io/manni/tracevals/",
1275
+ rules: [...rules.values()]
1276
+ }
1277
+ },
1278
+ originalUriBaseIds: { [URI_BASE_ID]: { uri: fileUri(input.root) } },
1279
+ results
1280
+ }
1281
+ ]
1282
+ };
1283
+ return `${JSON.stringify(log, null, 2)}
1284
+ `;
1285
+ }
1286
+
1287
+ // src/tracevals/reporters/format.ts
1288
+ var REPORT_FORMATS = [
1289
+ "pretty",
1290
+ "json",
1291
+ "markdown",
1292
+ "github",
1293
+ "sarif",
1294
+ "junit"
1295
+ ];
1296
+ var CALIBRATE_FORMATS = ["pretty", "json", "markdown"];
1297
+ var SUMMARY_FORMATS = ["pretty", "json"];
1298
+ function parseFormat(value, allowed, flag) {
1299
+ if (!allowed.includes(value)) {
1300
+ throw new TracevalsError(
1301
+ `${flag} must be one of ${allowed.join(" | ")}, got "${value}"`
1302
+ );
1303
+ }
1304
+ return value;
1305
+ }
1306
+
1307
+ // src/tracevals/reporters/index.ts
1308
+ function render(report, format, opts = {}) {
1309
+ parseFormat(format, REPORT_FORMATS, "format");
1310
+ switch (format) {
1311
+ case "json":
1312
+ return JSON.stringify(report, null, 2);
1313
+ case "markdown":
1314
+ return renderMarkdown(report);
1315
+ case "pretty":
1316
+ return renderPretty(report, opts);
1317
+ case "github":
1318
+ return renderGithub(ciInputFromRun(report), renderMarkdown(report));
1319
+ case "sarif":
1320
+ return renderSarif(ciInputFromRun(report));
1321
+ case "junit":
1322
+ return renderJunit(ciInputFromRun(report));
1323
+ }
1324
+ }
1325
+ function renderBatch(report, format, runs, opts = {}) {
1326
+ parseFormat(format, REPORT_FORMATS, "format");
1327
+ const ci = () => {
1328
+ if (runs === void 0) {
1329
+ throw new TracevalsError(
1330
+ `format "${format}" annotates findings, which live in the per-trace reports; pass them as renderBatch's third argument`
1331
+ );
1332
+ }
1333
+ return ciInputFromBatch(report, runs);
1334
+ };
1335
+ switch (format) {
1336
+ case "json":
1337
+ return JSON.stringify(report, null, 2);
1338
+ case "markdown":
1339
+ return renderBatchMarkdown(report);
1340
+ case "pretty":
1341
+ return renderBatchPretty(report, opts);
1342
+ case "github":
1343
+ return renderGithub(ci(), renderBatchMarkdown(report));
1344
+ case "sarif":
1345
+ return renderSarif(ci());
1346
+ case "junit":
1347
+ return renderJunit(ci());
1348
+ }
1349
+ }
1350
+ function renderCalibration(report, format, opts = {}) {
1351
+ const narrowed = parseFormat(format, CALIBRATE_FORMATS, "format");
1352
+ switch (narrowed) {
1353
+ case "json":
1354
+ return JSON.stringify(report, null, 2);
1355
+ case "markdown":
1356
+ return renderCalibrationMarkdown(report);
1357
+ case "pretty":
1358
+ return renderCalibrationPretty(report, opts);
1359
+ }
1360
+ }
1361
+
1362
+ // src/tracevals/commands/run.ts
1363
+ import { writeFile } from "node:fs/promises";
1364
+ import { resolve as resolve5 } from "node:path";
1365
+
1366
+ // src/tracevals/trace/claude.ts
1367
+ import { readdir, readFile } from "node:fs/promises";
1368
+ import { basename as basename3, dirname, join, resolve } from "node:path";
1369
+
1370
+ // src/tracevals/trace/detect.ts
1371
+ var SUPPORTED = "supported formats: Claude Code session files (~/.claude/projects/**.jsonl) and claude -p stream-json";
1372
+ function detectFormat(line) {
1373
+ let rec;
1374
+ try {
1375
+ rec = JSON.parse(line);
1376
+ } catch {
1377
+ throw new TracevalsError(`trace is not JSONL (${SUPPORTED})`);
1378
+ }
1379
+ if (typeof rec !== "object" || rec === null) {
1380
+ throw new TracevalsError(`unrecognized trace format (${SUPPORTED})`);
1381
+ }
1382
+ if ("parentUuid" in rec || "sessionId" in rec) return "claude-session";
1383
+ if ("session_id" in rec) return "claude-stream";
1384
+ throw new TracevalsError(`unrecognized trace format (${SUPPORTED})`);
1385
+ }
1386
+ function detectContentFormat(content) {
1387
+ let sawParseable = false;
1388
+ for (const line of content.split("\n")) {
1389
+ const trimmed = line.trim();
1390
+ if (!trimmed) continue;
1391
+ let rec;
1392
+ try {
1393
+ rec = JSON.parse(trimmed);
1394
+ } catch {
1395
+ continue;
1396
+ }
1397
+ if (typeof rec !== "object" || rec === null) continue;
1398
+ sawParseable = true;
1399
+ const r = rec;
1400
+ if ("parentUuid" in r || "sessionId" in r) return "claude-session";
1401
+ if ("session_id" in r) return "claude-stream";
1402
+ }
1403
+ throw new TracevalsError(
1404
+ sawParseable ? `unrecognized trace format (${SUPPORTED})` : `empty or unparseable trace file (${SUPPORTED})`
1405
+ );
1406
+ }
1407
+
1408
+ // src/tracevals/trace/claude.ts
1409
+ async function parseTraceFile(filePath) {
1410
+ const absolute = resolve(filePath);
1411
+ const content = await readFile(absolute, "utf-8");
1412
+ const trace = parseTraceContent(content, absolute);
1413
+ await mergeSidecarBranches(trace, absolute);
1414
+ return trace;
1415
+ }
1416
+ function parseTraceContent(content, filePath) {
1417
+ const format = detectContentFormat(content);
1418
+ const { records, unparseable } = parseLines(content);
1419
+ const trace = format === "claude-session" ? parseSession(records, filePath) : parseStream(records, filePath);
1420
+ if (unparseable > 0) {
1421
+ trace.warnings.push(
1422
+ `${unparseable} unparseable JSONL line(s) were skipped`
1423
+ );
1424
+ }
1425
+ return trace;
1426
+ }
1427
+ function parseLines(content) {
1428
+ const records = [];
1429
+ let unparseable = 0;
1430
+ for (const line of content.split("\n")) {
1431
+ const trimmed = line.trim();
1432
+ if (!trimmed) continue;
1433
+ try {
1434
+ const rec = JSON.parse(trimmed);
1435
+ if (typeof rec === "object" && rec !== null) records.push(rec);
1436
+ else unparseable += 1;
1437
+ } catch {
1438
+ unparseable += 1;
1439
+ }
1440
+ }
1441
+ return { records, unparseable };
1442
+ }
1443
+ var SESSION_META_TYPES = /* @__PURE__ */ new Set([
1444
+ "queue-operation",
1445
+ "attachment",
1446
+ "last-prompt",
1447
+ "custom-title",
1448
+ "summary",
1449
+ "mode",
1450
+ "pr-link"
1451
+ ]);
1452
+ var COMMAND_NAME_RE = /<command-name>([^<]+)<\/command-name>/;
1453
+ var COMMAND_ARGS_RE = /<command-args>([^<]*)<\/command-args>/;
1454
+ function newTrace(filePath) {
1455
+ return {
1456
+ source: "claude-code",
1457
+ file: filePath,
1458
+ cwd: "",
1459
+ events: [],
1460
+ toolCalls: [],
1461
+ skillInvocations: [],
1462
+ agentSpawns: [],
1463
+ subagentBranches: [],
1464
+ availability: newAvailability(),
1465
+ fileAccesses: [],
1466
+ userMessages: [],
1467
+ assistantTexts: [],
1468
+ turnCount: 0,
1469
+ warnings: []
1470
+ };
1471
+ }
1472
+ function pushEvent(trace, event) {
1473
+ const index = trace.events.length;
1474
+ trace.events.push({ ...event, index });
1475
+ return index;
1476
+ }
1477
+ function plainMessageText(rec) {
1478
+ const content = rec.message?.content;
1479
+ return typeof content === "string" ? content : void 0;
1480
+ }
1481
+ function resolveBranch(branches, parentUuid, rootText) {
1482
+ if (typeof parentUuid !== "string") return void 0;
1483
+ const inherited = branches.resolved.get(parentUuid);
1484
+ if (inherited !== void 0) return inherited;
1485
+ const pending = branches.spawns.get(parentUuid);
1486
+ if (pending === void 0 || pending.length === 0) return void 0;
1487
+ const byPrompt = rootText !== void 0 ? pending.find((b) => !b.claimed && b.prompt === rootText) : void 0;
1488
+ const branch = byPrompt ?? pending.find((b) => !b.claimed) ?? pending[0];
1489
+ if (branch === void 0) return void 0;
1490
+ branch.claimed = true;
1491
+ return branch.toolUseId;
1492
+ }
1493
+ function parseSession(records, filePath) {
1494
+ const trace = newTrace(filePath);
1495
+ const branches = { spawns: /* @__PURE__ */ new Map(), resolved: /* @__PURE__ */ new Map() };
1496
+ const availability = newReplay();
1497
+ const uuids = /* @__PURE__ */ new Set();
1498
+ for (const rec of records) {
1499
+ if (typeof rec.uuid === "string") uuids.add(rec.uuid);
1500
+ }
1501
+ const continuations = [];
1502
+ let sawMessage = false;
1503
+ let compactions = 0;
1504
+ let inputTokens = 0;
1505
+ let outputTokens = 0;
1506
+ let sawUsage = false;
1507
+ for (const rec of records) {
1508
+ const type = rec.type;
1509
+ const timestamp = rec.timestamp;
1510
+ if (timestamp) {
1511
+ trace.startedAt ??= timestamp;
1512
+ trace.endedAt = timestamp;
1513
+ }
1514
+ if (rec.sessionId) trace.sessionId ??= rec.sessionId;
1515
+ if (rec.subtype === "compact_boundary") compactions += 1;
1516
+ if (type !== void 0 && SESSION_META_TYPES.has(type)) {
1517
+ if (type === "summary" && !sawMessage && typeof rec.leafUuid === "string" && !uuids.has(rec.leafUuid)) {
1518
+ continuations.push(rec.leafUuid);
1519
+ }
1520
+ const index = pushEvent(trace, { kind: "meta", timestamp, raw: rec });
1521
+ if (type === "attachment" && applyAvailabilityRecord(availability, rec.attachment, index)) {
1522
+ availability.roster.recorded = true;
1523
+ }
1524
+ continue;
1525
+ }
1526
+ const sidechain = rec.isSidechain === true;
1527
+ if (!sidechain && typeof rec.cwd === "string") trace.cwd = rec.cwd;
1528
+ if (!sidechain && typeof rec.gitBranch === "string") {
1529
+ trace.gitBranch = rec.gitBranch;
1530
+ }
1531
+ const context = { sidechain, timestamp, branches };
1532
+ if (typeof rec.uuid === "string") context.uuid = rec.uuid;
1533
+ if (sidechain) {
1534
+ const branchId = resolveBranch(
1535
+ branches,
1536
+ rec.parentUuid,
1537
+ plainMessageText(rec)
1538
+ );
1539
+ if (branchId !== void 0) {
1540
+ context.branchId = branchId;
1541
+ if (context.uuid) branches.resolved.set(context.uuid, branchId);
1542
+ }
1543
+ }
1544
+ const message = rec.message;
1545
+ if (type === "user" && message) {
1546
+ sawMessage = true;
1547
+ handleUserMessage(trace, message, context, rec);
1548
+ } else if (type === "assistant" && message) {
1549
+ sawMessage = true;
1550
+ const usage = handleAssistantMessage(trace, message, context, rec);
1551
+ if (usage && !sidechain) {
1552
+ inputTokens += usage.input;
1553
+ outputTokens += usage.output;
1554
+ sawUsage = true;
1555
+ }
1556
+ } else {
1557
+ pushEvent(trace, { kind: "system", timestamp, raw: rec });
1558
+ }
1559
+ }
1560
+ if (sawUsage) trace.usage = { inputTokens, outputTokens };
1561
+ for (const leaf of continuations) {
1562
+ trace.warnings.push(
1563
+ `this trace is a fragment of a longer session: it resumes an earlier one recorded elsewhere (summary leafUuid ${leaf} is not in this file), so evals see only the turns recorded here`
1564
+ );
1565
+ }
1566
+ if (compactions > 0) {
1567
+ trace.warnings.push(
1568
+ `the conversation was compacted ${compactions} time(s): the agent stopped seeing the turns before each boundary, so early transcript content is not what it was working from`
1569
+ );
1570
+ }
1571
+ trace.availability = availability.roster;
1572
+ trace.subagentBranches = buildBranches(trace);
1573
+ return trace;
1574
+ }
1575
+ function buildBranches(trace, sidecars = /* @__PURE__ */ new Map()) {
1576
+ const ranges = /* @__PURE__ */ new Map();
1577
+ for (const event of trace.events) {
1578
+ const id = event.branchId;
1579
+ if (id === void 0) continue;
1580
+ const range = ranges.get(id);
1581
+ if (range === void 0) ranges.set(id, { min: event.index, max: event.index });
1582
+ else range.max = Math.max(range.max, event.index);
1583
+ }
1584
+ if (ranges.size === 0) return [];
1585
+ const spawnFor = /* @__PURE__ */ new Map();
1586
+ for (const spawn of trace.agentSpawns) {
1587
+ if (spawn.toolUseId) spawnFor.set(spawn.toolUseId, spawn);
1588
+ }
1589
+ const parentOf = /* @__PURE__ */ new Map();
1590
+ for (const id of ranges.keys()) {
1591
+ const spawn = spawnFor.get(id);
1592
+ parentOf.set(
1593
+ id,
1594
+ spawn ? trace.events[spawn.index]?.branchId : void 0
1595
+ );
1596
+ }
1597
+ const depthOf = (id) => {
1598
+ let depth = 1;
1599
+ let cursor = parentOf.get(id);
1600
+ const seen = /* @__PURE__ */ new Set([id]);
1601
+ while (cursor !== void 0 && !seen.has(cursor)) {
1602
+ seen.add(cursor);
1603
+ depth += 1;
1604
+ cursor = parentOf.get(cursor);
1605
+ }
1606
+ return depth;
1607
+ };
1608
+ const branches = [];
1609
+ for (const [id, range] of ranges) {
1610
+ const spawn = spawnFor.get(id);
1611
+ const sidecar = sidecars.get(id);
1612
+ const branch = {
1613
+ branchId: id,
1614
+ agentType: sidecar?.agentType ?? spawn?.subagentType ?? id,
1615
+ origin: sidecar ? "sidecar" : "inline",
1616
+ spawnDepth: sidecar?.spawnDepth ?? depthOf(id),
1617
+ spawnIndex: spawn?.index ?? range.min,
1618
+ startIndex: range.min,
1619
+ endIndex: range.max + 1
1620
+ };
1621
+ const description = sidecar?.description ?? spawn?.description;
1622
+ if (description !== void 0) branch.description = description;
1623
+ if (sidecar?.agentId) branch.agentId = sidecar.agentId;
1624
+ if (sidecar?.parentAgentId) branch.parentAgentId = sidecar.parentAgentId;
1625
+ if (sidecar?.file) branch.file = sidecar.file;
1626
+ branches.push(branch);
1627
+ }
1628
+ branches.sort((a, b) => b.spawnDepth - a.spawnDepth);
1629
+ const byId = new Map(branches.map((b) => [b.branchId, b]));
1630
+ for (const branch of branches) {
1631
+ const parentId = parentOf.get(branch.branchId);
1632
+ const parent = parentId === void 0 ? void 0 : byId.get(parentId);
1633
+ if (parent === void 0) continue;
1634
+ parent.startIndex = Math.min(parent.startIndex, branch.startIndex);
1635
+ parent.endIndex = Math.max(parent.endIndex, branch.endIndex);
1636
+ }
1637
+ branches.sort((a, b) => a.startIndex - b.startIndex);
1638
+ return branches;
1639
+ }
1640
+ function handleUserMessage(trace, message, context, raw) {
1641
+ const { sidechain, timestamp, branchId } = context;
1642
+ const content = message.content;
1643
+ if (typeof content === "string") {
1644
+ const index = pushEvent(trace, {
1645
+ kind: "user",
1646
+ timestamp,
1647
+ text: content,
1648
+ sidechain,
1649
+ branchId,
1650
+ raw
1651
+ });
1652
+ if (sidechain) return;
1653
+ const command = COMMAND_NAME_RE.exec(content);
1654
+ if (command?.[1]) {
1655
+ const invocation = {
1656
+ name: command[1].replace(/^\//, ""),
1657
+ via: "command-injection",
1658
+ index
1659
+ };
1660
+ const args = COMMAND_ARGS_RE.exec(content)?.[1]?.trim();
1661
+ if (args) invocation.args = args;
1662
+ trace.skillInvocations.push(invocation);
1663
+ }
1664
+ trace.userMessages.push(content);
1665
+ trace.turnCount += 1;
1666
+ return;
1667
+ }
1668
+ if (Array.isArray(content)) {
1669
+ let hasToolResult = false;
1670
+ const texts = [];
1671
+ for (const block of content) {
1672
+ if (block?.type === "tool_result") hasToolResult = true;
1673
+ if (block?.type === "text" && typeof block.text === "string") {
1674
+ texts.push(block.text);
1675
+ }
1676
+ }
1677
+ if (hasToolResult) {
1678
+ pushEvent(trace, {
1679
+ kind: "tool_result",
1680
+ timestamp,
1681
+ sidechain,
1682
+ branchId,
1683
+ raw
1684
+ });
1685
+ return;
1686
+ }
1687
+ const text = texts.join("\n");
1688
+ pushEvent(trace, {
1689
+ kind: "user",
1690
+ timestamp,
1691
+ text,
1692
+ sidechain,
1693
+ branchId,
1694
+ raw
1695
+ });
1696
+ if (!sidechain && text) {
1697
+ trace.userMessages.push(text);
1698
+ trace.turnCount += 1;
1699
+ }
1700
+ }
1701
+ }
1702
+ function handleAssistantMessage(trace, message, context, raw) {
1703
+ const { sidechain, timestamp, branchId } = context;
1704
+ if (!sidechain && typeof message.model === "string") {
1705
+ trace.model ??= message.model;
1706
+ }
1707
+ const content = message.content;
1708
+ if (Array.isArray(content)) {
1709
+ for (const block of content) {
1710
+ if (block?.type === "text" && typeof block.text === "string") {
1711
+ pushEvent(trace, {
1712
+ kind: "assistant",
1713
+ timestamp,
1714
+ text: block.text,
1715
+ sidechain,
1716
+ branchId,
1717
+ raw
1718
+ });
1719
+ if (!sidechain) trace.assistantTexts.push(block.text);
1720
+ } else if (block?.type === "tool_use") {
1721
+ extractToolUse(trace, block, context);
1722
+ }
1723
+ }
1724
+ }
1725
+ const usage = message.usage;
1726
+ if (usage) {
1727
+ return {
1728
+ input: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
1729
+ output: typeof usage.output_tokens === "number" ? usage.output_tokens : 0
1730
+ };
1731
+ }
1732
+ return void 0;
1733
+ }
1734
+ function extractToolUse(trace, block, context) {
1735
+ const { sidechain, timestamp, branchId } = context;
1736
+ const name = block.name;
1737
+ const input = block.input ?? {};
1738
+ const toolUseId = typeof block.id === "string" ? block.id : void 0;
1739
+ const index = pushEvent(trace, {
1740
+ kind: "tool_call",
1741
+ timestamp,
1742
+ toolName: name,
1743
+ sidechain,
1744
+ branchId,
1745
+ raw: block
1746
+ });
1747
+ const call = { name, input, index, sidechain };
1748
+ if (timestamp) call.timestamp = timestamp;
1749
+ if (branchId) call.branchId = branchId;
1750
+ trace.toolCalls.push(call);
1751
+ switch (name) {
1752
+ case "Skill": {
1753
+ if (typeof input.skill === "string") {
1754
+ const invocation = {
1755
+ name: input.skill,
1756
+ via: "skill-tool",
1757
+ index
1758
+ };
1759
+ if (toolUseId) invocation.toolUseId = toolUseId;
1760
+ if (typeof input.args === "string" && input.args) {
1761
+ invocation.args = input.args;
1762
+ }
1763
+ trace.skillInvocations.push(invocation);
1764
+ }
1765
+ break;
1766
+ }
1767
+ case "Agent": {
1768
+ if (typeof input.subagent_type === "string") {
1769
+ const spawn = {
1770
+ subagentType: input.subagent_type,
1771
+ index
1772
+ };
1773
+ if (typeof input.description === "string") {
1774
+ spawn.description = input.description;
1775
+ }
1776
+ if (toolUseId) spawn.toolUseId = toolUseId;
1777
+ trace.agentSpawns.push(spawn);
1778
+ }
1779
+ if (context.branches && context.uuid && toolUseId) {
1780
+ const pending = context.branches.spawns.get(context.uuid) ?? [];
1781
+ pending.push({
1782
+ toolUseId,
1783
+ prompt: typeof input.prompt === "string" ? input.prompt : void 0,
1784
+ claimed: false
1785
+ });
1786
+ context.branches.spawns.set(context.uuid, pending);
1787
+ }
1788
+ break;
1789
+ }
1790
+ case "Read":
1791
+ case "Write":
1792
+ case "Edit": {
1793
+ if (typeof input.file_path === "string") {
1794
+ const op = name === "Read" ? "read" : name === "Write" ? "write" : "edit";
1795
+ trace.fileAccesses.push({ path: input.file_path, op, index });
1796
+ }
1797
+ break;
1798
+ }
1799
+ }
1800
+ }
1801
+ function parseStream(records, filePath) {
1802
+ const trace = newTrace(filePath);
1803
+ const context = { sidechain: false };
1804
+ let inputTokens = 0;
1805
+ let outputTokens = 0;
1806
+ let sawMessageUsage = false;
1807
+ for (const rec of records) {
1808
+ const type = rec.type;
1809
+ if (type === "system" && (rec.subtype === "init" || !rec.subtype)) {
1810
+ if (typeof rec.cwd === "string") trace.cwd = rec.cwd;
1811
+ if (typeof rec.model === "string") trace.model = rec.model;
1812
+ if (typeof rec.session_id === "string") trace.sessionId = rec.session_id;
1813
+ pushEvent(trace, { kind: "system", raw: rec });
1814
+ continue;
1815
+ }
1816
+ if (type === "assistant" || type === "user") {
1817
+ const message = rec.message ?? rec;
1818
+ if (type === "assistant") {
1819
+ const usage = handleAssistantMessage(trace, message, context, rec);
1820
+ if (usage) {
1821
+ inputTokens += usage.input;
1822
+ outputTokens += usage.output;
1823
+ sawMessageUsage = true;
1824
+ }
1825
+ } else {
1826
+ handleUserMessage(trace, message, context, rec);
1827
+ }
1828
+ continue;
1829
+ }
1830
+ if (type === "result") {
1831
+ trace.isError = rec.is_error === true;
1832
+ if (typeof rec.num_turns === "number") trace.turnCount = rec.num_turns;
1833
+ const usage = rec.usage;
1834
+ const totalCost = typeof rec.total_cost_usd === "number" ? rec.total_cost_usd : void 0;
1835
+ if (usage) {
1836
+ trace.usage = {
1837
+ inputTokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
1838
+ outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0
1839
+ };
1840
+ if (totalCost !== void 0) trace.usage.totalCostUsd = totalCost;
1841
+ } else if (sawMessageUsage) {
1842
+ trace.usage = { inputTokens, outputTokens };
1843
+ if (totalCost !== void 0) trace.usage.totalCostUsd = totalCost;
1844
+ }
1845
+ pushEvent(trace, { kind: "meta", raw: rec });
1846
+ continue;
1847
+ }
1848
+ pushEvent(trace, { kind: "meta", raw: rec });
1849
+ }
1850
+ if (!trace.usage && sawMessageUsage) {
1851
+ trace.usage = { inputTokens, outputTokens };
1852
+ }
1853
+ return trace;
1854
+ }
1855
+ var AGENT_META_RE = /^agent-(.+)\.meta\.json$/;
1856
+ var AGENT_JSONL_RE = /^agent-(.+)\.jsonl$/;
1857
+ function sidecarDirFor(traceFile) {
1858
+ const stem = basename3(traceFile).replace(/\.jsonl$/i, "");
1859
+ return join(dirname(traceFile), stem, "subagents");
1860
+ }
1861
+ function asSource(id, trace) {
1862
+ return {
1863
+ id,
1864
+ events: trace.events,
1865
+ toolCalls: trace.toolCalls,
1866
+ fileAccesses: trace.fileAccesses,
1867
+ skillInvocations: trace.skillInvocations,
1868
+ agentSpawns: trace.agentSpawns
1869
+ };
1870
+ }
1871
+ function stampBranch(sub, branchId) {
1872
+ for (const event of sub.events) {
1873
+ event.sidechain = true;
1874
+ event.branchId = branchId;
1875
+ }
1876
+ for (const call of sub.toolCalls) {
1877
+ call.sidechain = true;
1878
+ call.branchId = branchId;
1879
+ }
1880
+ }
1881
+ async function loadSidecars(dir, trace) {
1882
+ let names2;
1883
+ try {
1884
+ names2 = await readdir(dir);
1885
+ } catch (error) {
1886
+ const code = error.code;
1887
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
1888
+ trace.warnings.push(
1889
+ `could not read the subagent transcript directory ${dir} (${code ?? "unknown error"}), so subagent turns are missing here`
1890
+ );
1891
+ }
1892
+ return [];
1893
+ }
1894
+ const metas = /* @__PURE__ */ new Set();
1895
+ const transcripts = /* @__PURE__ */ new Set();
1896
+ for (const name of names2) {
1897
+ const meta = AGENT_META_RE.exec(name);
1898
+ if (meta?.[1] !== void 0) {
1899
+ metas.add(meta[1]);
1900
+ continue;
1901
+ }
1902
+ const jsonl = AGENT_JSONL_RE.exec(name);
1903
+ if (jsonl?.[1] !== void 0) transcripts.add(jsonl[1]);
1904
+ }
1905
+ for (const orphan of [...transcripts].sort()) {
1906
+ if (!metas.has(orphan)) {
1907
+ trace.warnings.push(
1908
+ `subagent transcript agent-${orphan}.jsonl has no agent-${orphan}.meta.json, so it names no Agent spawn and was not merged`
1909
+ );
1910
+ }
1911
+ }
1912
+ const loaded = [];
1913
+ for (const agentId of [...metas].sort()) {
1914
+ let meta;
1915
+ try {
1916
+ const raw = await readFile(
1917
+ join(dir, `agent-${agentId}.meta.json`),
1918
+ "utf-8"
1919
+ );
1920
+ const parsed = JSON.parse(raw);
1921
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1922
+ throw new Error("shape");
1923
+ }
1924
+ meta = parsed;
1925
+ } catch {
1926
+ trace.warnings.push(
1927
+ `subagent metadata agent-${agentId}.meta.json is unreadable or not an object, so that branch was not merged`
1928
+ );
1929
+ continue;
1930
+ }
1931
+ const file = join(dir, `agent-${agentId}.jsonl`);
1932
+ let sub;
1933
+ try {
1934
+ sub = parseTraceContent(await readFile(file, "utf-8"), file);
1935
+ } catch {
1936
+ trace.warnings.push(
1937
+ `subagent transcript agent-${agentId}.jsonl is missing or unreadable, so that branch was not merged`
1938
+ );
1939
+ continue;
1940
+ }
1941
+ for (const warning of sub.warnings) {
1942
+ trace.warnings.push(`subagent ${meta.agentType ?? agentId}: ${warning}`);
1943
+ }
1944
+ loaded.push({ agentId, file, meta, sub, source: asSource(agentId, sub) });
1945
+ }
1946
+ return loaded;
1947
+ }
1948
+ async function mergeSidecarBranches(trace, traceFile) {
1949
+ const loaded = await loadSidecars(sidecarDirFor(traceFile), trace);
1950
+ if (loaded.length === 0) return;
1951
+ const base = asSource("", trace);
1952
+ const sources = /* @__PURE__ */ new Map([["", base]]);
1953
+ const spawnOwner = /* @__PURE__ */ new Map();
1954
+ const registerSpawns = (source) => {
1955
+ for (const spawn of source.agentSpawns) {
1956
+ if (spawn.toolUseId !== void 0) {
1957
+ spawnOwner.set(spawn.toolUseId, {
1958
+ sourceId: source.id,
1959
+ local: spawn.index
1960
+ });
1961
+ }
1962
+ }
1963
+ };
1964
+ registerSpawns(base);
1965
+ const children = /* @__PURE__ */ new Map();
1966
+ const attached = [];
1967
+ const pending = [...loaded].sort(
1968
+ (a, b) => (a.meta.spawnDepth ?? 1) - (b.meta.spawnDepth ?? 1) || (a.agentId < b.agentId ? -1 : a.agentId > b.agentId ? 1 : 0)
1969
+ );
1970
+ let progress = true;
1971
+ while (progress) {
1972
+ progress = false;
1973
+ for (let i = 0; i < pending.length; i += 1) {
1974
+ const sidecar = pending[i];
1975
+ const branchId = sidecar.meta.toolUseId;
1976
+ const owner = branchId === void 0 ? void 0 : spawnOwner.get(branchId);
1977
+ if (branchId === void 0 || owner === void 0) continue;
1978
+ pending.splice(i, 1);
1979
+ i -= 1;
1980
+ progress = true;
1981
+ stampBranch(sidecar.sub, branchId);
1982
+ sources.set(sidecar.agentId, sidecar.source);
1983
+ const key = `${owner.sourceId}\0${owner.local}`;
1984
+ children.set(key, [...children.get(key) ?? [], sidecar]);
1985
+ registerSpawns(sidecar.source);
1986
+ attached.push({ sidecar, branchId });
1987
+ }
1988
+ }
1989
+ for (const sidecar of pending) {
1990
+ trace.warnings.push(
1991
+ `subagent transcript agent-${sidecar.agentId}.jsonl names Agent call ${sidecar.meta.toolUseId ?? "(none recorded)"}, which is not in this trace, so that branch was not merged`
1992
+ );
1993
+ }
1994
+ if (attached.length === 0) return;
1995
+ const slots = [];
1996
+ const emit = (sourceId) => {
1997
+ const source = sources.get(sourceId);
1998
+ if (source === void 0) return;
1999
+ for (let local = 0; local < source.events.length; local += 1) {
2000
+ slots.push({ sourceId, local });
2001
+ for (const kid of children.get(`${sourceId}\0${local}`) ?? []) {
2002
+ emit(kid.agentId);
2003
+ }
2004
+ }
2005
+ };
2006
+ emit("");
2007
+ const remap = /* @__PURE__ */ new Map();
2008
+ slots.forEach((slot, final) => {
2009
+ const row = remap.get(slot.sourceId) ?? [];
2010
+ row[slot.local] = final;
2011
+ remap.set(slot.sourceId, row);
2012
+ });
2013
+ const finalIndex = (sourceId, local) => remap.get(sourceId)?.[local] ?? local;
2014
+ trace.events = slots.map((slot, final) => {
2015
+ const event = sources.get(slot.sourceId)?.events[slot.local];
2016
+ return { ...event, index: final };
2017
+ });
2018
+ const gather = (pick) => [...sources.values()].flatMap(
2019
+ (source) => pick(source).map((item) => ({
2020
+ ...item,
2021
+ index: finalIndex(source.id, item.index)
2022
+ }))
2023
+ ).sort((a, b) => a.index - b.index);
2024
+ trace.toolCalls = gather((s) => s.toolCalls);
2025
+ trace.fileAccesses = gather((s) => s.fileAccesses);
2026
+ trace.skillInvocations = gather((s) => s.skillInvocations);
2027
+ trace.agentSpawns = gather((s) => s.agentSpawns);
2028
+ trace.availability = remapAvailability(
2029
+ trace.availability,
2030
+ (index) => finalIndex("", index)
2031
+ );
2032
+ for (const { sidecar, branchId } of attached) {
2033
+ const owner = spawnOwner.get(branchId);
2034
+ foldBranchAvailability(
2035
+ trace.availability,
2036
+ sidecar.sub.availability,
2037
+ branchId,
2038
+ owner === void 0 ? 0 : finalIndex(owner.sourceId, owner.local)
2039
+ );
2040
+ }
2041
+ const described = /* @__PURE__ */ new Map();
2042
+ for (const { sidecar, branchId } of attached) {
2043
+ const info = {
2044
+ agentId: sidecar.agentId,
2045
+ file: sidecar.file
2046
+ };
2047
+ if (sidecar.meta.agentType) info.agentType = sidecar.meta.agentType;
2048
+ if (sidecar.meta.description) info.description = sidecar.meta.description;
2049
+ if (sidecar.meta.parentAgentId) {
2050
+ info.parentAgentId = sidecar.meta.parentAgentId;
2051
+ }
2052
+ if (typeof sidecar.meta.spawnDepth === "number") {
2053
+ info.spawnDepth = sidecar.meta.spawnDepth;
2054
+ }
2055
+ described.set(branchId, info);
2056
+ }
2057
+ trace.subagentBranches = buildBranches(trace, described);
2058
+ }
2059
+
2060
+ // src/tracevals/core/plan.ts
2061
+ var IMPLICIT_EVAL_NAME = "adheres-to-artifact";
2062
+ function planEvals(artifacts, metadataFor) {
2063
+ const plans = [];
2064
+ for (const artifact of artifacts) {
2065
+ const supplied = metadataFor?.(artifact);
2066
+ const metadata = {
2067
+ extracted: supplied?.extracted ?? extractFrontmatter(artifact.content, "markdown"),
2068
+ ...supplied?.locate === void 0 ? {} : { locate: supplied.locate }
2069
+ };
2070
+ const extracted = extractEvals(artifact, metadata.extracted);
2071
+ const at = (pointer) => declaredAt(artifact.path, pointer, metadata);
2072
+ const whole = { file: artifact.path };
2073
+ if (extracted.errors.length > 0) {
2074
+ const detail2 = extracted.errors.map(
2075
+ (e) => `${e.instancePath || "/"}${e.line !== void 0 ? ` (line ${e.line})` : ""}: ${e.message}`
2076
+ ).join("; ");
2077
+ plans.push({
2078
+ artifact,
2079
+ evalName: "evals-block-valid",
2080
+ assertion: "The artifact's metadata.evals block matches the schema.",
2081
+ grader: "ai",
2082
+ severity: "error",
2083
+ implicit: false,
2084
+ location: at("/metadata/evals"),
2085
+ error: `invalid metadata.evals block: ${detail2}`
2086
+ });
2087
+ continue;
2088
+ }
2089
+ if (extracted.skip) {
2090
+ plans.push({
2091
+ artifact,
2092
+ evalName: IMPLICIT_EVAL_NAME,
2093
+ assertion: "Artifact skipped via metadata.eval-skip.",
2094
+ grader: "ai",
2095
+ severity: "error",
2096
+ implicit: true,
2097
+ location: at("/metadata/eval-skip"),
2098
+ skipped: true,
2099
+ skipReason: "artifact skipped via metadata.eval-skip"
2100
+ });
2101
+ continue;
2102
+ }
2103
+ if (extracted.evals.length === 0) {
2104
+ plans.push({
2105
+ artifact,
2106
+ evalName: IMPLICIT_EVAL_NAME,
2107
+ assertion: `The session adhered to the instructions in this ${artifact.type} ("${artifact.name}"). Cite the specific instructions followed or violated.`,
2108
+ grader: "ai",
2109
+ severity: "error",
2110
+ implicit: true,
2111
+ location: whole
2112
+ });
2113
+ continue;
2114
+ }
2115
+ for (const [index, entry] of extracted.evals.entries()) {
2116
+ const plan = {
2117
+ artifact,
2118
+ evalName: entry.id,
2119
+ assertion: entry.assertion,
2120
+ grader: entry.grader,
2121
+ ...entry.model !== void 0 ? { model: entry.model } : {},
2122
+ ...entry.runs !== void 0 ? { runs: entry.runs } : {},
2123
+ ...entry.target !== void 0 ? { target: entry.target } : {},
2124
+ ...entry.weight !== void 0 ? { weight: entry.weight } : {},
2125
+ severity: entry.severity,
2126
+ implicit: false,
2127
+ // A string block is one entry at index 0, and the pointer walks up to
2128
+ // the block's own line.
2129
+ location: at(`/metadata/evals/${String(index)}`)
2130
+ };
2131
+ if (entry.options) plan.options = entry.options;
2132
+ if (entry.evidence) plan.evidence = entry.evidence;
2133
+ if (entry.examples) plan.examples = entry.examples;
2134
+ if (entry.provider) plan.provider = entry.provider;
2135
+ if (entry.command) plan.command = entry.command;
2136
+ if (entry.successExitCodes) plan.successExitCodes = entry.successExitCodes;
2137
+ if (entry.timeoutMs !== void 0) plan.timeoutMs = entry.timeoutMs;
2138
+ if (entry.generatedAssertionHash !== void 0) {
2139
+ plan.generatedAssertionHash = entry.generatedAssertionHash;
2140
+ }
2141
+ const proposedBy = extracted.proposedBy.get(entry.id);
2142
+ if (proposedBy !== void 0 && proposedBy.length > 0) {
2143
+ plan.proposedBy = proposedBy;
2144
+ }
2145
+ if (entry.skip) {
2146
+ plan.skipped = true;
2147
+ plan.skipReason = "eval skipped via its own skip: true";
2148
+ }
2149
+ plans.push(plan);
2150
+ }
2151
+ }
2152
+ return plans;
2153
+ }
2154
+
2155
+ // src/tracevals/judge/prompt.ts
2156
+ var PROMPT_VERSION = 4;
2157
+ var JUDGE_SYSTEM_PROMPT = `You are an adherence judge for AI agent sessions. You are given some evidence from an agent session and one assertion drawn from the instructions the session was operating under \u2014 a skill definition, an agent definition, or project rules.
2158
+
2159
+ The evidence is whatever the eval asked to be graded, and its heading says which: usually the session transcript (messages, tool calls, skills used), but it may instead be the session's final message, the list of files it touched, the instruction artifact itself, or one named file. Judge the assertion against what you were actually given.
2160
+
2161
+ Decide whether the session adhered to the assertion:
2162
+ - "pass" only when the session fully satisfied it.
2163
+ - "partial" when it partly satisfied it.
2164
+ - "fail" when it violated it or plainly did not do it.
2165
+
2166
+ Ground every judgment in the graded content: quote or cite the specific part of it \u2014 a message, a tool call, a line, or a telling omission \u2014 that supports your verdict in the "observed" field. If the assertion concerns something the graded content cannot show (e.g. events outside what you were given), say so and lower your confidence.
2167
+
2168
+ Respond with a single JSON object matching the provided schema. No prose outside the JSON.`;
2169
+ var MAX_ARTIFACT_CHARS = 8e3;
2170
+ function artifactWasTruncated(content) {
2171
+ return content.length > MAX_ARTIFACT_CHARS;
2172
+ }
2173
+ function buildUserContent(plan, graded, targetLabel = "transcript") {
2174
+ const parts = [];
2175
+ parts.push(`# Assertion
2176
+ ${plan.assertion ?? ""}`);
2177
+ if (plan.evidence) {
2178
+ parts.push(`# Where to look
2179
+ ${plan.evidence}`);
2180
+ }
2181
+ if (plan.examples?.pass?.length) {
2182
+ parts.push(`# Examples of passing behavior
2183
+ - ${plan.examples.pass.join("\n- ")}`);
2184
+ }
2185
+ if (plan.examples?.fail?.length) {
2186
+ parts.push(`# Examples of failing behavior
2187
+ - ${plan.examples.fail.join("\n- ")}`);
2188
+ }
2189
+ let artifactBody = plan.artifact.content;
2190
+ if (artifactBody.length > MAX_ARTIFACT_CHARS) {
2191
+ artifactBody = `${artifactBody.slice(0, MAX_ARTIFACT_CHARS)}
2192
+ [... artifact truncated ...]`;
2193
+ }
2194
+ const gradingTheArtifact = targetLabel === "artifact";
2195
+ const artifactHeading = `${plan.artifact.type} ("${plan.artifact.name}")`;
2196
+ if (!gradingTheArtifact) {
2197
+ parts.push(`# Source ${artifactHeading}
2198
+ ${artifactBody}`);
2199
+ }
2200
+ parts.push(
2201
+ targetLabel === "transcript" ? `# Session transcript
2202
+ ${graded}` : gradingTheArtifact ? `# Graded content: the ${artifactHeading} itself
2203
+ ${artifactBody}` : `# Graded content (${targetLabel})
2204
+ ${graded}`
2205
+ );
2206
+ return parts.join("\n\n");
2207
+ }
2208
+
2209
+ // src/tracevals/graders/glob.ts
2210
+ var literal = (ch) => ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2211
+ var compiled = /* @__PURE__ */ new Map();
2212
+ function globToRegExp(glob) {
2213
+ const cached = compiled.get(glob);
2214
+ if (cached !== void 0) return cached;
2215
+ const built = compile(glob);
2216
+ compiled.set(glob, built);
2217
+ return built;
2218
+ }
2219
+ function compile(glob) {
2220
+ const spec = glob.replace(/\\/g, "/").toLowerCase().replace(/(?:\*\*\/)+/g, "**/");
2221
+ let out = "";
2222
+ for (let i = 0; i < spec.length; i += 1) {
2223
+ const ch = spec[i];
2224
+ if (ch === "?") {
2225
+ out += "[^/]";
2226
+ continue;
2227
+ }
2228
+ if (ch !== "*") {
2229
+ out += literal(ch);
2230
+ continue;
2231
+ }
2232
+ if (spec[i + 1] !== "*") {
2233
+ out += "[^/]*";
2234
+ continue;
2235
+ }
2236
+ i += 1;
2237
+ if (spec[i + 1] === "/") {
2238
+ i += 1;
2239
+ if (out !== "") out += "(?:.*/)?";
2240
+ } else if (out.endsWith("/")) {
2241
+ out = `${out.slice(0, -1)}(?:/.*)?`;
2242
+ } else {
2243
+ out += ".*";
2244
+ }
2245
+ }
2246
+ return new RegExp(`^(?:.*/)?${out}$`);
2247
+ }
2248
+ function normalizePath(path) {
2249
+ return path.replace(/\\/g, "/").toLowerCase();
2250
+ }
2251
+ function matchesGlob(path, glob) {
2252
+ return globToRegExp(glob).test(normalizePath(path));
2253
+ }
2254
+
2255
+ // src/tracevals/graders/when.ts
2256
+ var stringValue = (kind) => (value) => typeof value === "string" && value.length > 0 ? void 0 : `options.when.${kind} must be a string`;
2257
+ var prompts = /* @__PURE__ */ new Map();
2258
+ function promptRe(pattern) {
2259
+ const cached = prompts.get(pattern);
2260
+ if (cached !== void 0) return cached;
2261
+ const built = new RegExp(pattern);
2262
+ prompts.set(pattern, built);
2263
+ return built;
2264
+ }
2265
+ var CONDITIONS = {
2266
+ "file-access": {
2267
+ validate: stringValue("file-access"),
2268
+ test: (value, window) => window.fileAccesses.some((a) => matchesGlob(a.path, value)),
2269
+ reason: (value) => `no file matching "${value}" was accessed`
2270
+ },
2271
+ "tool-used": {
2272
+ validate: stringValue("tool-used"),
2273
+ test: (value, window) => window.toolCalls.some((c) => c.name === value),
2274
+ reason: (value) => `the ${value} tool was never used`
2275
+ },
2276
+ "prompt-matches": {
2277
+ validate: (value) => {
2278
+ const shape = stringValue("prompt-matches")(value);
2279
+ if (shape !== void 0) return shape;
2280
+ try {
2281
+ new RegExp(value);
2282
+ } catch (err) {
2283
+ return `options.when.prompt-matches is not a valid regular expression: ${err.message}`;
2284
+ }
2285
+ return void 0;
2286
+ },
2287
+ test: (value, window) => {
2288
+ const re = promptRe(value);
2289
+ return window.userMessages.some((text) => re.test(text));
2290
+ },
2291
+ reason: (value) => `no prompt matched /${value}/`
2292
+ },
2293
+ "turn-count-above": {
2294
+ validate: (value) => {
2295
+ if (typeof value !== "number" || !Number.isInteger(value)) {
2296
+ return "options.when.turn-count-above must be a whole number";
2297
+ }
2298
+ if (value < 0) return "options.when.turn-count-above must be at least 0";
2299
+ return void 0;
2300
+ },
2301
+ test: (value, window) => window.turnCount > value,
2302
+ reason: (value, window) => `${window.turnCount} turn(s) is not more than ${value}`
2303
+ }
2304
+ };
2305
+ var WHEN_CONDITIONS = Object.keys(
2306
+ CONDITIONS
2307
+ );
2308
+ function validateWhen(options) {
2309
+ const when = options.when;
2310
+ if (when === void 0) return void 0;
2311
+ if (typeof when !== "object" || when === null || Array.isArray(when)) {
2312
+ return "options.when must be an object of conditions";
2313
+ }
2314
+ const conditions = when;
2315
+ const keys = Object.keys(conditions);
2316
+ if (keys.length === 0) {
2317
+ return `options.when needs at least one of: ${WHEN_CONDITIONS.join(", ")}`;
2318
+ }
2319
+ for (const key of keys) {
2320
+ const condition = CONDITIONS[key];
2321
+ if (condition === void 0) {
2322
+ return `options.when.${key} is not a known condition; expected one of: ${WHEN_CONDITIONS.join(", ")}`;
2323
+ }
2324
+ const invalid = condition.validate(conditions[key]);
2325
+ if (invalid !== void 0) return invalid;
2326
+ }
2327
+ return void 0;
2328
+ }
2329
+ function evaluateWhen(options, window) {
2330
+ const when = options.when;
2331
+ if (when === void 0) return { armed: true };
2332
+ for (const name of WHEN_CONDITIONS) {
2333
+ const value = when[name];
2334
+ if (value === void 0) continue;
2335
+ const condition = CONDITIONS[name];
2336
+ if (condition.test(value, window)) continue;
2337
+ return {
2338
+ armed: false,
2339
+ reason: `trigger not met (${window.label}): ${condition.reason(value, window)}`
2340
+ };
2341
+ }
2342
+ return { armed: true };
2343
+ }
2344
+ function rejectWhen(kind, options) {
2345
+ if (options.when === void 0) return void 0;
2346
+ return `options.when is not supported by the ${kind} grader, which grades the whole session rather than a window`;
2347
+ }
2348
+ function skippedTrigger(trigger) {
2349
+ return { findings: [], skipped: trigger.reason ?? "trigger not met" };
2350
+ }
2351
+
2352
+ // src/tracevals/graders/util.ts
2353
+ function finding(plan, message) {
2354
+ return {
2355
+ evalName: plan.evalName,
2356
+ artifact: plan.artifact.path,
2357
+ message,
2358
+ severity: plan.severity
2359
+ };
2360
+ }
2361
+ function fail(plan, message) {
2362
+ return { findings: [finding(plan, message)] };
2363
+ }
2364
+ var pass = { findings: [] };
2365
+ function optionsError(kind, message) {
2366
+ return { findings: [], error: `${kind}: ${message}` };
2367
+ }
2368
+ function firstError(...checks) {
2369
+ return checks.find((check) => check !== void 0);
2370
+ }
2371
+ function requiredString(options, key) {
2372
+ const value = options[key];
2373
+ if (typeof value !== "string" || value.length === 0) {
2374
+ return `options.${key} is required`;
2375
+ }
2376
+ return void 0;
2377
+ }
2378
+ function optionalString(options, key) {
2379
+ const value = options[key];
2380
+ if (value === void 0) return void 0;
2381
+ if (typeof value !== "string" || value.length === 0) {
2382
+ return `options.${key} must be a non-empty string`;
2383
+ }
2384
+ return void 0;
2385
+ }
2386
+ function optionalEnum(options, key, allowed) {
2387
+ const value = options[key];
2388
+ if (value === void 0) return void 0;
2389
+ if (typeof value !== "string" || !allowed.includes(value)) {
2390
+ return `options.${key} must be one of: ${allowed.join(", ")}`;
2391
+ }
2392
+ return void 0;
2393
+ }
2394
+ function optionalNumber(options, key, bounds = {}) {
2395
+ const value = options[key];
2396
+ if (value === void 0) return void 0;
2397
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2398
+ return `options.${key} must be a finite number`;
2399
+ }
2400
+ if (bounds.integer === true && !Number.isInteger(value)) {
2401
+ return `options.${key} must be a whole number`;
2402
+ }
2403
+ if (bounds.min !== void 0 && value < bounds.min) {
2404
+ return `options.${key} must be at least ${bounds.min}`;
2405
+ }
2406
+ return void 0;
2407
+ }
2408
+ function optionalBoolean(options, key) {
2409
+ if (options[key] !== void 0 && typeof options[key] !== "boolean") {
2410
+ return `options.${key} must be a boolean`;
2411
+ }
2412
+ return void 0;
2413
+ }
2414
+ function orderedBounds(options, minKey, maxKey) {
2415
+ const min = options[minKey];
2416
+ const max = options[maxKey];
2417
+ if (typeof min === "number" && typeof max === "number" && min > max) {
2418
+ return `options.${maxKey} must be greater than or equal to options.${minKey}`;
2419
+ }
2420
+ return void 0;
2421
+ }
2422
+ function requireOneOf(options, keys) {
2423
+ if (keys.some((key) => options[key] !== void 0)) return void 0;
2424
+ return `at least one of ${keys.map((k) => `options.${k}`).join(" or ")} is required`;
2425
+ }
2426
+ function windowFor(trace, plan) {
2427
+ const { name, type } = plan.artifact;
2428
+ if (type === "skill") return skillWindow(trace, name);
2429
+ if (type === "agent") return agentWindow(trace, name);
2430
+ if (type === "slash-command") return slashCommandWindow(trace, name);
2431
+ return {
2432
+ scope: "session",
2433
+ label: "whole session",
2434
+ empty: false,
2435
+ events: trace.events,
2436
+ toolCalls: trace.toolCalls,
2437
+ fileAccesses: trace.fileAccesses,
2438
+ skillInvocations: trace.skillInvocations,
2439
+ userMessages: trace.userMessages,
2440
+ assistantTexts: trace.assistantTexts,
2441
+ turnCount: trace.turnCount
2442
+ };
2443
+ }
2444
+ function skippedWindow(window) {
2445
+ return { findings: [], skipped: window.reason ?? "the artifact governed no turns" };
2446
+ }
2447
+ function skillWindow(trace, name) {
2448
+ return injectionWindow(trace, "skill", `skill "${name}"`, (s) => s.name === name);
2449
+ }
2450
+ function slashCommandWindow(trace, name) {
2451
+ return injectionWindow(
2452
+ trace,
2453
+ "slash-command",
2454
+ `slash command "/${name}"`,
2455
+ (s) => s.name === name && s.via === "command-injection"
2456
+ );
2457
+ }
2458
+ function injectionWindow(trace, scope, label, opensHere) {
2459
+ const opens = trace.skillInvocations.filter(opensHere).map((s) => s.index);
2460
+ if (opens.length === 0) {
2461
+ return emptyWindow(
2462
+ scope,
2463
+ label,
2464
+ `${label} was never invoked in this trace, so it governed no turns`
2465
+ );
2466
+ }
2467
+ const chainOf = (index) => trace.events[index]?.branchId;
2468
+ const own = new Set(opens.map(chainOf));
2469
+ const boundaries = trace.skillInvocations.filter((s) => own.has(chainOf(s.index))).map((s) => s.index).sort((a, b) => a - b);
2470
+ const spans = opens.map((start) => ({
2471
+ start,
2472
+ end: boundaries.find((b) => b > start) ?? Number.POSITIVE_INFINITY
2473
+ }));
2474
+ return materialize(trace, scope, label, spans, void 0, own);
2475
+ }
2476
+ function agentWindow(trace, name) {
2477
+ const label = `agent "${name}"`;
2478
+ const mine = trace.subagentBranches.filter((b) => b.agentType === name);
2479
+ if (mine.length === 0) {
2480
+ const spawned = trace.agentSpawns.some((a) => a.subagentType === name);
2481
+ return emptyWindow(
2482
+ "agent",
2483
+ label,
2484
+ spawned ? `${label} was spawned but recorded no subagent turns (no inline sidechain records and no sidecar transcript), so it governed no turns` : `${label} was never spawned in this trace, so it governed no turns`
2485
+ );
2486
+ }
2487
+ const branchIds = new Set(mine.map((b) => b.branchId));
2488
+ for (const branch of trace.subagentBranches) {
2489
+ if (branchIds.has(branch.branchId)) continue;
2490
+ const nested = mine.some(
2491
+ (m) => m.startIndex <= branch.startIndex && branch.endIndex <= m.endIndex
2492
+ );
2493
+ if (nested) branchIds.add(branch.branchId);
2494
+ }
2495
+ const spans = mine.map((b) => ({ start: b.startIndex, end: b.endIndex }));
2496
+ return materialize(
2497
+ trace,
2498
+ "agent",
2499
+ label,
2500
+ spans,
2501
+ branchIds,
2502
+ new Set(mine.map((b) => b.branchId))
2503
+ );
2504
+ }
2505
+ function emptyWindow(scope, label, reason) {
2506
+ return {
2507
+ scope,
2508
+ label,
2509
+ empty: true,
2510
+ reason,
2511
+ events: [],
2512
+ toolCalls: [],
2513
+ fileAccesses: [],
2514
+ skillInvocations: [],
2515
+ userMessages: [],
2516
+ assistantTexts: [],
2517
+ turnCount: 0
2518
+ };
2519
+ }
2520
+ function materialize(trace, scope, label, spans, branchIds, own) {
2521
+ const inSpan = (index) => spans.some((s) => index >= s.start && index < s.end);
2522
+ const excluded = /* @__PURE__ */ new Set();
2523
+ if (branchIds !== void 0) {
2524
+ for (const event of trace.events) {
2525
+ if (!inSpan(event.index)) continue;
2526
+ if (event.branchId === void 0 || !branchIds.has(event.branchId)) {
2527
+ excluded.add(event.index);
2528
+ }
2529
+ }
2530
+ }
2531
+ const inWindow = (index) => inSpan(index) && !excluded.has(index);
2532
+ const events = trace.events.filter((e) => inWindow(e.index));
2533
+ const textOf = (kind) => events.filter((e) => e.kind === kind && own.has(e.branchId) && e.text).map((e) => e.text);
2534
+ const userMessages = textOf("user");
2535
+ return {
2536
+ scope,
2537
+ label,
2538
+ empty: false,
2539
+ events,
2540
+ toolCalls: trace.toolCalls.filter((c) => inWindow(c.index)),
2541
+ fileAccesses: trace.fileAccesses.filter((a) => inWindow(a.index)),
2542
+ skillInvocations: trace.skillInvocations.filter((s) => inWindow(s.index)),
2543
+ userMessages,
2544
+ assistantTexts: textOf("assistant"),
2545
+ turnCount: userMessages.length
2546
+ };
2547
+ }
2548
+
2549
+ // src/tracevals/graders/tool-usage.ts
2550
+ function checkContradiction(options) {
2551
+ const expect = options.expect ?? "used";
2552
+ const { min, max } = options;
2553
+ if (expect === "not-used" && typeof min === "number" && min > 0) {
2554
+ return "options.min is unsatisfiable with expect: not-used";
2555
+ }
2556
+ if (expect === "used" && typeof max === "number" && max === 0) {
2557
+ return "options.max of 0 is unsatisfiable with expect: used";
2558
+ }
2559
+ return void 0;
2560
+ }
2561
+ function validateOptions(options) {
2562
+ return firstError(
2563
+ requiredString(options, "tool"),
2564
+ optionalEnum(options, "expect", ["used", "not-used"]),
2565
+ optionalNumber(options, "min", { min: 0, integer: true }),
2566
+ optionalNumber(options, "max", { min: 0, integer: true }),
2567
+ orderedBounds(options, "min", "max"),
2568
+ checkContradiction(options),
2569
+ optionalBoolean(options, "includeSidechains"),
2570
+ validateWhen(options)
2571
+ );
2572
+ }
2573
+ var toolUsageGrader = {
2574
+ kind: "tool-usage",
2575
+ validateOptions,
2576
+ grade({ trace, plan }) {
2577
+ const options = plan.options ?? {};
2578
+ const invalid = validateOptions(options);
2579
+ if (invalid !== void 0) return optionsError("tool-usage", invalid);
2580
+ const tool = options.tool;
2581
+ const expect = options.expect ?? "used";
2582
+ const includeSidechains = options.includeSidechains === true;
2583
+ const window = windowFor(trace, plan);
2584
+ if (window.empty) return skippedWindow(window);
2585
+ const trigger = evaluateWhen(options, window);
2586
+ if (!trigger.armed) return skippedTrigger(trigger);
2587
+ const branchScoped = window.scope === "agent";
2588
+ const count = window.toolCalls.filter(
2589
+ (c) => c.name === tool && (branchScoped || includeSidechains || !c.sidechain)
2590
+ ).length;
2591
+ if (expect === "not-used" && count > 0) {
2592
+ return fail(plan, `tool ${tool} was used ${count} time(s) but must not be`);
2593
+ }
2594
+ if (expect === "used" && count === 0 && options.min === void 0) {
2595
+ return fail(plan, `tool ${tool} was never used`);
2596
+ }
2597
+ if (typeof options.min === "number" && count < options.min) {
2598
+ return fail(
2599
+ plan,
2600
+ `tool ${tool} was used ${count} time(s); at least ${options.min} required`
2601
+ );
2602
+ }
2603
+ if (typeof options.max === "number" && count > options.max) {
2604
+ return fail(
2605
+ plan,
2606
+ `tool ${tool} was used ${count} time(s); at most ${options.max} allowed`
2607
+ );
2608
+ }
2609
+ return pass;
2610
+ }
2611
+ };
2612
+
2613
+ // src/tracevals/graders/tool-order.ts
2614
+ function validateOptions2(options) {
2615
+ return firstError(
2616
+ requiredString(options, "before"),
2617
+ requiredString(options, "after"),
2618
+ optionalString(options, "beforeInputMatch"),
2619
+ optionalString(options, "afterInputMatch"),
2620
+ optionalBoolean(options, "includeSidechains"),
2621
+ validateWhen(options),
2622
+ ...["beforeInputMatch", "afterInputMatch"].map((key) => {
2623
+ const value = options[key];
2624
+ if (typeof value !== "string") return void 0;
2625
+ try {
2626
+ new RegExp(value);
2627
+ return void 0;
2628
+ } catch (err) {
2629
+ return `options.${key} is not a valid regular expression (${err instanceof Error ? err.message : String(err)})`;
2630
+ }
2631
+ })
2632
+ );
2633
+ }
2634
+ function ordinalsOf(calls, tool, inputMatch) {
2635
+ const re = inputMatch === void 0 ? void 0 : new RegExp(inputMatch);
2636
+ return calls.filter(
2637
+ (c) => c.name === tool && (re === void 0 || re.test(JSON.stringify(c.input)))
2638
+ ).map((c) => c.index);
2639
+ }
2640
+ function describe(tool, inputMatch) {
2641
+ return inputMatch === void 0 ? tool : `${tool} matching /${inputMatch}/`;
2642
+ }
2643
+ var toolOrderGrader = {
2644
+ kind: "tool-order",
2645
+ validateOptions: validateOptions2,
2646
+ grade({ trace, plan }) {
2647
+ const options = plan.options ?? {};
2648
+ const invalid = validateOptions2(options);
2649
+ if (invalid !== void 0) return optionsError("tool-order", invalid);
2650
+ const before = options.before;
2651
+ const after = options.after;
2652
+ const beforeMatch = options.beforeInputMatch;
2653
+ const afterMatch = options.afterInputMatch;
2654
+ const includeSidechains = options.includeSidechains === true;
2655
+ const window = windowFor(trace, plan);
2656
+ if (window.empty) return skippedWindow(window);
2657
+ const trigger = evaluateWhen(options, window);
2658
+ if (!trigger.armed) return skippedTrigger(trigger);
2659
+ const branchScoped = window.scope === "agent";
2660
+ const calls = window.toolCalls.filter(
2661
+ (c) => branchScoped || includeSidechains || !c.sidechain
2662
+ );
2663
+ const beforeOrdinals = ordinalsOf(calls, before, beforeMatch);
2664
+ const afterOrdinals = ordinalsOf(calls, after, afterMatch);
2665
+ const firstBefore = beforeOrdinals.length > 0 ? Math.min(...beforeOrdinals) : void 0;
2666
+ const lastAfter = afterOrdinals.length > 0 ? Math.max(...afterOrdinals) : void 0;
2667
+ if (firstBefore === void 0 && lastAfter === void 0) return pass;
2668
+ if (lastAfter === void 0) {
2669
+ return fail(
2670
+ plan,
2671
+ `${describe(after, afterMatch)} was never used, so it could not follow ${describe(before, beforeMatch)}`
2672
+ );
2673
+ }
2674
+ if (firstBefore === void 0) {
2675
+ return fail(
2676
+ plan,
2677
+ `${describe(after, afterMatch)} was used without ${describe(before, beforeMatch)} ever being used first`
2678
+ );
2679
+ }
2680
+ if (firstBefore > lastAfter) {
2681
+ return fail(
2682
+ plan,
2683
+ `${describe(before, beforeMatch)} was only used after ${describe(after, afterMatch)}`
2684
+ );
2685
+ }
2686
+ return pass;
2687
+ }
2688
+ };
2689
+
2690
+ // src/tracevals/graders/skill-invoked.ts
2691
+ function validateOptions3(options) {
2692
+ return firstError(
2693
+ requiredString(options, "skill"),
2694
+ optionalEnum(options, "expect", ["used", "not-used"]),
2695
+ validateWhen(options)
2696
+ );
2697
+ }
2698
+ var skillInvokedGrader = {
2699
+ kind: "skill-invoked",
2700
+ validateOptions: validateOptions3,
2701
+ grade({ trace, plan }) {
2702
+ const options = plan.options ?? {};
2703
+ const invalid = validateOptions3(options);
2704
+ if (invalid !== void 0) return optionsError("skill-invoked", invalid);
2705
+ const skill = options.skill;
2706
+ const expect = options.expect ?? "used";
2707
+ const window = windowFor(trace, plan);
2708
+ if (window.empty) return skippedWindow(window);
2709
+ const trigger = evaluateWhen(options, window);
2710
+ if (!trigger.armed) return skippedTrigger(trigger);
2711
+ const used = window.skillInvocations.some((s) => s.name === skill);
2712
+ if (expect === "used" && !used) {
2713
+ return fail(plan, `skill ${skill} was never invoked`);
2714
+ }
2715
+ if (expect === "not-used" && used) {
2716
+ return fail(plan, `skill ${skill} was invoked but must not be`);
2717
+ }
2718
+ return pass;
2719
+ }
2720
+ };
2721
+
2722
+ // src/tracevals/graders/file-access.ts
2723
+ var OPS = ["read", "write", "edit"];
2724
+ function validateOptions4(options) {
2725
+ return firstError(
2726
+ requiredString(options, "path"),
2727
+ optionalEnum(options, "op", OPS),
2728
+ optionalEnum(options, "expect", ["accessed", "not-accessed"]),
2729
+ validateWhen(options)
2730
+ );
2731
+ }
2732
+ function matches(accessPath, spec) {
2733
+ return matchesGlob(accessPath, spec);
2734
+ }
2735
+ var fileAccessGrader = {
2736
+ kind: "file-access",
2737
+ validateOptions: validateOptions4,
2738
+ grade({ trace, plan }) {
2739
+ const options = plan.options ?? {};
2740
+ const invalid = validateOptions4(options);
2741
+ if (invalid !== void 0) return optionsError("file-access", invalid);
2742
+ const path = options.path;
2743
+ const op = options.op;
2744
+ const expect = options.expect ?? "accessed";
2745
+ const window = windowFor(trace, plan);
2746
+ if (window.empty) return skippedWindow(window);
2747
+ const trigger = evaluateWhen(options, window);
2748
+ if (!trigger.armed) return skippedTrigger(trigger);
2749
+ const accessed = window.fileAccesses.some(
2750
+ (a) => matches(a.path, path) && (op === void 0 || a.op === op)
2751
+ );
2752
+ const opLabel = op ?? "any";
2753
+ if (expect === "accessed" && !accessed) {
2754
+ return fail(plan, `file ${path} was never accessed (op: ${opLabel})`);
2755
+ }
2756
+ if (expect === "not-accessed" && accessed) {
2757
+ return fail(plan, `file ${path} was accessed (op: ${opLabel}) but must not be`);
2758
+ }
2759
+ return pass;
2760
+ }
2761
+ };
2762
+
2763
+ // src/tracevals/graders/turn-count.ts
2764
+ function validateOptions5(options) {
2765
+ return firstError(
2766
+ requireOneOf(options, ["min", "max"]),
2767
+ optionalNumber(options, "min", { min: 0, integer: true }),
2768
+ optionalNumber(options, "max", { min: 0, integer: true }),
2769
+ orderedBounds(options, "min", "max"),
2770
+ validateWhen(options)
2771
+ );
2772
+ }
2773
+ var turnCountGrader = {
2774
+ kind: "turn-count",
2775
+ validateOptions: validateOptions5,
2776
+ grade({ trace, plan }) {
2777
+ const options = plan.options ?? {};
2778
+ const invalid = validateOptions5(options);
2779
+ if (invalid !== void 0) return optionsError("turn-count", invalid);
2780
+ const window = windowFor(trace, plan);
2781
+ if (window.empty) return skippedWindow(window);
2782
+ const trigger = evaluateWhen(options, window);
2783
+ if (!trigger.armed) return skippedTrigger(trigger);
2784
+ const { turnCount } = window;
2785
+ if (typeof options.max === "number" && turnCount > options.max) {
2786
+ return fail(plan, `${turnCount} turn(s); at most ${options.max} allowed`);
2787
+ }
2788
+ if (typeof options.min === "number" && turnCount < options.min) {
2789
+ return fail(plan, `${turnCount} turn(s); at least ${options.min} required`);
2790
+ }
2791
+ return pass;
2792
+ }
2793
+ };
2794
+
2795
+ // src/tracevals/graders/cost.ts
2796
+ function validateOptions6(options) {
2797
+ return firstError(
2798
+ requireOneOf(options, ["maxUsd", "maxTokens"]),
2799
+ optionalNumber(options, "maxUsd", { min: 0 }),
2800
+ optionalNumber(options, "maxTokens", { min: 0 }),
2801
+ rejectWhen("cost", options)
2802
+ );
2803
+ }
2804
+ var costGrader = {
2805
+ kind: "cost",
2806
+ validateOptions: validateOptions6,
2807
+ grade({ trace, plan }) {
2808
+ const options = plan.options ?? {};
2809
+ const invalid = validateOptions6(options);
2810
+ if (invalid !== void 0) return optionsError("cost", invalid);
2811
+ const usage = trace.usage;
2812
+ const skips = [];
2813
+ let checked = false;
2814
+ if (typeof options.maxUsd === "number") {
2815
+ if (usage?.totalCostUsd === void 0) {
2816
+ skips.push("no cost data (maxUsd not checkable)");
2817
+ } else {
2818
+ checked = true;
2819
+ if (usage.totalCostUsd > options.maxUsd) {
2820
+ return fail(
2821
+ plan,
2822
+ `session cost $${usage.totalCostUsd.toFixed(4)}; at most $${options.maxUsd} allowed`
2823
+ );
2824
+ }
2825
+ }
2826
+ }
2827
+ if (typeof options.maxTokens === "number") {
2828
+ if (usage === void 0) {
2829
+ skips.push("no usage data (maxTokens not checkable)");
2830
+ } else {
2831
+ checked = true;
2832
+ const total = usage.inputTokens + usage.outputTokens;
2833
+ if (total > options.maxTokens) {
2834
+ return fail(
2835
+ plan,
2836
+ `session used ${total} tokens; at most ${options.maxTokens} allowed`
2837
+ );
2838
+ }
2839
+ }
2840
+ }
2841
+ if (!checked && skips.length > 0) {
2842
+ return {
2843
+ findings: [],
2844
+ skipped: `trace carries insufficient cost data: ${skips.join("; ")}`
2845
+ };
2846
+ }
2847
+ return pass;
2848
+ }
2849
+ };
2850
+
2851
+ // src/tracevals/core/target.ts
2852
+ import { readFileSync } from "node:fs";
2853
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep } from "node:path";
2854
+ var DEFAULT_TARGET = "transcript";
2855
+ function describeTarget(target) {
2856
+ const t = target ?? DEFAULT_TARGET;
2857
+ return typeof t === "string" ? t : `file ${t.path}`;
2858
+ }
2859
+ function readTarget(target, ctx) {
2860
+ const t = target ?? DEFAULT_TARGET;
2861
+ if (t === "transcript") {
2862
+ return { ok: true, text: ctx.renderedTrace, label: "transcript" };
2863
+ }
2864
+ if (t === "last-message") {
2865
+ const last = ctx.trace.assistantTexts.at(-1);
2866
+ return { ok: true, text: last ?? "", label: "last-message" };
2867
+ }
2868
+ if (t === "files") {
2869
+ const paths = [...new Set(ctx.trace.fileAccesses.map((f) => f.path))];
2870
+ return { ok: true, text: paths.join("\n"), label: "files" };
2871
+ }
2872
+ if (t === "artifact") {
2873
+ return { ok: true, text: ctx.artifactContent, label: "artifact" };
2874
+ }
2875
+ if (isAbsolute2(t.path)) {
2876
+ return {
2877
+ ok: false,
2878
+ reason: `target file "${t.path}" is an absolute path; name it relative to the project root`
2879
+ };
2880
+ }
2881
+ const abs = resolve2(ctx.root, t.path);
2882
+ const rel = relative2(ctx.root, abs);
2883
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute2(rel)) {
2884
+ return {
2885
+ ok: false,
2886
+ reason: `target file "${t.path}" resolves outside the project root`
2887
+ };
2888
+ }
2889
+ try {
2890
+ return { ok: true, text: readFileSync(abs, "utf8"), label: `file ${t.path}` };
2891
+ } catch (err) {
2892
+ const code = err.code ?? "unknown error";
2893
+ return { ok: false, reason: `target file "${t.path}" could not be read (${code})` };
2894
+ }
2895
+ }
2896
+
2897
+ // src/tracevals/graders/regex.ts
2898
+ function checkFlags(options) {
2899
+ const flags = options.flags;
2900
+ if (flags === void 0) return void 0;
2901
+ if (typeof flags !== "string") return "options.flags must be a string";
2902
+ try {
2903
+ new RegExp("", flags);
2904
+ } catch (err) {
2905
+ return `options.flags is invalid: ${err.message}`;
2906
+ }
2907
+ return void 0;
2908
+ }
2909
+ function checkPattern(options) {
2910
+ const required = requiredString(options, "pattern");
2911
+ if (required !== void 0) return required;
2912
+ try {
2913
+ new RegExp(
2914
+ options.pattern,
2915
+ options.flags ?? ""
2916
+ );
2917
+ } catch (err) {
2918
+ return `options.pattern is not a valid regular expression: ${err.message}`;
2919
+ }
2920
+ return void 0;
2921
+ }
2922
+ function validateOptions7(options) {
2923
+ return firstError(
2924
+ checkFlags(options),
2925
+ checkPattern(options),
2926
+ optionalEnum(options, "on", ["assistant", "user", "all"]),
2927
+ optionalEnum(options, "expect", ["match", "no-match"]),
2928
+ validateWhen(options)
2929
+ );
2930
+ }
2931
+ var regexGrader = {
2932
+ kind: "regex",
2933
+ validateOptions: validateOptions7,
2934
+ grade({ trace, plan, projectRoot }) {
2935
+ const options = plan.options ?? {};
2936
+ const invalid = validateOptions7(options);
2937
+ if (invalid !== void 0) return optionsError("regex", invalid);
2938
+ const pattern = options.pattern;
2939
+ const re = new RegExp(pattern, options.flags ?? "");
2940
+ const on = options.on ?? "assistant";
2941
+ const expect = options.expect ?? "match";
2942
+ const window = windowFor(trace, plan);
2943
+ if (window.empty) return skippedWindow(window);
2944
+ const trigger = evaluateWhen(options, window);
2945
+ if (!trigger.armed) return skippedTrigger(trigger);
2946
+ const target = plan.target ?? "transcript";
2947
+ let corpus;
2948
+ let where;
2949
+ if (target === "transcript") {
2950
+ corpus = on === "user" ? window.userMessages : on === "all" ? [...window.userMessages, ...window.assistantTexts] : window.assistantTexts;
2951
+ where = `${on} text`;
2952
+ } else {
2953
+ const selected = readTarget(target, {
2954
+ trace,
2955
+ // Never consulted: `transcript` took the branch above.
2956
+ renderedTrace: "",
2957
+ artifactContent: plan.artifact.content,
2958
+ root: projectRoot ?? trace.cwd
2959
+ });
2960
+ if (!selected.ok) return optionsError("regex", selected.reason);
2961
+ corpus = [selected.text];
2962
+ where = selected.label;
2963
+ }
2964
+ const matched = corpus.some((text) => re.test(text));
2965
+ if (expect === "match" && !matched) {
2966
+ return fail(plan, `pattern /${pattern}/ never matched ${where}`);
2967
+ }
2968
+ if (expect === "no-match" && matched) {
2969
+ return fail(plan, `pattern /${pattern}/ matched ${where} but must not`);
2970
+ }
2971
+ return pass;
2972
+ }
2973
+ };
2974
+
2975
+ // src/tracevals/graders/json-output.ts
2976
+ import { Ajv2020 } from "ajv/dist/2020.js";
2977
+ var compiled2 = /* @__PURE__ */ new Map();
2978
+ function compileSchema(schema) {
2979
+ const key = JSON.stringify(schema);
2980
+ const hit = compiled2.get(key);
2981
+ if (hit !== void 0) return hit;
2982
+ const built = new Ajv2020({ allErrors: true, strict: false }).compile(schema);
2983
+ compiled2.set(key, built);
2984
+ return built;
2985
+ }
2986
+ function validateOptions8(options) {
2987
+ const schema = options.schema;
2988
+ if (schema === void 0 || schema === null || typeof schema !== "object" || Array.isArray(schema)) {
2989
+ return "options.schema is required and must be a JSON Schema object";
2990
+ }
2991
+ return rejectWhen("json-output", options);
2992
+ }
2993
+ var jsonOutputGrader = {
2994
+ kind: "json-output",
2995
+ validateOptions: validateOptions8,
2996
+ grade({ trace, plan }) {
2997
+ const options = plan.options ?? {};
2998
+ const invalid = validateOptions8(options);
2999
+ if (invalid !== void 0) return optionsError("json-output", invalid);
3000
+ const schema = options.schema;
3001
+ const last = trace.assistantTexts.at(-1);
3002
+ if (last === void 0) {
3003
+ return fail(plan, "session produced no assistant text to validate");
3004
+ }
3005
+ let data;
3006
+ try {
3007
+ data = JSON.parse(last);
3008
+ } catch {
3009
+ return fail(plan, "final assistant output is not valid JSON");
3010
+ }
3011
+ let validate;
3012
+ try {
3013
+ validate = compileSchema(schema);
3014
+ } catch (err) {
3015
+ return optionsError(
3016
+ "json-output",
3017
+ `schema failed to compile: ${err.message}`
3018
+ );
3019
+ }
3020
+ if (validate(data)) return pass;
3021
+ return {
3022
+ findings: (validate.errors ?? []).map(
3023
+ (e) => finding(plan, `${e.instancePath || "/"} ${e.message ?? "invalid"}`)
3024
+ )
3025
+ };
3026
+ }
3027
+ };
3028
+
3029
+ // src/tracevals/graders/command.ts
3030
+ import { createHash } from "node:crypto";
3031
+
3032
+ // src/shared/exec.ts
3033
+ import { realExec } from "@hawkeyexl/inference";
3034
+ function outputTail(result, maxChars = 400) {
3035
+ const text = (result.stderr.trim() || result.stdout.trim()).trim();
3036
+ if (text.length <= maxChars) return text;
3037
+ return `\u2026${text.slice(-maxChars)}`;
3038
+ }
3039
+
3040
+ // src/tracevals/graders/command.ts
3041
+ var DEFAULT_TIMEOUT_MS = 3e4;
3042
+ var STDERR_LIMIT = 2e3;
3043
+ var commandGrader = {
3044
+ kind: "command",
3045
+ async grade(ctx) {
3046
+ const { plan, trace } = ctx;
3047
+ if (!plan.command || plan.command.length === 0) {
3048
+ return {
3049
+ findings: [],
3050
+ error: "command-graded eval has no `command`; add one (or remove the eval) \u2014 manni tracevals does not generate check scripts"
3051
+ };
3052
+ }
3053
+ if (plan.generatedAssertionHash !== void 0) {
3054
+ if (plan.assertion === void 0) {
3055
+ return {
3056
+ findings: [],
3057
+ error: "generated-assertion-hash is present but the eval has no assertion, so the hash cannot be checked against anything; remove the hash or restore the assertion"
3058
+ };
3059
+ }
3060
+ const actual = createHash("sha256").update(plan.assertion).digest("hex");
3061
+ if (actual !== plan.generatedAssertionHash) {
3062
+ return {
3063
+ findings: [],
3064
+ error: `assertion has changed since the command was generated (generated-assertion-hash ${plan.generatedAssertionHash.slice(0, 12)}\u2026, assertion now hashes to ${actual.slice(0, 12)}\u2026); regenerate the command or update the hash`
3065
+ };
3066
+ }
3067
+ }
3068
+ const argv = plan.command.map((part) => part.replaceAll("{trace}", trace.file));
3069
+ const timeoutMs = plan.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3070
+ const successCodes = plan.successExitCodes ?? [0];
3071
+ const run = await execute(argv, ctx.projectRoot ?? trace.cwd, timeoutMs);
3072
+ if (run.timedOut) {
3073
+ return {
3074
+ findings: [],
3075
+ error: `command timed out after ${timeoutMs}ms: ${argv.join(" ")}`
3076
+ };
3077
+ }
3078
+ if (run.spawnError !== void 0) {
3079
+ return { findings: [], error: `could not run command: ${run.spawnError}` };
3080
+ }
3081
+ if (run.code !== null && successCodes.includes(run.code)) {
3082
+ return { findings: [] };
3083
+ }
3084
+ const detail2 = run.stderr.trim();
3085
+ return {
3086
+ findings: [
3087
+ {
3088
+ evalName: plan.evalName,
3089
+ artifact: plan.artifact.path,
3090
+ severity: plan.severity,
3091
+ message: `command exited ${run.code ?? "on a signal"} (expected ${successCodes.join(" or ")})` + (detail2 ? `: ${detail2.slice(0, STDERR_LIMIT)}` : "")
3092
+ }
3093
+ ]
3094
+ };
3095
+ }
3096
+ };
3097
+ async function execute(argv, cwd, timeoutMs) {
3098
+ const result = await realExec(argv, { cwd, timeoutMs });
3099
+ return {
3100
+ code: result.code,
3101
+ // Capped here, not merely when printed: a check script can write far more
3102
+ // to stderr than a report should carry.
3103
+ stderr: result.stderr.slice(0, STDERR_LIMIT),
3104
+ timedOut: result.timedOut,
3105
+ ...result.spawnError === void 0 ? {} : { spawnError: result.spawnError }
3106
+ };
3107
+ }
3108
+
3109
+ // src/tracevals/graders/registry.ts
3110
+ var graders = new Map(
3111
+ [
3112
+ toolUsageGrader,
3113
+ toolOrderGrader,
3114
+ skillInvokedGrader,
3115
+ fileAccessGrader,
3116
+ turnCountGrader,
3117
+ costGrader,
3118
+ regexGrader,
3119
+ jsonOutputGrader,
3120
+ commandGrader
3121
+ ].map((g) => [g.kind, g])
3122
+ );
3123
+ var BUILTIN_GRADER_KINDS = new Set(graders.keys());
3124
+ function registerGrader(grader) {
3125
+ graders.set(grader.kind, grader);
3126
+ }
3127
+ function graderFor(kind) {
3128
+ return graders.get(kind);
3129
+ }
3130
+ function listGraderKinds() {
3131
+ return [...graders.keys()];
3132
+ }
3133
+
3134
+ // src/tracevals/judge/render.ts
3135
+ var DEFAULTS = {
3136
+ maxBlockChars: 2e3,
3137
+ maxTotalChars: 15e4,
3138
+ redact: []
3139
+ };
3140
+ var MIN_TIMELINE_CHARS = 200;
3141
+ var HEADER_TRUNCATED = "\n[... header truncated ...]";
3142
+ var TIMELINE_SHELL = "\n\n## Timeline\n";
3143
+ function renderTrace(trace, options = {}, plan) {
3144
+ const { maxBlockChars, maxTotalChars, redact } = { ...DEFAULTS, ...options };
3145
+ const scrub = makeRedactor(redact);
3146
+ const clip = (text, max) => {
3147
+ const safe = scrub(text);
3148
+ if (safe.length <= max) return safe;
3149
+ return `${safe.slice(0, max)} [... truncated ${safe.length - max} chars ...]`;
3150
+ };
3151
+ const window = plan === void 0 ? void 0 : windowFor(trace, plan);
3152
+ const scoped = window !== void 0 && window.scope !== "session" ? window : void 0;
3153
+ const events = scoped?.events ?? trace.events;
3154
+ const fullHeader = scrub(
3155
+ [
3156
+ "# Session",
3157
+ `source: ${trace.source}`,
3158
+ trace.model ? `model: ${trace.model}` : void 0,
3159
+ `cwd: ${trace.cwd}`,
3160
+ trace.gitBranch ? `branch: ${trace.gitBranch}` : void 0,
3161
+ scoped ? `scope: ${scoped.label} \u2014 ${events.length} of ${trace.events.length} session events` : void 0,
3162
+ // Everything below describes what the judge can actually see. On a
3163
+ // scoped render the timeline is the window, so a session-wide count here
3164
+ // reads as evidence that the rest was truncated away — the judge would
3165
+ // discount a digest that is complete for what it was asked about. The
3166
+ // session totals stay, in parentheses, because "3 of 87" is context the
3167
+ // window count alone does not carry.
3168
+ scoped ? `turns: ${scoped.turnCount} (of ${trace.turnCount} in the session)` : `turns: ${trace.turnCount}`,
3169
+ (() => {
3170
+ const used = (scoped ?? trace).skillInvocations.map((s) => s.name);
3171
+ return used.length > 0 ? `skills used: ${used.join(", ")}` : "skills used: none";
3172
+ })(),
3173
+ (() => {
3174
+ const inWindow = new Set(events.map((e) => e.index));
3175
+ const spawns = trace.agentSpawns.filter(
3176
+ (a) => !scoped || inWindow.has(a.index)
3177
+ );
3178
+ return spawns.length > 0 ? `agents spawned: ${spawns.map((a) => a.subagentType).join(", ")}` : void 0;
3179
+ })(),
3180
+ (() => {
3181
+ const inWindow = new Set(events.map((e) => e.index));
3182
+ const branches = trace.subagentBranches.filter(
3183
+ (b) => !scoped || inWindow.has(b.spawnIndex)
3184
+ );
3185
+ return branches.length > 0 ? `subagent transcripts: ${branches.map((b) => `${b.agentType} (depth ${b.spawnDepth})`).join(", ")}` : void 0;
3186
+ })()
3187
+ ].filter((line) => line !== void 0).join("\n")
3188
+ );
3189
+ const headerRoom = Math.max(
3190
+ maxTotalChars - TIMELINE_SHELL.length - MIN_TIMELINE_CHARS,
3191
+ 0
3192
+ );
3193
+ const header = fullHeader.length <= headerRoom ? fullHeader : fullHeader.slice(
3194
+ 0,
3195
+ Math.max(headerRoom - HEADER_TRUNCATED.length, 0)
3196
+ ) + HEADER_TRUNCATED;
3197
+ const branchTypes = /* @__PURE__ */ new Map();
3198
+ for (const spawn of trace.agentSpawns) {
3199
+ if (spawn.toolUseId) branchTypes.set(spawn.toolUseId, spawn.subagentType);
3200
+ }
3201
+ for (const branch2 of trace.subagentBranches) {
3202
+ branchTypes.set(branch2.branchId, branch2.agentType);
3203
+ }
3204
+ const label = (branchId) => branchTypes.get(branchId) ?? branchId;
3205
+ const callAt = new Map(trace.toolCalls.map((call) => [call.index, call]));
3206
+ if (scoped?.empty === true) {
3207
+ return `${header}${TIMELINE_SHELL}[no turns: ${scrub(scoped.reason ?? "")}]`;
3208
+ }
3209
+ const lines = [];
3210
+ let branch;
3211
+ for (const event of events) {
3212
+ const tag = event.branchId ? `:${label(event.branchId)}` : event.sidechain ? ":sidechain" : "";
3213
+ let line;
3214
+ switch (event.kind) {
3215
+ case "user":
3216
+ if (event.text) line = `[user${tag}] ${clip(event.text, maxBlockChars)}`;
3217
+ break;
3218
+ case "assistant":
3219
+ if (event.text) {
3220
+ line = `[assistant${tag}] ${clip(event.text, maxBlockChars)}`;
3221
+ }
3222
+ break;
3223
+ case "tool_call": {
3224
+ const call = callAt.get(event.index);
3225
+ const input = call ? JSON.stringify(call.input) : "";
3226
+ line = `[tool${tag}] ${event.toolName ?? call?.name ?? "?"} ${clip(input, Math.min(maxBlockChars, 500))}`;
3227
+ break;
3228
+ }
3229
+ default:
3230
+ break;
3231
+ }
3232
+ if (line === void 0) continue;
3233
+ if (event.branchId !== branch) {
3234
+ if (branch !== void 0) lines.push(`[/subagent ${label(branch)}]`);
3235
+ branch = event.branchId;
3236
+ if (branch !== void 0) {
3237
+ lines.push(`[subagent ${label(branch)} (${branch})]`);
3238
+ }
3239
+ }
3240
+ lines.push(line);
3241
+ }
3242
+ if (branch !== void 0) lines.push(`[/subagent ${label(branch)}]`);
3243
+ let timeline = scrub(lines.join("\n"));
3244
+ const budget = Math.max(
3245
+ maxTotalChars - header.length - TIMELINE_SHELL.length,
3246
+ MIN_TIMELINE_CHARS
3247
+ );
3248
+ if (timeline.length > budget) {
3249
+ const marker = (omitted2) => `
3250
+ [... truncated ${omitted2} chars of transcript ...]
3251
+ `;
3252
+ const room = Math.max(budget - marker(timeline.length).length, 0);
3253
+ const headLen = Math.floor(room * 0.6);
3254
+ const tailLen = room - headLen;
3255
+ const omitted = timeline.length - headLen - tailLen;
3256
+ timeline = `${timeline.slice(0, headLen)}${marker(omitted)}${tailLen > 0 ? timeline.slice(-tailLen) : ""}`;
3257
+ }
3258
+ return `${header}${TIMELINE_SHELL}${timeline}`;
3259
+ }
3260
+
3261
+ // src/tracevals/core/engine.ts
3262
+ async function runEvals(options) {
3263
+ const start = Date.now();
3264
+ const { config } = options;
3265
+ const trace = await parseTraceFile(options.tracePath);
3266
+ const refusals = [];
3267
+ const found = await findManifest({
3268
+ tracePath: options.tracePath,
3269
+ ...trace.sessionId !== void 0 ? { sessionId: trace.sessionId } : {},
3270
+ projectDir: options.projectDir ?? trace.cwd,
3271
+ captureDir: config.capture.dir,
3272
+ ...options.manifest !== void 0 ? { explicit: options.manifest } : {},
3273
+ onRefused: (path, reason) => refusals.push({ path, reason })
3274
+ });
3275
+ if (found === null && options.manifest !== void 0) {
3276
+ throw new TracevalsError(
3277
+ `no usable session manifest at ${options.manifest} \u2014 ${refusals[0]?.reason ?? "it is missing, unreadable, or was recorded for a different session"}`
3278
+ );
3279
+ }
3280
+ const manifestWarnings = found === null ? refusals.map(
3281
+ (r) => `ignoring the session manifest at ${r.path} \u2014 ${r.reason}`
3282
+ ) : [];
3283
+ const resolved = await resolveArtifacts(trace, {
3284
+ ...options.projectDir !== void 0 ? { projectDir: options.projectDir, projectRoot: options.projectDir } : {},
3285
+ ...options.env !== void 0 ? { env: options.env } : {},
3286
+ reportUnusedArtifacts: config.reportUnusedArtifacts,
3287
+ ...found !== null ? { manifest: found.manifest } : {}
3288
+ });
3289
+ const loader = options.metadataFor;
3290
+ const metadata = /* @__PURE__ */ new Map();
3291
+ if (loader !== void 0) {
3292
+ for (const artifact of resolved.artifacts) {
3293
+ metadata.set(artifact, await loader(artifact));
3294
+ }
3295
+ }
3296
+ const plans = planEvals(
3297
+ resolved.artifacts,
3298
+ loader === void 0 ? void 0 : (artifact) => metadata.get(artifact)
3299
+ );
3300
+ const results = [];
3301
+ const aiPlans = [];
3302
+ const root = normalizeRoot(process.cwd());
3303
+ for (const plan of plans) {
3304
+ const base = {
3305
+ evalName: plan.evalName,
3306
+ artifact: plan.artifact.path,
3307
+ artifactName: plan.artifact.name,
3308
+ artifactType: plan.artifact.type,
3309
+ grader: plan.grader,
3310
+ implicit: plan.implicit,
3311
+ location: spelled(plan.location, root)
3312
+ };
3313
+ if (plan.error !== void 0) {
3314
+ results.push({ ...base, outcome: "error", error: plan.error, durationMs: 0 });
3315
+ continue;
3316
+ }
3317
+ if (plan.skipped) {
3318
+ results.push({
3319
+ ...base,
3320
+ outcome: "skipped",
3321
+ skipReason: plan.skipReason ?? "eval skipped",
3322
+ durationMs: 0
3323
+ });
3324
+ continue;
3325
+ }
3326
+ if (plan.grader === "ai" || plan.grader === "human") {
3327
+ const window = windowFor(trace, plan);
3328
+ if (window.empty) {
3329
+ results.push({
3330
+ ...base,
3331
+ outcome: "skipped",
3332
+ skipReason: window.reason ?? "the artifact governed no turns",
3333
+ durationMs: 0
3334
+ });
3335
+ continue;
3336
+ }
3337
+ }
3338
+ if (plan.grader === "ai") {
3339
+ aiPlans.push(plan);
3340
+ continue;
3341
+ }
3342
+ if (plan.grader === "human") {
3343
+ results.push({
3344
+ ...base,
3345
+ outcome: "needs-review",
3346
+ skipReason: "awaiting human review",
3347
+ durationMs: 0
3348
+ });
3349
+ continue;
3350
+ }
3351
+ if (plan.grader === "command" && !config.execution.allow.includes("frontmatter-commands")) {
3352
+ results.push({
3353
+ ...base,
3354
+ outcome: "skipped",
3355
+ skipReason: NOT_GRANTED_REASON,
3356
+ durationMs: 0
3357
+ });
3358
+ continue;
3359
+ }
3360
+ const grader = graderFor(plan.grader);
3361
+ if (!grader) {
3362
+ results.push({
3363
+ ...base,
3364
+ outcome: "error",
3365
+ error: `unknown grader kind "${plan.grader}"; the grader registry knows ${listGraderKinds().sort().join(", ")}, "ai", and "human"`,
3366
+ durationMs: 0
3367
+ });
3368
+ continue;
3369
+ }
3370
+ const graded = Date.now();
3371
+ let result;
3372
+ try {
3373
+ result = await grader.grade({
3374
+ trace,
3375
+ plan,
3376
+ ...options.projectDir !== void 0 ? { projectRoot: options.projectDir } : {}
3377
+ });
3378
+ } catch (err) {
3379
+ results.push({
3380
+ ...base,
3381
+ outcome: "error",
3382
+ error: `${plan.grader}: ${err instanceof Error ? err.message : String(err)}`,
3383
+ durationMs: Date.now() - graded
3384
+ });
3385
+ continue;
3386
+ }
3387
+ if (result.error !== void 0) {
3388
+ results.push({
3389
+ ...base,
3390
+ outcome: "error",
3391
+ error: result.error,
3392
+ durationMs: Date.now() - graded
3393
+ });
3394
+ continue;
3395
+ }
3396
+ if (result.skipped !== void 0) {
3397
+ results.push({
3398
+ ...base,
3399
+ outcome: "skipped",
3400
+ skipReason: result.skipped,
3401
+ durationMs: Date.now() - graded
3402
+ });
3403
+ continue;
3404
+ }
3405
+ const failing2 = result.findings.some((f) => f.severity === "error");
3406
+ results.push({
3407
+ ...base,
3408
+ outcome: failing2 ? "fail" : "pass",
3409
+ findings: result.findings,
3410
+ durationMs: Date.now() - graded
3411
+ });
3412
+ }
3413
+ if (aiPlans.length > 0) {
3414
+ if (options.deterministicOnly || options.judge === void 0) {
3415
+ const skipReason = options.deterministicOnly ? "ai evals skipped (deterministic-only run)" : "ai evals skipped (no judge provided)";
3416
+ for (const plan of aiPlans) {
3417
+ results.push({
3418
+ evalName: plan.evalName,
3419
+ artifact: plan.artifact.path,
3420
+ artifactName: plan.artifact.name,
3421
+ artifactType: plan.artifact.type,
3422
+ grader: plan.grader,
3423
+ implicit: plan.implicit,
3424
+ location: spelled(plan.location, root),
3425
+ outcome: "skipped",
3426
+ skipReason,
3427
+ durationMs: 0
3428
+ });
3429
+ }
3430
+ } else {
3431
+ const judged = await options.judge(
3432
+ aiPlans,
3433
+ (plan) => renderTrace(
3434
+ trace,
3435
+ { ...config.render, redact: config.judge.redact },
3436
+ plan
3437
+ ),
3438
+ {
3439
+ trace,
3440
+ ...options.projectDir !== void 0 ? { projectRoot: options.projectDir } : {}
3441
+ }
3442
+ );
3443
+ judged.forEach((j, i) => {
3444
+ const plan = aiPlans[i];
3445
+ results.push({
3446
+ ...j.selfPreference ? { selfPreference: j.selfPreference } : {},
3447
+ evalName: j.evalName,
3448
+ artifact: j.artifact,
3449
+ artifactName: j.artifactName,
3450
+ artifactType: plan?.artifact.type ?? "skill",
3451
+ grader: j.grader,
3452
+ implicit: j.implicit,
3453
+ location: spelled(plan?.location ?? { file: j.artifact }, root),
3454
+ outcome: j.outcome,
3455
+ ...j.consensus !== void 0 ? { consensus: j.consensus } : {},
3456
+ ...j.skipReason !== void 0 ? { skipReason: j.skipReason } : {},
3457
+ ...j.error !== void 0 ? { error: j.error } : {},
3458
+ turns: j.turns,
3459
+ durationMs: j.durationMs
3460
+ });
3461
+ });
3462
+ }
3463
+ }
3464
+ const weightOf = /* @__PURE__ */ new Map();
3465
+ for (const plan of plans) {
3466
+ weightOf.set(weightKey(plan.artifact.path, plan.evalName), plan.weight ?? 1);
3467
+ }
3468
+ for (const r of results) {
3469
+ r.weight = weightOf.get(weightKey(r.artifact, r.evalName)) ?? 1;
3470
+ }
3471
+ const gradedRows = results.filter(
3472
+ (r) => r.outcome === "pass" || r.outcome === "fail" || r.outcome === "error"
3473
+ );
3474
+ const gradedWeight = gradedRows.reduce((n, r) => n + (r.weight ?? 1), 0);
3475
+ const summary = {
3476
+ total: results.length,
3477
+ pass: results.filter((r) => r.outcome === "pass").length,
3478
+ fail: results.filter((r) => r.outcome === "fail").length,
3479
+ error: results.filter((r) => r.outcome === "error").length,
3480
+ needsReview: results.filter((r) => r.outcome === "needs-review").length,
3481
+ skipped: results.filter((r) => r.outcome === "skipped").length,
3482
+ passRate: gradedWeight > 0 ? gradedRows.filter((r) => r.outcome === "pass").reduce((n, r) => n + (r.weight ?? 1), 0) / gradedWeight : 1
3483
+ };
3484
+ const failing = summary.fail > 0 || summary.error > 0 || config.failOnReview && summary.needsReview > 0;
3485
+ return {
3486
+ trace: {
3487
+ file: trace.file,
3488
+ source: trace.source,
3489
+ ...trace.sessionId !== void 0 ? { sessionId: trace.sessionId } : {},
3490
+ cwd: trace.cwd,
3491
+ ...trace.model !== void 0 ? { model: trace.model } : {},
3492
+ turnCount: trace.turnCount
3493
+ },
3494
+ warnings: [
3495
+ ...options.warnings ?? [],
3496
+ ...trace.warnings,
3497
+ ...manifestWarnings,
3498
+ ...resolved.warnings,
3499
+ ...vanityMetricWarnings(plans),
3500
+ ...truncatedArtifactWarnings(plans)
3501
+ ],
3502
+ coverage: resolved.coverage,
3503
+ // An observation about the session's configuration, never a verdict: it is
3504
+ // deliberately not part of `summary` and never moves `exitCode`.
3505
+ availability: resolved.availability,
3506
+ ...found !== null ? { manifest: summariseManifest(found, resolved.coverage) } : {},
3507
+ evalResults: results,
3508
+ summary,
3509
+ exitCode: failing ? 1 : 0,
3510
+ turns: results.reduce((sum, r) => sum + (r.turns ?? 0), 0),
3511
+ durationMs: Date.now() - start
3512
+ };
3513
+ }
3514
+ function spelled(location, root) {
3515
+ if (classifyRef(location.file).kind === "url") return location;
3516
+ const posix = toPosix(location.file);
3517
+ return { ...location, file: underRoot(posix, root) ?? posix };
3518
+ }
3519
+ function weightKey(artifact, evalName) {
3520
+ return `${artifact}\0${evalName}`;
3521
+ }
3522
+ function vanityMetricWarnings(plans) {
3523
+ const byArtifact = /* @__PURE__ */ new Map();
3524
+ for (const plan of plans) {
3525
+ if (plan.implicit) continue;
3526
+ const list = byArtifact.get(plan.artifact.path) ?? [];
3527
+ list.push(plan);
3528
+ byArtifact.set(plan.artifact.path, list);
3529
+ }
3530
+ const warnings = [];
3531
+ for (const [path, group] of byArtifact) {
3532
+ if (group.length > 0 && group.every((p) => p.grader === "skill-invoked")) {
3533
+ warnings.push(
3534
+ `${path}: every eval here is skill-invoked, which checks that the skill fired, not that the session followed it. Add at least one eval about what the session actually did.`
3535
+ );
3536
+ }
3537
+ }
3538
+ return warnings;
3539
+ }
3540
+ function truncatedArtifactWarnings(plans) {
3541
+ const seen = /* @__PURE__ */ new Set();
3542
+ const warnings = [];
3543
+ for (const plan of plans) {
3544
+ if (plan.grader !== "ai") continue;
3545
+ const path = plan.artifact.path;
3546
+ if (seen.has(path)) continue;
3547
+ if (!artifactWasTruncated(plan.artifact.content)) continue;
3548
+ seen.add(path);
3549
+ warnings.push(
3550
+ `${path}: only the first ${String(MAX_ARTIFACT_CHARS)} characters were shown to the judge (the artifact is ${String(plan.artifact.content.length)}). Verdicts about instructions beyond that point rest on text the judge never read.`
3551
+ );
3552
+ }
3553
+ return warnings;
3554
+ }
3555
+ function summariseManifest(found, coverage) {
3556
+ const checked = coverage.filter((c) => c.contentCheck !== void 0);
3557
+ return {
3558
+ path: found.path,
3559
+ sessionId: found.manifest.sessionId,
3560
+ capturedAt: found.manifest.capturedAt,
3561
+ ...found.manifest.git !== void 0 ? { gitSha: found.manifest.git.sha } : {},
3562
+ matched: checked.filter((c) => c.contentCheck?.status === "match").length,
3563
+ changed: checked.filter((c) => c.contentCheck?.status === "mismatch").length,
3564
+ unrecorded: checked.filter((c) => c.contentCheck?.status === "skipped").length
3565
+ };
3566
+ }
3567
+
3568
+ // src/tracevals/evals/external.ts
3569
+ import { relative as relative3, resolve as resolve3, sep as sep2 } from "node:path";
3570
+ var METADATA_KEY = "metadata";
3571
+ var MARKED = /* @__PURE__ */ new Set([METADATA_KEY]);
3572
+ var toPosix2 = (path) => path.split(sep2).join("/");
3573
+ function twoManifestsRefusal(label, a, b) {
3574
+ return `${label} is in collections ${a} and ${b}, and both keep ${METADATA_KEY} in a manifest.`;
3575
+ }
3576
+ function urlManifestRefusal(label, file) {
3577
+ return `${label}: ${METADATA_KEY} is owned by ${file}, and a URL manifest cannot be written. Vendor it to a path, or run fill with --dry-run.`;
3578
+ }
3579
+ function ownsMetadata(collection, manifest) {
3580
+ return ownsKey(collection, manifest, METADATA_KEY, MARKED);
3581
+ }
3582
+ function loadExternalEvals(opts) {
3583
+ const scoped = [];
3584
+ for (const collection of opts.collections) {
3585
+ const owning = collection.externalMetadata.filter((m) => ownsMetadata(collection, m));
3586
+ if (owning.length === 0) continue;
3587
+ scoped.push({ ...collection, externalMetadata: owning });
3588
+ }
3589
+ return Promise.resolve(scoped.length === 0 ? null : reader(scoped, opts));
3590
+ }
3591
+ function reader(scoped, opts) {
3592
+ const { configDir } = opts;
3593
+ const base = configDir;
3594
+ const declared = opts.collections;
3595
+ const marks = familyMarks({ configPath: opts.configPath, configDir, cwd: base });
3596
+ const perPage = scoped.some(
3597
+ (c) => c.externalMetadata.some((m) => hasPagePlaceholder(m.file))
3598
+ );
3599
+ const load = async (pages) => {
3600
+ try {
3601
+ return await loadExternalMetadata(scoped, {
3602
+ configDir,
3603
+ base,
3604
+ ...opts.offline === void 0 ? {} : { offline: opts.offline },
3605
+ ...pages === void 0 ? {} : { pages }
3606
+ });
3607
+ } catch (err) {
3608
+ if (err instanceof DocmetaError) throw new TracevalsError(err.message);
3609
+ throw err;
3610
+ }
3611
+ };
3612
+ let shared;
3613
+ const own = /* @__PURE__ */ new Map();
3614
+ const indexFor = (abs) => {
3615
+ if (!perPage) return shared ??= load();
3616
+ let index = own.get(abs);
3617
+ if (index === void 0) {
3618
+ index = load([abs]);
3619
+ own.set(abs, index);
3620
+ }
3621
+ return index;
3622
+ };
3623
+ const forArtifact = async (artifact) => {
3624
+ const label = artifact.path;
3625
+ const extracted = extractFrontmatter(artifact.content, "markdown");
3626
+ const members = memberOf(declared, configDir, base, label);
3627
+ const [first, second] = scoped.filter((c) => members.includes(c.name));
3628
+ if (first === void 0) return { extracted };
3629
+ if (second !== void 0) {
3630
+ throw new TracevalsError(twoManifestsRefusal(label, first.name, second.name));
3631
+ }
3632
+ const merged = await mergeWithMarks(
3633
+ label,
3634
+ extracted,
3635
+ await indexFor(resolve3(base, label)),
3636
+ [first.name],
3637
+ base,
3638
+ { encryptionKey: () => opts.key, marks: await marks() }
3639
+ );
3640
+ const locate2 = (pointer) => {
3641
+ const found = merged.locate(pointer);
3642
+ if (found === void 0 || classifyRef(found.file).kind === "url") return found;
3643
+ return { ...found, file: resolve3(base, found.file) };
3644
+ };
3645
+ const at = merged.locate(`/${METADATA_KEY}`);
3646
+ const [declaration] = first.externalMetadata;
3647
+ if (at === void 0 || declaration === void 0) {
3648
+ return { extracted: merged.extracted, locate: locate2 };
3649
+ }
3650
+ const url = classifyRef(at.file).kind === "url";
3651
+ const owner = {
3652
+ collection: first.name,
3653
+ path: url ? at.file : resolve3(base, at.file),
3654
+ file: at.file,
3655
+ join: externalMetadataJoin(declaration),
3656
+ url
3657
+ };
3658
+ const out = { extracted: merged.extracted, locate: locate2, owner };
3659
+ if (owner.join === PATH_JOIN) {
3660
+ out.entry = toPosix2(relative3(configDir, resolve3(base, label)));
3661
+ } else {
3662
+ const value = merged.joins.find((j) => j.field === owner.join)?.value ?? extracted.data[owner.join];
3663
+ if (typeof value === "string" && value !== "") out.entry = value;
3664
+ else if (typeof value === "number" || typeof value === "boolean") {
3665
+ out.entry = String(value);
3666
+ }
3667
+ }
3668
+ return out;
3669
+ };
3670
+ return { configDir, forArtifact };
3671
+ }
3672
+
3673
+ // src/tracevals/graders/plugins.ts
3674
+ import { createRequire } from "node:module";
3675
+ import { isAbsolute as isAbsolute3, join as join2, resolve as resolve4 } from "node:path";
3676
+ import { pathToFileURL } from "node:url";
3677
+ var alreadyLoaded = /* @__PURE__ */ new Set();
3678
+ var RELATIVE = /^\.\.?[/\\]/;
3679
+ function locate(specifier, configDir) {
3680
+ if (specifier.startsWith("file:")) return { href: specifier };
3681
+ if (RELATIVE.test(specifier) || isAbsolute3(specifier)) {
3682
+ const path = resolve4(configDir, specifier);
3683
+ return { href: pathToFileURL(path).href, shown: path };
3684
+ }
3685
+ try {
3686
+ const path = createRequire(join2(resolve4(configDir), "package.json")).resolve(
3687
+ specifier
3688
+ );
3689
+ return { href: pathToFileURL(path).href, shown: path };
3690
+ } catch {
3691
+ return { href: specifier };
3692
+ }
3693
+ }
3694
+ function registrarIn(mod) {
3695
+ if (typeof mod.register === "function") {
3696
+ return mod.register;
3697
+ }
3698
+ if (typeof mod.default === "function") {
3699
+ return mod.default;
3700
+ }
3701
+ return void 0;
3702
+ }
3703
+ function snapshot() {
3704
+ const registered = /* @__PURE__ */ new Map();
3705
+ for (const kind of listGraderKinds()) {
3706
+ const grader = graderFor(kind);
3707
+ if (grader !== void 0) registered.set(kind, grader);
3708
+ }
3709
+ return registered;
3710
+ }
3711
+ function changesSince(specifier, before) {
3712
+ const added = [];
3713
+ const replaced = [];
3714
+ for (const kind of listGraderKinds()) {
3715
+ const previous = before.get(kind);
3716
+ if (previous === void 0) added.push(kind);
3717
+ else if (previous !== graderFor(kind)) replaced.push(kind);
3718
+ }
3719
+ const warnings = [];
3720
+ if (added.length === 0 && replaced.length === 0) {
3721
+ warnings.push(
3722
+ `grader plugin "${specifier}" registered no grader kinds \u2014 export a \`register({ registerGrader })\` function, or call registerGrader while the module is imported. Any eval naming a custom grader will report \`unknown grader kind\`.`
3723
+ );
3724
+ }
3725
+ for (const kind of replaced) {
3726
+ warnings.push(
3727
+ BUILTIN_GRADER_KINDS.has(kind) ? `grader plugin "${specifier}" replaced the built-in grader "${kind}"; every eval declaring that kind now runs the plugin's implementation` : `grader plugin "${specifier}" replaced the grader "${kind}" registered by an earlier plugin`
3728
+ );
3729
+ }
3730
+ return warnings;
3731
+ }
3732
+ async function loadGraderPlugins(options) {
3733
+ const loaded = [];
3734
+ const warnings = [];
3735
+ for (const specifier of options.plugins) {
3736
+ const { href, shown } = locate(specifier, options.configDir);
3737
+ if (alreadyLoaded.has(href)) continue;
3738
+ const before = snapshot();
3739
+ let mod;
3740
+ try {
3741
+ mod = await import(href);
3742
+ } catch (err) {
3743
+ throw new TracevalsError(
3744
+ `could not load grader plugin "${specifier}"` + (shown !== void 0 ? ` (resolved to ${shown})` : "") + `: ${err instanceof Error ? err.message : String(err)}`
3745
+ );
3746
+ }
3747
+ const register = registrarIn(mod);
3748
+ if (register !== void 0) {
3749
+ try {
3750
+ await register({ registerGrader });
3751
+ } catch (err) {
3752
+ throw new TracevalsError(
3753
+ `grader plugin "${specifier}" threw while registering: ` + (err instanceof Error ? err.message : String(err))
3754
+ );
3755
+ }
3756
+ }
3757
+ alreadyLoaded.add(href);
3758
+ warnings.push(...changesSince(specifier, before));
3759
+ loaded.push(specifier);
3760
+ }
3761
+ return { loaded, warnings };
3762
+ }
3763
+
3764
+ // src/tracevals/history.ts
3765
+ import { appendFile, mkdir, readFile as readFile2 } from "node:fs/promises";
3766
+ import { dirname as dirname2 } from "node:path";
3767
+ function entryFor(report) {
3768
+ return {
3769
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3770
+ traceFile: report.trace.file,
3771
+ ...report.trace.sessionId !== void 0 ? { sessionId: report.trace.sessionId } : {},
3772
+ project: report.trace.cwd,
3773
+ summary: report.summary,
3774
+ exitCode: report.exitCode,
3775
+ turns: report.turns,
3776
+ evals: report.evalResults.map((r) => ({
3777
+ artifact: r.artifact,
3778
+ evalName: r.evalName,
3779
+ outcome: r.outcome
3780
+ }))
3781
+ };
3782
+ }
3783
+ async function appendHistory(file, report) {
3784
+ try {
3785
+ await mkdir(dirname2(file), { recursive: true });
3786
+ await appendFile(file, `${JSON.stringify(entryFor(report))}
3787
+ `, "utf-8");
3788
+ } catch (err) {
3789
+ warn(
3790
+ `could not write history at ${file} (${errorMessage(err)}). Continuing.`
3791
+ );
3792
+ }
3793
+ }
3794
+ async function loadHistory(file) {
3795
+ let content;
3796
+ try {
3797
+ content = await readFile2(file, "utf-8");
3798
+ } catch {
3799
+ return [];
3800
+ }
3801
+ const entries = [];
3802
+ for (const line of content.split("\n")) {
3803
+ const trimmed = line.trim();
3804
+ if (!trimmed) continue;
3805
+ try {
3806
+ entries.push(JSON.parse(trimmed));
3807
+ } catch {
3808
+ continue;
3809
+ }
3810
+ }
3811
+ return entries;
3812
+ }
3813
+ var GOOD = /* @__PURE__ */ new Set(["pass"]);
3814
+ var BAD = /* @__PURE__ */ new Set(["fail", "error"]);
3815
+ function compareToLast(history, report) {
3816
+ const key = report.trace.sessionId ?? report.trace.file;
3817
+ const previous = [...history].reverse().find((e) => (e.sessionId ?? e.traceFile) === key);
3818
+ if (!previous) return null;
3819
+ const keyOf = (e) => `${e.artifact}::${e.evalName}`;
3820
+ const before = new Map(previous.evals.map((e) => [keyOf(e), e]));
3821
+ const current = entryFor(report).evals;
3822
+ const currentKeys = new Set(current.map(keyOf));
3823
+ const regressions = [];
3824
+ const improvements = [];
3825
+ const added = [];
3826
+ for (const e of current) {
3827
+ const prior = before.get(keyOf(e));
3828
+ if (!prior) {
3829
+ added.push(e.evalName);
3830
+ continue;
3831
+ }
3832
+ if (GOOD.has(prior.outcome) && BAD.has(e.outcome)) regressions.push(e);
3833
+ if (BAD.has(prior.outcome) && GOOD.has(e.outcome)) improvements.push(e);
3834
+ }
3835
+ const removed = previous.evals.filter((e) => !currentKeys.has(keyOf(e))).map((e) => e.evalName);
3836
+ return {
3837
+ previousTimestamp: previous.timestamp,
3838
+ regressions,
3839
+ improvements,
3840
+ added,
3841
+ removed
3842
+ };
3843
+ }
3844
+
3845
+ // src/tracevals/judge/provider.ts
3846
+ import {
3847
+ makeProvider as makeInferenceProvider,
3848
+ mockVerdict
3849
+ } from "@hawkeyexl/inference";
3850
+ var CONFIG_KEY = "tracevals.provider";
3851
+ var toTracevalsError = (message) => new TracevalsError(message);
3852
+ function selectProvider2(config, flags = {}, ev = {}) {
3853
+ assertLocalFlag(flags, toTracevalsError);
3854
+ return selectProvider(flags, [
3855
+ {
3856
+ ...ev.provider !== void 0 ? { provider: ev.provider } : {},
3857
+ ...ev.model !== void 0 ? { model: ev.model } : {},
3858
+ ...ev.origin !== void 0 ? { origin: ev.origin } : {}
3859
+ },
3860
+ {
3861
+ ...config.provider !== null ? { provider: config.provider } : {},
3862
+ ...config.model !== null ? { model: config.model } : {},
3863
+ origin: CONFIG_KEY
3864
+ },
3865
+ {
3866
+ ...config.providers.provider !== void 0 ? { provider: config.providers.provider } : {},
3867
+ ...config.providers.model !== void 0 ? { model: config.providers.model } : {},
3868
+ origin: "providers.provider"
3869
+ }
3870
+ ]);
3871
+ }
3872
+ function announceSelection(selection) {
3873
+ const message = localNotice(selection);
3874
+ if (message !== void 0) noticeOnce(message);
3875
+ }
3876
+ function assertProviderSelection(selection) {
3877
+ assertKnownProvider(selection.provider, toTracevalsError);
3878
+ assertModelHasProvider(selection.provider, selection.model, CONFIG_KEY, toTracevalsError);
3879
+ }
3880
+ function providerSpecFor2(config, selection, options = {}) {
3881
+ const spec = providerSpecFor(config.providers, selection);
3882
+ switch (selection.provider) {
3883
+ case "anthropic":
3884
+ return { ...spec, anthropic: { toolName: "record_verdict" } };
3885
+ case "openai":
3886
+ return { ...spec, openai: { schemaName: "verdict" } };
3887
+ case "mock":
3888
+ return {
3889
+ ...spec,
3890
+ mockResponses: options.mockResponses ?? [mockVerdict("pass", 0.95)]
3891
+ };
3892
+ default:
3893
+ return spec;
3894
+ }
3895
+ }
3896
+ async function resolveProviderIdentity(config, flags = {}, ev = {}) {
3897
+ const selection = selectProvider2(config, flags, ev);
3898
+ const { provider, model } = selection;
3899
+ assertKnownProvider(provider, toTracevalsError);
3900
+ assertModelHasProvider(provider, model, CONFIG_KEY, toTracevalsError);
3901
+ announceSelection(selection);
3902
+ return resolveIdentity(
3903
+ providerSpecFor2(config, { provider, model: model ?? null }),
3904
+ toTracevalsError
3905
+ );
3906
+ }
3907
+ function constructProvider(config, identity, options = {}) {
3908
+ try {
3909
+ return makeInferenceProvider(providerSpecFor2(config, identity, options));
3910
+ } catch (e) {
3911
+ throw new TracevalsError(errorMessage(e));
3912
+ }
3913
+ }
3914
+ async function makeJudgeProvider(config, flags = {}, ev = {}, options = {}) {
3915
+ return constructProvider(
3916
+ config,
3917
+ await resolveProviderIdentity(config, flags, ev),
3918
+ options
3919
+ );
3920
+ }
3921
+
3922
+ // src/tracevals/judge/trace-judge.ts
3923
+ import {
3924
+ computeConsensus,
3925
+ runEnsemble,
3926
+ zoneFor
3927
+ } from "@hawkeyexl/inference";
3928
+
3929
+ // src/tracevals/judge/verdict-schema.json
3930
+ var verdict_schema_default = {
3931
+ $schema: "https://json-schema.org/draft/2020-12/schema",
3932
+ $id: "manni:tracevals:verdict",
3933
+ title: "manni tracevals judge verdict",
3934
+ type: "object",
3935
+ required: ["claim", "observed", "match", "confidence", "reasoning"],
3936
+ properties: {
3937
+ claim: {
3938
+ type: "string",
3939
+ description: "The specific instruction or assertion under evaluation."
3940
+ },
3941
+ observed: {
3942
+ type: "string",
3943
+ description: "What actually happened in the session, citing specific messages, tool calls, or instructions followed/violated."
3944
+ },
3945
+ match: {
3946
+ enum: ["pass", "fail", "partial"],
3947
+ description: "pass only if the session fully adhered; partial for partial adherence."
3948
+ },
3949
+ confidence: {
3950
+ type: "number",
3951
+ minimum: 0,
3952
+ maximum: 1,
3953
+ description: "Self-reported confidence in this verdict."
3954
+ },
3955
+ reasoning: {
3956
+ type: "string",
3957
+ description: "Why this conclusion was reached."
3958
+ }
3959
+ },
3960
+ additionalProperties: false
3961
+ };
3962
+
3963
+ // src/tracevals/judge/cache.ts
3964
+ import { JsonCache, buildCacheKey, sha256 } from "@hawkeyexl/inference";
3965
+ var VerdictCache = class _VerdictCache extends JsonCache {
3966
+ /** An ensemble is cacheable only if every run produced a verdict. */
3967
+ static usable(runs) {
3968
+ return runs.every((r) => r.error === void 0 && r.verdict !== void 0);
3969
+ }
3970
+ set(key, value) {
3971
+ if (!_VerdictCache.usable(value)) return;
3972
+ super.set(key, value);
3973
+ }
3974
+ get(key) {
3975
+ const hit = super.get(key);
3976
+ if (hit === void 0) return void 0;
3977
+ return _VerdictCache.usable(hit) ? hit : void 0;
3978
+ }
3979
+ };
3980
+ function cacheKey(provider, model, runs, temperature, renderedTrace, plan, promptVersion = PROMPT_VERSION) {
3981
+ const planFingerprint = JSON.stringify({
3982
+ assertion: plan.assertion,
3983
+ evidence: plan.evidence,
3984
+ examples: plan.examples,
3985
+ artifact: plan.artifact.content
3986
+ });
3987
+ return buildCacheKey([
3988
+ provider,
3989
+ model,
3990
+ `v${promptVersion}`,
3991
+ `r${runs}`,
3992
+ `t${temperature}`,
3993
+ // Pre-hashed: traces and artifacts are large, and key parts stay short.
3994
+ sha256(renderedTrace),
3995
+ sha256(planFingerprint)
3996
+ ]);
3997
+ }
3998
+
3999
+ // src/tracevals/judge/trace-judge.ts
4000
+ var verdictSchema = verdict_schema_default;
4001
+ function makeTraceJudge(options) {
4002
+ const provider = options.provider;
4003
+ const runsFor = (plan) => options.runs ?? plan.runs ?? 3;
4004
+ const temperature = options.temperature ?? 0;
4005
+ const zones = options.zones ?? { autoPass: 0.8, autoFail: 0.8 };
4006
+ const cache = new VerdictCache(
4007
+ options.cacheDir ?? ".manni/tracevals/cache",
4008
+ options.noCache !== true && options.cacheDir !== void 0,
4009
+ "manni-tracevals"
4010
+ );
4011
+ const maxTurns = options.maxTurns ?? null;
4012
+ let turnsSpent = 0;
4013
+ return async (plans, renderFor, context) => {
4014
+ const trace = context?.trace;
4015
+ const sessionModel = trace?.model;
4016
+ const results = [];
4017
+ for (const plan of plans) {
4018
+ const start = Date.now();
4019
+ const base = {
4020
+ evalName: plan.evalName,
4021
+ artifact: plan.artifact.path,
4022
+ artifactName: plan.artifact.name,
4023
+ grader: plan.grader,
4024
+ implicit: plan.implicit
4025
+ };
4026
+ let evalProvider = provider;
4027
+ if (plan.provider !== void 0 || plan.model !== void 0) {
4028
+ const resolved = await resolveOverride(plan, options);
4029
+ if ("error" in resolved) {
4030
+ results.push({
4031
+ ...base,
4032
+ outcome: "error",
4033
+ error: resolved.error,
4034
+ turns: 0,
4035
+ durationMs: Date.now() - start
4036
+ });
4037
+ continue;
4038
+ }
4039
+ evalProvider = resolved.provider;
4040
+ }
4041
+ const renderedTrace = renderFor(plan);
4042
+ const selected = trace === void 0 ? plan.target === void 0 || plan.target === "transcript" || plan.target === "artifact" ? {
4043
+ ok: true,
4044
+ text: plan.target === "artifact" ? plan.artifact.content : renderedTrace,
4045
+ label: plan.target === "artifact" ? "artifact" : "transcript"
4046
+ } : {
4047
+ ok: false,
4048
+ reason: `target "${describeTarget(plan.target)}" needs the parsed session, which this run did not supply`
4049
+ } : readTarget(plan.target, {
4050
+ trace,
4051
+ renderedTrace,
4052
+ artifactContent: plan.artifact.content,
4053
+ root: context?.projectRoot ?? trace.cwd
4054
+ });
4055
+ if (!selected.ok) {
4056
+ results.push({
4057
+ ...base,
4058
+ outcome: "error",
4059
+ error: selected.reason,
4060
+ turns: 0,
4061
+ durationMs: Date.now() - start
4062
+ });
4063
+ continue;
4064
+ }
4065
+ const runsPerEval = runsFor(plan);
4066
+ const key = cacheKey(
4067
+ evalProvider.provider(),
4068
+ evalProvider.modelName(),
4069
+ runsPerEval,
4070
+ temperature,
4071
+ // The selected bytes, not always the transcript: two targets on one
4072
+ // session are two different questions and must not share a verdict.
4073
+ selected.text,
4074
+ plan
4075
+ );
4076
+ const cached = cache.get(key) !== void 0;
4077
+ if (maxTurns !== null && !cached) {
4078
+ if (turnsSpent + runsPerEval > maxTurns) {
4079
+ results.push({
4080
+ ...base,
4081
+ outcome: "skipped",
4082
+ skipReason: turnBudgetSkipReason(maxTurns),
4083
+ turns: 0,
4084
+ durationMs: 0
4085
+ });
4086
+ continue;
4087
+ }
4088
+ turnsSpent += runsPerEval;
4089
+ }
4090
+ const runs = await runEnsemble({
4091
+ provider: evalProvider,
4092
+ system: JUDGE_SYSTEM_PROMPT,
4093
+ user: buildUserContent(plan, selected.text, selected.label),
4094
+ runs: runsPerEval,
4095
+ temperature,
4096
+ schema: verdictSchema,
4097
+ cache,
4098
+ cacheKey: key,
4099
+ label: "manni-tracevals"
4100
+ });
4101
+ const consensusBase = computeConsensus(runs);
4102
+ const zone = zoneFor(consensusBase, zones);
4103
+ const consensus = { ...consensusBase, zone };
4104
+ const judgeModel = evalProvider.modelName();
4105
+ const selfPreference = sessionModel !== void 0 && sessionModel === judgeModel ? { axis: "session", model: judgeModel } : plan.proposedBy?.includes(judgeModel) === true ? { axis: "criterion", model: judgeModel } : void 0;
4106
+ results.push({
4107
+ ...base,
4108
+ outcome: zone === "auto-pass" ? "pass" : zone === "auto-fail" ? "fail" : "needs-review",
4109
+ consensus,
4110
+ turns: cached ? 0 : runsPerEval,
4111
+ ...selfPreference ? { selfPreference } : {},
4112
+ durationMs: Date.now() - start
4113
+ });
4114
+ }
4115
+ return results;
4116
+ };
4117
+ }
4118
+ async function resolveOverride(plan, options) {
4119
+ const named = plan.provider !== void 0 ? `provider "${plan.provider}"` : `model "${plan.model ?? ""}"`;
4120
+ if (options.providerFor === void 0) {
4121
+ return {
4122
+ error: `eval names ${named}, but this run cannot construct providers by name`
4123
+ };
4124
+ }
4125
+ try {
4126
+ return {
4127
+ provider: await options.providerFor({
4128
+ ...plan.provider !== void 0 ? { provider: plan.provider } : {},
4129
+ ...plan.model !== void 0 ? { model: plan.model } : {},
4130
+ origin: `eval "${plan.evalName}" in ${plan.artifact.path}`
4131
+ })
4132
+ };
4133
+ } catch (err) {
4134
+ return {
4135
+ error: `could not construct ${named}${plan.provider === void 0 || plan.model === void 0 ? "" : ` at model "${plan.model}"`} for this eval: ${err instanceof Error ? err.message : String(err)}`
4136
+ };
4137
+ }
4138
+ }
4139
+
4140
+ // src/tracevals/commands/run.ts
4141
+ function asGrants(values) {
4142
+ const unknown = values.filter((v) => !isExecutionGrant(v));
4143
+ if (unknown.length > 0) throw new TracevalsError(unknownGrantsMessage(unknown));
4144
+ return values.filter(isExecutionGrant);
4145
+ }
4146
+ async function prepareRun(options) {
4147
+ const {
4148
+ config: loaded,
4149
+ dir: configDir,
4150
+ collections,
4151
+ path: configPath
4152
+ } = await discoverConfig(
4153
+ options.configDir ?? process.cwd(),
4154
+ {
4155
+ ...options.config === void 0 ? {} : { configPath: options.config },
4156
+ ...options.noConfig === void 0 ? {} : { noConfig: options.noConfig }
4157
+ }
4158
+ );
4159
+ const config = {
4160
+ ...loaded,
4161
+ failOnReview: options.failOnReview ?? loaded.failOnReview,
4162
+ execution: {
4163
+ allow: [
4164
+ ...grantsFor(loaded.execution.allow, {
4165
+ ...options.allowExecution === void 0 ? {} : { allowExecution: asGrants(options.allowExecution) }
4166
+ })
4167
+ ]
4168
+ },
4169
+ // A set-valued knob, so `--require` *adds* instead of replacing. A one-off
4170
+ // flag must not silently unregister the house graders a repo's config
4171
+ // names — every eval declaring one would flip to `unknown grader kind`,
4172
+ // which reads as a typo in an artifact nobody touched. Config entries load
4173
+ // first, so a deliberate `--require` still wins a colliding kind
4174
+ // (ADR 01017).
4175
+ plugins: [...loaded.plugins, ...options.require ?? []],
4176
+ reportUnusedArtifacts: options.reportUnusedArtifacts ?? loaded.reportUnusedArtifacts
4177
+ };
4178
+ const plugins = await loadGraderPlugins({
4179
+ plugins: config.plugins,
4180
+ configDir
4181
+ });
4182
+ const flags = {
4183
+ ...options.provider !== void 0 ? { provider: options.provider } : {},
4184
+ ...options.model !== void 0 ? { model: options.model } : {},
4185
+ ...options.local !== void 0 ? { local: options.local } : {}
4186
+ };
4187
+ const selection = selectProvider2(config, flags);
4188
+ assertProviderSelection(selection);
4189
+ let judge = options.judge;
4190
+ if (judge === void 0 && options.deterministicOnly !== true) {
4191
+ const provider = constructProvider(
4192
+ config,
4193
+ await resolveProviderIdentity(config, flags)
4194
+ );
4195
+ const overridden = /* @__PURE__ */ new Map();
4196
+ const keyOf = (s) => `${s.provider}:${s.model ?? ""}`;
4197
+ const defaultKey = keyOf(selection);
4198
+ judge = makeTraceJudge({
4199
+ provider,
4200
+ // An eval may name its own provider or model. It is selected by the same
4201
+ // rule the run's own was — flag, then eval, then `tracevals.provider`,
4202
+ // then the family's `providers:` — so an eval cannot quietly outrank a
4203
+ // flag someone just typed.
4204
+ providerFor: async (ev) => {
4205
+ const chosen = selectProvider2(config, flags, ev);
4206
+ announceSelection(chosen);
4207
+ const key = keyOf(chosen);
4208
+ if (key === defaultKey) return provider;
4209
+ let built = overridden.get(key);
4210
+ if (built === void 0) {
4211
+ built = (async () => {
4212
+ assertProviderSelection(chosen);
4213
+ return constructProvider(
4214
+ config,
4215
+ await resolveProviderIdentity(config, flags, ev)
4216
+ );
4217
+ })();
4218
+ overridden.set(key, built);
4219
+ }
4220
+ return built;
4221
+ },
4222
+ runs: options.runs ?? config.judge.ensembleRuns,
4223
+ temperature: config.judge.temperature,
4224
+ zones: config.judge.zones,
4225
+ cacheDir: resolve5(configDir, config.judge.cacheDir),
4226
+ ...options.noCache !== void 0 ? { noCache: options.noCache } : {},
4227
+ maxTurns: options.maxTurns ?? config.judge.maxTurns
4228
+ });
4229
+ }
4230
+ const external = await loadExternalEvals({
4231
+ collections,
4232
+ configDir,
4233
+ ...configPath === void 0 ? {} : { configPath },
4234
+ ...options.offline === void 0 ? {} : { offline: options.offline }
4235
+ });
4236
+ return {
4237
+ config,
4238
+ configDir,
4239
+ warnings: plugins.warnings,
4240
+ ...judge !== void 0 ? { judge } : {},
4241
+ ...external === null ? {} : {
4242
+ metadataFor: (artifact) => external.forArtifact(artifact)
4243
+ }
4244
+ };
4245
+ }
4246
+ async function runOne(options, context) {
4247
+ const { config } = context;
4248
+ const report = await runEvals({
4249
+ tracePath: options.tracePath,
4250
+ ...options.project !== void 0 ? { projectDir: options.project } : {},
4251
+ ...options.env !== void 0 ? { env: options.env } : {},
4252
+ config,
4253
+ ...context.judge !== void 0 ? { judge: context.judge } : {},
4254
+ ...options.deterministicOnly !== void 0 ? { deterministicOnly: options.deterministicOnly } : {},
4255
+ ...options.manifest !== void 0 ? { manifest: options.manifest } : {},
4256
+ ...context.warnings.length > 0 ? { warnings: context.warnings } : {},
4257
+ ...context.metadataFor !== void 0 ? { metadataFor: context.metadataFor } : {}
4258
+ });
4259
+ let comparison;
4260
+ if (options.history) {
4261
+ const historyFile = resolve5(context.configDir, config.history.file);
4262
+ comparison = compareToLast(await loadHistory(historyFile), report) ?? void 0;
4263
+ await appendHistory(historyFile, report);
4264
+ }
4265
+ return { report, ...comparison ? { comparison } : {} };
4266
+ }
4267
+ async function runRun(options) {
4268
+ const context = await prepareRun(options);
4269
+ const { report, comparison } = await runOne(options, context);
4270
+ const format = options.format ?? "pretty";
4271
+ let rendered = render(report, format, { color: options.color === true });
4272
+ if (comparison && (format === "pretty" || format === "markdown" || format === "github")) {
4273
+ rendered += `
4274
+
4275
+ ${renderComparison(comparison)}`;
4276
+ }
4277
+ if (options.output) {
4278
+ await writeFile(options.output, rendered, "utf-8");
4279
+ }
4280
+ return { report, rendered, ...comparison ? { comparison } : {} };
4281
+ }
4282
+ function renderComparison(comparison) {
4283
+ const lines = [`History vs ${comparison.previousTimestamp}:`];
4284
+ for (const r of comparison.regressions) {
4285
+ lines.push(` regression: ${r.evalName} (${r.outcome})`);
4286
+ }
4287
+ for (const i of comparison.improvements) {
4288
+ lines.push(` improvement: ${i.evalName} (${i.outcome})`);
4289
+ }
4290
+ if (comparison.added.length) lines.push(` added: ${comparison.added.join(", ")}`);
4291
+ if (comparison.removed.length) {
4292
+ lines.push(` removed: ${comparison.removed.join(", ")}`);
4293
+ }
4294
+ if (lines.length === 1) lines.push(" no changes");
4295
+ return lines.join("\n");
4296
+ }
4297
+
4298
+ // src/tracevals/commands/batch.ts
4299
+ function parseNewerThan(text) {
4300
+ return parseDuration(text, (message) => new TracevalsError(message));
4301
+ }
4302
+ function keepNewerThan(listings, newerThan) {
4303
+ if (newerThan === void 0) return listings;
4304
+ const floor = Date.now() - parseNewerThan(newerThan);
4305
+ return listings.filter((l) => l.mtimeMs >= floor);
4306
+ }
4307
+ function usesDiscovery(options) {
4308
+ return options.allProjects === true || options.newerThan !== void 0 || options.limit !== void 0;
4309
+ }
4310
+ async function resolveBatchTraces(options) {
4311
+ const named = options.traces ?? [];
4312
+ if (named.length > 0 && usesDiscovery(options)) {
4313
+ throw new TracevalsError(
4314
+ "name traces or select them with --all-projects/--newer-than/--limit, not both"
4315
+ );
4316
+ }
4317
+ if (named.length > 0) return named;
4318
+ const listings = await discoverTraces({
4319
+ ...options.allProjects !== void 0 ? { allProjects: options.allProjects } : {},
4320
+ ...options.project !== void 0 ? { project: options.project } : {},
4321
+ ...options.limit !== void 0 ? { limit: options.limit } : {},
4322
+ ...options.env !== void 0 ? { env: options.env } : {}
4323
+ });
4324
+ const kept = keepNewerThan(listings, options.newerThan);
4325
+ if (kept.length === 0) {
4326
+ const how = [
4327
+ options.allProjects === true ? "--all-projects" : `project ${options.project ?? process.cwd()}`,
4328
+ options.newerThan !== void 0 ? `--newer-than ${options.newerThan}` : void 0,
4329
+ options.limit !== void 0 ? `--limit ${options.limit}` : void 0
4330
+ ].filter((s) => s !== void 0).join(", ");
4331
+ throw new TracevalsError(
4332
+ `no traces matched (${how}); run \`manni tracevals list\` to see what the session store holds`
4333
+ );
4334
+ }
4335
+ return kept.map((l) => l.file);
4336
+ }
4337
+ async function runBatch(options) {
4338
+ const start = Date.now();
4339
+ const files = await resolveBatchTraces(options);
4340
+ const context = await prepareRun(options);
4341
+ const outcomes = [];
4342
+ const reports = [];
4343
+ const comparisons = [];
4344
+ for (const file of files) {
4345
+ const traceStart = Date.now();
4346
+ try {
4347
+ const { report: report2, comparison } = await runOne(
4348
+ { ...options, tracePath: file },
4349
+ context
4350
+ );
4351
+ outcomes.push({ file, report: report2 });
4352
+ reports.push(report2);
4353
+ if (comparison) comparisons.push(comparison);
4354
+ } catch (err) {
4355
+ outcomes.push({
4356
+ file,
4357
+ error: err instanceof Error ? err.message : String(err),
4358
+ durationMs: Date.now() - traceStart
4359
+ });
4360
+ }
4361
+ }
4362
+ const report = aggregate(outcomes, {
4363
+ warnings: context.warnings,
4364
+ durationMs: Date.now() - start
4365
+ });
4366
+ const rendered = renderBatch(report, options.format ?? "pretty", reports, {
4367
+ color: options.color === true
4368
+ });
4369
+ if (options.output) {
4370
+ await writeFile2(options.output, rendered, "utf-8");
4371
+ }
4372
+ return { report, rendered, reports, outcomes, comparisons };
4373
+ }
4374
+
4375
+ export {
4376
+ outputTail,
4377
+ realExec,
4378
+ parseDuration,
4379
+ TURN_BUDGET_SKIP,
4380
+ turnBudgetSkipReason,
4381
+ isTurnBudgetSkip,
4382
+ URI_BASE_ID,
4383
+ fileUri,
4384
+ normalizeRoot,
4385
+ artifactLocation,
4386
+ aggregate,
4387
+ renderBatchPretty,
4388
+ renderBatchMarkdown,
4389
+ renderCalibrationPretty,
4390
+ renderCalibrationMarkdown,
4391
+ REPORT_FORMATS,
4392
+ CALIBRATE_FORMATS,
4393
+ SUMMARY_FORMATS,
4394
+ parseFormat,
4395
+ render,
4396
+ renderBatch,
4397
+ renderCalibration,
4398
+ detectFormat,
4399
+ detectContentFormat,
4400
+ parseTraceFile,
4401
+ parseTraceContent,
4402
+ IMPLICIT_EVAL_NAME,
4403
+ planEvals,
4404
+ PROMPT_VERSION,
4405
+ JUDGE_SYSTEM_PROMPT,
4406
+ buildUserContent,
4407
+ globToRegExp,
4408
+ matchesGlob,
4409
+ WHEN_CONDITIONS,
4410
+ validateWhen,
4411
+ evaluateWhen,
4412
+ windowFor,
4413
+ skippedWindow,
4414
+ BUILTIN_GRADER_KINDS,
4415
+ registerGrader,
4416
+ graderFor,
4417
+ listGraderKinds,
4418
+ renderTrace,
4419
+ runEvals,
4420
+ METADATA_KEY,
4421
+ urlManifestRefusal,
4422
+ loadExternalEvals,
4423
+ loadGraderPlugins,
4424
+ entryFor,
4425
+ appendHistory,
4426
+ loadHistory,
4427
+ compareToLast,
4428
+ selectProvider2 as selectProvider,
4429
+ announceSelection,
4430
+ assertProviderSelection,
4431
+ providerSpecFor2 as providerSpecFor,
4432
+ resolveProviderIdentity,
4433
+ constructProvider,
4434
+ makeJudgeProvider,
4435
+ sha256,
4436
+ VerdictCache,
4437
+ cacheKey,
4438
+ makeTraceJudge,
4439
+ prepareRun,
4440
+ runOne,
4441
+ runRun,
4442
+ parseNewerThan,
4443
+ keepNewerThan,
4444
+ resolveBatchTraces,
4445
+ runBatch
4446
+ };
4447
+ //# sourceMappingURL=chunk-CI2GOU3A.js.map