@tangle-network/agent-eval 0.86.0 → 0.90.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 (140) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/adapters/http.d.ts +3 -3
  3. package/dist/adapters/langchain.d.ts +3 -3
  4. package/dist/adapters/otel.d.ts +6 -6
  5. package/dist/adversarial-DIVcDoI_.d.ts +88 -0
  6. package/dist/analyst/index.d.ts +11 -10
  7. package/dist/analyst/index.js +13 -8
  8. package/dist/analyst/index.js.map +1 -1
  9. package/dist/analyze-runs-DwCEkpO_.d.ts +81 -0
  10. package/dist/belief-state/index.d.ts +4 -4
  11. package/dist/belief-state/index.js +1 -1
  12. package/dist/benchmarks/index.d.ts +3 -3
  13. package/dist/campaign/index.d.ts +165 -18
  14. package/dist/campaign/index.js +289 -14
  15. package/dist/campaign/index.js.map +1 -1
  16. package/dist/chunk-45EEMHTC.js +35 -0
  17. package/dist/chunk-45EEMHTC.js.map +1 -0
  18. package/dist/{chunk-FZWAFVAA.js → chunk-4FBZZIYD.js} +2 -2
  19. package/dist/{chunk-YV7J7X5N.js → chunk-5HRORJQY.js} +22 -12
  20. package/dist/chunk-5HRORJQY.js.map +1 -0
  21. package/dist/{chunk-OTYQPHPL.js → chunk-6SOJM3VR.js} +5 -5
  22. package/dist/chunk-BOD4O7OF.js +40 -0
  23. package/dist/chunk-BOD4O7OF.js.map +1 -0
  24. package/dist/{chunk-Z7VFTS2J.js → chunk-CY6U5S3X.js} +2 -2
  25. package/dist/{chunk-VIDQF3F5.js → chunk-D3V5B42D.js} +5 -34
  26. package/dist/chunk-D3V5B42D.js.map +1 -0
  27. package/dist/{chunk-YGYXHNAQ.js → chunk-FIUKOSWI.js} +21 -8
  28. package/dist/chunk-FIUKOSWI.js.map +1 -0
  29. package/dist/{chunk-WJL2NJXN.js → chunk-GSH6QNNS.js} +2 -2
  30. package/dist/{chunk-RBNA5AZT.js → chunk-L3JOU6XM.js} +2 -2
  31. package/dist/{chunk-IDVBLYCY.js → chunk-LMZQ2Z4U.js} +56 -2
  32. package/dist/{chunk-IDVBLYCY.js.map → chunk-LMZQ2Z4U.js.map} +1 -1
  33. package/dist/{chunk-VUINJM5M.js → chunk-QAY5UIJO.js} +2 -193
  34. package/dist/chunk-QAY5UIJO.js.map +1 -0
  35. package/dist/{chunk-P2J6SOXT.js → chunk-QG2OVF2D.js} +5 -3
  36. package/dist/{chunk-P2J6SOXT.js.map → chunk-QG2OVF2D.js.map} +1 -1
  37. package/dist/chunk-REVYNR6C.js +100 -0
  38. package/dist/chunk-REVYNR6C.js.map +1 -0
  39. package/dist/chunk-STGVSCDH.js +202 -0
  40. package/dist/chunk-STGVSCDH.js.map +1 -0
  41. package/dist/{chunk-ZZ2HOPME.js → chunk-TWS7AZEY.js} +2 -2
  42. package/dist/chunk-UHMJT4T7.js +200 -0
  43. package/dist/chunk-UHMJT4T7.js.map +1 -0
  44. package/dist/chunk-UMMZHCPB.js +190 -0
  45. package/dist/chunk-UMMZHCPB.js.map +1 -0
  46. package/dist/chunk-VZSRQ272.js +149 -0
  47. package/dist/chunk-VZSRQ272.js.map +1 -0
  48. package/dist/{chunk-L5G7OUKD.js → chunk-XY4DDNEG.js} +8 -190
  49. package/dist/chunk-XY4DDNEG.js.map +1 -0
  50. package/dist/chunk-Y47J2LJ3.js +859 -0
  51. package/dist/chunk-Y47J2LJ3.js.map +1 -0
  52. package/dist/{chunk-BABOZOSN.js → chunk-ZFIBGEOL.js} +3 -3
  53. package/dist/chunk-ZFIBGEOL.js.map +1 -0
  54. package/dist/{code-agent-session-BRXmavYv.d.ts → code-agent-session-BO8nCnv3.d.ts} +1 -1
  55. package/dist/contract/index.d.ts +24 -95
  56. package/dist/contract/index.js +25 -764
  57. package/dist/contract/index.js.map +1 -1
  58. package/dist/{control-GeE8OhpN.d.ts → control-_Qb7skHX.d.ts} +2 -2
  59. package/dist/control.d.ts +5 -5
  60. package/dist/corpus-BoR-041R.d.ts +560 -0
  61. package/dist/cost-ledger-DuSqlw5B.d.ts +113 -0
  62. package/dist/counterfactual-Dwibr5IW.d.ts +85 -0
  63. package/dist/{dataset-B2kL-fSM.d.ts → dataset-BbGkaN2I.d.ts} +1 -1
  64. package/dist/{registry-DrEQ3Luj.d.ts → default-registry-zoGHUQEH.d.ts} +29 -2
  65. package/dist/diagnose.d.ts +251 -0
  66. package/dist/diagnose.js +381 -0
  67. package/dist/diagnose.js.map +1 -0
  68. package/dist/{errors-Dwqw-T_m.d.ts → errors-CzMUYo7b.d.ts} +1 -1
  69. package/dist/{feedback-trajectory-B3rErRsh.d.ts → feedback-trajectory-D9OVLrg9.d.ts} +1 -1
  70. package/dist/fuzz.d.ts +484 -0
  71. package/dist/fuzz.js +613 -0
  72. package/dist/fuzz.js.map +1 -0
  73. package/dist/governance/index.d.ts +4 -4
  74. package/dist/hosted/index.d.ts +6 -6
  75. package/dist/{index-DE3RXAXD.d.ts → index-Bx3gZ8xl.d.ts} +1 -1
  76. package/dist/index.d.ts +718 -455
  77. package/dist/index.js +1604 -793
  78. package/dist/index.js.map +1 -1
  79. package/dist/{insight-report-3ADTfClO.d.ts → insight-report-BBwvOh6x.d.ts} +2 -2
  80. package/dist/{integrity-CJzrpUua.d.ts → integrity-VJ9A7aST.d.ts} +1 -1
  81. package/dist/{judge-calibration-DilmB3Ml.d.ts → judge-calibration-0p2QcWNE.d.ts} +1 -1
  82. package/dist/{kind-factory-CVecZZG_.d.ts → kind-factory-5b7xXXOr.d.ts} +2 -2
  83. package/dist/{llm-client-CuUg2Mn3.d.ts → llm-client-BeEcAokY.d.ts} +1 -1
  84. package/dist/matrix/index.d.ts +2 -2
  85. package/dist/meta-eval/index.d.ts +177 -3
  86. package/dist/meta-eval/index.js +260 -1
  87. package/dist/meta-eval/index.js.map +1 -1
  88. package/dist/{multi-layer-verifier-DlWCXuxL.d.ts → multi-layer-verifier-DUZXrPDA.d.ts} +7 -1
  89. package/dist/multishot/index.d.ts +25 -11
  90. package/dist/multishot/index.js +36 -7
  91. package/dist/multishot/index.js.map +1 -1
  92. package/dist/openapi.json +1 -1
  93. package/dist/perf/index.d.ts +123 -0
  94. package/dist/perf/index.js +18 -0
  95. package/dist/pipelines/index.js +2 -2
  96. package/dist/{agent-profile-D0PBIWlV.d.ts → pre-registration-DELOEJ8v.d.ts} +144 -4
  97. package/dist/{provenance-DPpNIOJD.d.ts → provenance-LnqRT0sS.d.ts} +5 -5
  98. package/dist/{red-team-DW9Ca_tj.d.ts → red-team-BXHil6c8.d.ts} +1 -1
  99. package/dist/{release-report-hlNtD12q.d.ts → release-report-euXIV_Sk.d.ts} +3 -3
  100. package/dist/reporting.d.ts +8 -8
  101. package/dist/reporting.js +3 -3
  102. package/dist/{researcher-BLPHBbNV.d.ts → researcher-DE6Gpnb4.d.ts} +4 -4
  103. package/dist/rl.d.ts +194 -656
  104. package/dist/rl.js +231 -149
  105. package/dist/rl.js.map +1 -1
  106. package/dist/{rubric-predictive-validity-CnEl9Jc8.d.ts → rubric-predictive-validity-Cy_W-hWZ.d.ts} +1 -1
  107. package/dist/{run-campaign-4Y5V5CN3.js → run-campaign-RDGAM5KJ.js} +3 -3
  108. package/dist/run-campaign-RDGAM5KJ.js.map +1 -0
  109. package/dist/{run-improvement-loop-CNqQckTj.d.ts → run-improvement-loop-5z_l5zDz.d.ts} +2 -2
  110. package/dist/{run-record-De9VarXR.d.ts → run-record-e7vj1uZQ.d.ts} +1 -1
  111. package/dist/{runtime-trajectory-BLRiaifm.d.ts → runtime-trajectory-BDgfGZSr.d.ts} +1 -1
  112. package/dist/{semantic-concept-judge-DIEgr_6v.d.ts → semantic-concept-judge-Dn8Z6KEG.d.ts} +5 -31
  113. package/dist/series-convergence-D5OWMBg6.d.ts +33 -0
  114. package/dist/{statistics-CnC1FMbx.d.ts → statistics-C7PozGrZ.d.ts} +71 -2
  115. package/dist/{summary-report-Db0dDSWP.d.ts → summary-report-DGmUucwQ.d.ts} +1 -1
  116. package/dist/traces.d.ts +3 -3
  117. package/dist/traces.js +8 -6
  118. package/dist/{types-Cu3u_x59.d.ts → types-2VVIL04s.d.ts} +2 -2
  119. package/dist/{types-D7lLRYe9.d.ts → types-BU-7W85F.d.ts} +21 -1
  120. package/dist/{types-CqPax19X.d.ts → types-mn5Aqk7x.d.ts} +1 -1
  121. package/dist/{verdict-CeEgtjyI.d.ts → verdict-C9MlYujm.d.ts} +3 -0
  122. package/dist/wire/index.d.ts +3 -3
  123. package/dist/workflow/index.d.ts +12 -11
  124. package/dist/workflow/index.js +1 -1
  125. package/package.json +16 -1
  126. package/dist/chunk-BABOZOSN.js.map +0 -1
  127. package/dist/chunk-L5G7OUKD.js.map +0 -1
  128. package/dist/chunk-SHTXZ4O2.js +0 -113
  129. package/dist/chunk-SHTXZ4O2.js.map +0 -1
  130. package/dist/chunk-VIDQF3F5.js.map +0 -1
  131. package/dist/chunk-VUINJM5M.js.map +0 -1
  132. package/dist/chunk-YGYXHNAQ.js.map +0 -1
  133. package/dist/chunk-YV7J7X5N.js.map +0 -1
  134. /package/dist/{chunk-FZWAFVAA.js.map → chunk-4FBZZIYD.js.map} +0 -0
  135. /package/dist/{chunk-OTYQPHPL.js.map → chunk-6SOJM3VR.js.map} +0 -0
  136. /package/dist/{chunk-Z7VFTS2J.js.map → chunk-CY6U5S3X.js.map} +0 -0
  137. /package/dist/{chunk-WJL2NJXN.js.map → chunk-GSH6QNNS.js.map} +0 -0
  138. /package/dist/{chunk-RBNA5AZT.js.map → chunk-L3JOU6XM.js.map} +0 -0
  139. /package/dist/{chunk-ZZ2HOPME.js.map → chunk-TWS7AZEY.js.map} +0 -0
  140. /package/dist/{run-campaign-4Y5V5CN3.js.map → perf/index.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,22 +1,47 @@
1
- import {
2
- agentProfileHash,
3
- createLlmCorrectnessChecker,
4
- createTokenRecallChecker,
5
- extractProducedState,
6
- parseCorrectnessResponse,
7
- verifyCompletion
8
- } from "./chunk-YGYXHNAQ.js";
9
1
  import {
10
2
  parseRuntimeTrajectoryHookEvent,
11
3
  projectRuntimeTrajectoryEvidence
12
4
  } from "./chunk-T4SQEITX.js";
13
5
  import {
14
6
  HoldoutAuditor,
7
+ analyzeRuns,
15
8
  canaryLeakView,
16
9
  checkBehavioralCanary,
17
10
  checkCanaries,
18
11
  runBehavioralCanaries
19
- } from "./chunk-SHTXZ4O2.js";
12
+ } from "./chunk-Y47J2LJ3.js";
13
+ import {
14
+ classifyEuAiRisk,
15
+ euAiActReport,
16
+ nistAiRmfReport,
17
+ renderMarkdown,
18
+ soc2Report,
19
+ summarize
20
+ } from "./chunk-KKHDIONI.js";
21
+ import {
22
+ acquisitionPlansForKnowledgeGaps,
23
+ blockingKnowledgeEval,
24
+ knowledgeReadinessTracePayload,
25
+ scoreKnowledgeReadiness,
26
+ userQuestionsForKnowledgeGaps
27
+ } from "./chunk-3CKU6VGU.js";
28
+ import {
29
+ assertRecordIntegrity,
30
+ checkRecordIntegrity,
31
+ expandMatrix,
32
+ gatePerf,
33
+ scenarioKey,
34
+ summarizeRecords
35
+ } from "./chunk-STGVSCDH.js";
36
+ import {
37
+ agentProfileHash,
38
+ completionVerdict,
39
+ createLlmCorrectnessChecker,
40
+ createTokenRecallChecker,
41
+ extractProducedState,
42
+ parseCorrectnessResponse,
43
+ verifyCompletion
44
+ } from "./chunk-FIUKOSWI.js";
20
45
  import {
21
46
  DEFAULT_MUTATION_PRIMITIVES,
22
47
  DEFAULT_RED_TEAM_CORPUS,
@@ -40,12 +65,12 @@ import {
40
65
  scoreRedTeamOutput,
41
66
  surfaceContentHash,
42
67
  toolNamesForRun
43
- } from "./chunk-BABOZOSN.js";
68
+ } from "./chunk-ZFIBGEOL.js";
44
69
  import {
45
70
  BackendIntegrityError,
46
71
  assertRealBackend,
47
72
  summarizeBackendIntegrity
48
- } from "./chunk-ZZ2HOPME.js";
73
+ } from "./chunk-TWS7AZEY.js";
49
74
  import {
50
75
  MODEL_PRICING,
51
76
  MetricsCollector,
@@ -67,14 +92,14 @@ import {
67
92
  computeToolUseMetrics,
68
93
  iqr,
69
94
  welchsTTest
70
- } from "./chunk-RBNA5AZT.js";
95
+ } from "./chunk-L3JOU6XM.js";
96
+ import {
97
+ analyzeSeries
98
+ } from "./chunk-BOD4O7OF.js";
71
99
  import {
72
100
  exportTrainingData,
73
101
  toNdjson
74
102
  } from "./chunk-KMPRBJK4.js";
75
- import {
76
- buildTrajectory
77
- } from "./chunk-RZTMDUO7.js";
78
103
  import {
79
104
  DockerSandboxDriver,
80
105
  SandboxHarness,
@@ -85,21 +110,6 @@ import {
85
110
  runTestGradedScenario,
86
111
  vitestTestParser
87
112
  } from "./chunk-T375SUOZ.js";
88
- import {
89
- classifyEuAiRisk,
90
- euAiActReport,
91
- nistAiRmfReport,
92
- renderMarkdown,
93
- soc2Report,
94
- summarize
95
- } from "./chunk-KKHDIONI.js";
96
- import {
97
- acquisitionPlansForKnowledgeGaps,
98
- blockingKnowledgeEval,
99
- knowledgeReadinessTracePayload,
100
- scoreKnowledgeReadiness,
101
- userQuestionsForKnowledgeGaps
102
- } from "./chunk-3CKU6VGU.js";
103
113
  import {
104
114
  DEFAULT_COMPLEXITY_WEIGHTS,
105
115
  DEFAULT_RUN_SCORE_WEIGHTS,
@@ -111,9 +121,7 @@ import {
111
121
  SKILL_USAGE_ANALYST,
112
122
  SkillUsageAnalyst,
113
123
  aggregateRunScore,
114
- buildDefaultAnalystRegistry,
115
124
  clamp01,
116
- computeTraceMetrics,
117
125
  createAnalystAi,
118
126
  createChatClient,
119
127
  createSemanticConceptJudge,
@@ -121,7 +129,11 @@ import {
121
129
  diffFindings,
122
130
  resetLockedAppendersForTesting,
123
131
  runSemanticConceptJudge
124
- } from "./chunk-L5G7OUKD.js";
132
+ } from "./chunk-XY4DDNEG.js";
133
+ import {
134
+ buildDefaultAnalystRegistry,
135
+ computeTraceMetrics
136
+ } from "./chunk-UMMZHCPB.js";
125
137
  import {
126
138
  AnalystRegistry,
127
139
  DEFAULT_TRACE_ANALYST_KINDS,
@@ -129,11 +141,9 @@ import {
129
141
  IMPROVEMENT_KIND_SPEC,
130
142
  KNOWLEDGE_GAP_KIND_SPEC,
131
143
  KNOWLEDGE_POISONING_KIND_SPEC,
132
- computeFindingId,
133
144
  createTraceAnalystKind,
134
- makeFinding,
135
145
  renderPriorFindings
136
- } from "./chunk-VIDQF3F5.js";
146
+ } from "./chunk-D3V5B42D.js";
137
147
  import {
138
148
  controlFailureClassFromVerification,
139
149
  controlRunToRunRecord,
@@ -159,10 +169,10 @@ import {
159
169
  evaluateReleaseConfidence,
160
170
  judgeReplayGate,
161
171
  renderReleaseReport
162
- } from "./chunk-FZWAFVAA.js";
172
+ } from "./chunk-4FBZZIYD.js";
163
173
  import {
164
174
  runEvalCampaign
165
- } from "./chunk-WJL2NJXN.js";
175
+ } from "./chunk-GSH6QNNS.js";
166
176
  import {
167
177
  LlmCallError,
168
178
  LlmClient,
@@ -185,7 +195,18 @@ import {
185
195
  paretoChart,
186
196
  researchReport,
187
197
  summaryTable
188
- } from "./chunk-Z7VFTS2J.js";
198
+ } from "./chunk-CY6U5S3X.js";
199
+ import {
200
+ attributeCounterfactuals,
201
+ runCounterfactual
202
+ } from "./chunk-REVYNR6C.js";
203
+ import {
204
+ buildTrajectory
205
+ } from "./chunk-RZTMDUO7.js";
206
+ import {
207
+ computeFindingId,
208
+ makeFinding
209
+ } from "./chunk-45EEMHTC.js";
189
210
  import {
190
211
  benjaminiHochberg,
191
212
  bonferroni,
@@ -197,9 +218,11 @@ import {
197
218
  continuousAgreement,
198
219
  corpusInterRaterAgreement,
199
220
  corpusInterRaterAgreementFromJudgeScores,
221
+ eProcess,
200
222
  interRaterReliability,
201
223
  interpretCliffs,
202
224
  mannWhitneyU,
225
+ mulberry32,
203
226
  normalizeScores,
204
227
  pairedBootstrap,
205
228
  pairedMde,
@@ -212,7 +235,7 @@ import {
212
235
  weightedComposite,
213
236
  weightedMean,
214
237
  wilcoxonSignedRank
215
- } from "./chunk-IDVBLYCY.js";
238
+ } from "./chunk-LMZQ2Z4U.js";
216
239
  import {
217
240
  FileSystemTraceStore,
218
241
  InMemoryTraceStore,
@@ -243,7 +266,7 @@ import {
243
266
  scoreTraceInsightReadiness,
244
267
  tokenizeDomainWords,
245
268
  traceAnalystOnRunComplete
246
- } from "./chunk-P2J6SOXT.js";
269
+ } from "./chunk-QG2OVF2D.js";
247
270
  import {
248
271
  DEFAULT_REDACTION_RULES,
249
272
  REDACTION_VERSION,
@@ -270,16 +293,18 @@ import {
270
293
  isToolSpan
271
294
  } from "./chunk-5BKGXME7.js";
272
295
  import {
273
- DEFAULT_TRACE_ANALYST_BUDGETS,
274
- OtlpFileTraceStore,
275
- SpanNotFoundError,
276
296
  TRACE_ANALYST_ACTOR_DESCRIPTION,
277
297
  TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION,
278
298
  TRACE_ANALYST_SUBAGENT_DESCRIPTION,
299
+ analyzeTraces
300
+ } from "./chunk-UHMJT4T7.js";
301
+ import {
302
+ DEFAULT_TRACE_ANALYST_BUDGETS,
303
+ OtlpFileTraceStore,
304
+ SpanNotFoundError,
279
305
  TRACE_ANALYST_TRUNCATION_MARKER_PREFIX,
280
306
  TraceFileMissingError,
281
307
  TraceNotFoundError,
282
- analyzeTraces,
283
308
  asNumber,
284
309
  asString,
285
310
  buildTraceAnalystTools,
@@ -291,7 +316,7 @@ import {
291
316
  readOtlpStatus,
292
317
  stringField,
293
318
  traceAnalystFunctionGroup
294
- } from "./chunk-VUINJM5M.js";
319
+ } from "./chunk-QAY5UIJO.js";
295
320
  import {
296
321
  RunIntegrityError,
297
322
  assertRunCaptured,
@@ -646,134 +671,339 @@ function renderCommitMessage(input) {
646
671
  return lines.join("\n").trim();
647
672
  }
648
673
 
649
- // src/executor.ts
650
- async function executeScenario(tc, scenario, config) {
651
- const startTime = Date.now();
652
- const model = config.model ?? "gpt-4o";
653
- const systemPrompt = [config.systemPrompt, scenario.systemPromptAppend ?? ""].filter(Boolean).join("\n\n");
654
- const messages = [{ role: "system", content: systemPrompt }];
655
- const turns = [];
656
- const allCodeBlocks = [];
657
- const allBlocks = [];
658
- const allToolCalls = [];
659
- const blockRe = config.blockPattern ?? /:::(\w+)\s*\n([\s\S]*?)\n\s*:::/g;
660
- for (let i = 0; i < scenario.turns.length; i++) {
661
- const turn = scenario.turns[i];
662
- const turnStart = Date.now();
663
- messages.push({ role: "user", content: turn.user });
674
+ // src/judges.ts
675
+ var JudgeParseError = class extends JudgeError {
676
+ /** Name of the judge whose response failed to parse. */
677
+ judgeName;
678
+ /** The raw (truncated) model response that failed to parse. */
679
+ raw;
680
+ constructor(judgeName, raw, options) {
681
+ super(`judge '${judgeName}' returned an unparseable response: ${raw.slice(0, 200)}`, options);
682
+ this.judgeName = judgeName;
683
+ this.raw = raw;
684
+ }
685
+ };
686
+ function createDomainExpertJudge(domain) {
687
+ return async (tc, { scenario, turns }) => {
688
+ const conversation = turns.map(
689
+ (t, i) => `Turn ${i + 1}:
690
+ User: ${t.userMessage}
691
+ Agent: ${t.agentResponse.slice(0, 2e3)}`
692
+ ).join("\n\n---\n\n");
664
693
  const resp = await tc.chat({
665
- model,
666
- messages,
667
- temperature: 0.4,
668
- maxTokens: 3e3
669
- });
670
- const content = resp.choices?.[0]?.message?.content ?? "";
671
- messages.push({ role: "assistant", content });
672
- const codeRe = /```(\w+)?\n([\s\S]*?)```/g;
673
- let codeMatch = codeRe.exec(content);
674
- while (codeMatch !== null) {
675
- allCodeBlocks.push({ language: codeMatch[1] ?? "text", code: codeMatch[2] ?? "" });
676
- codeMatch = codeRe.exec(content);
677
- }
678
- const turnBlocks = [];
679
- const blockReLocal = new RegExp(blockRe.source, blockRe.flags);
680
- let blockMatch = blockReLocal.exec(content);
681
- while (blockMatch !== null) {
682
- const fields = {};
683
- for (const line of (blockMatch[2] ?? "").split("\n")) {
684
- const idx = line.indexOf(":");
685
- if (idx > 0) fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
686
- }
687
- const blockType = blockMatch[1] ?? "";
688
- allBlocks.push({ type: blockType, fields });
689
- turnBlocks.push({ type: blockType, title: fields.title ?? "" });
690
- blockMatch = blockReLocal.exec(content);
691
- }
692
- let hasToolCall = false;
693
- if (config.toolCallPatterns) {
694
- for (const pattern of config.toolCallPatterns) {
695
- const re = new RegExp(pattern.source, pattern.flags);
696
- let toolMatch = re.exec(content);
697
- while (toolMatch !== null) {
698
- allToolCalls.push(toolMatch[0]);
699
- hasToolCall = true;
700
- toolMatch = re.exec(content);
694
+ model: "gpt-4o",
695
+ messages: [
696
+ {
697
+ role: "system",
698
+ content: `You are a senior ${domain} professional with 20+ years of experience. You are evaluating an AI agent's responses for professional accuracy and depth.
699
+
700
+ Score STRICTLY. A 5 means "a junior professional could do this." An 8 means "solid mid-career work." A 10 means "I would hire this agent."
701
+
702
+ Evaluate:
703
+ 1. **domain_accuracy** (0-10): Are the technical terms correct? Are the recommendations what you'd actually do? Would this advice cause problems if followed?
704
+ 2. **professional_depth** (0-10): Does it go beyond surface-level? Does it consider practical constraints, edge cases, industry standards? Or is it generic textbook advice?
705
+
706
+ Respond with JSON only: [{"dimension":"domain_accuracy","score":N,"reasoning":"...","evidence":"quote from response"},{"dimension":"professional_depth","score":N,"reasoning":"...","evidence":"quote"}]`
707
+ },
708
+ {
709
+ role: "user",
710
+ content: `Persona: ${scenario.persona} (${scenario.label})
711
+ Scenario: ${scenario.thesis}
712
+
713
+ ${conversation}`
701
714
  }
702
- }
703
- }
704
- turns.push({
705
- turnIndex: i,
706
- userMessage: turn.user,
707
- agentResponse: content,
708
- durationMs: Date.now() - turnStart,
709
- blocksExtracted: turnBlocks,
710
- containsCode: allCodeBlocks.length > 0,
711
- containsToolCall: hasToolCall
715
+ ],
716
+ temperature: 0.1,
717
+ maxTokens: 800
712
718
  });
713
- }
714
- const artifacts = {
715
- vaultFiles: [],
716
- blocksExtracted: allBlocks,
717
- codeBlocks: allCodeBlocks,
718
- toolCalls: allToolCalls
719
+ return parseJudgeResponse("domain_expert", resp);
719
720
  };
720
- const artifactResults = scenario.artifactChecks.map((check2) => {
721
- if (config.artifactChecker) {
722
- const custom = config.artifactChecker(check2, artifacts);
723
- if (custom) return { check: check2, ...custom };
724
- }
725
- switch (check2.type) {
726
- case "block_extracted": {
727
- const count = allBlocks.filter((b) => b.type === check2.target).length;
728
- return {
729
- check: check2,
730
- passed: count >= (check2.minCount ?? 1),
731
- detail: `Found ${count} ${check2.target} blocks (need ${check2.minCount ?? 1})`
732
- };
721
+ }
722
+ var codeExecutionJudge = async (tc, { scenario, artifacts }) => {
723
+ const codeBlocks = artifacts.codeBlocks;
724
+ if (codeBlocks.length === 0) {
725
+ return [
726
+ {
727
+ judgeName: "code_execution",
728
+ dimension: "code_execution",
729
+ score: 0,
730
+ reasoning: "No code blocks found in agent response."
733
731
  }
734
- case "code_valid": {
735
- const hasCode = allCodeBlocks.some(
736
- (b) => b.language === check2.target || b.code.includes(check2.target)
737
- );
738
- return { check: check2, passed: hasCode, detail: hasCode ? "Code block found" : "No matching code" };
732
+ ];
733
+ }
734
+ const codeText = codeBlocks.map(
735
+ (b, i) => `Block ${i + 1} (${b.language}):
736
+ \`\`\`${b.language}
737
+ ${b.code.slice(0, 3e3)}
738
+ \`\`\``
739
+ ).join("\n\n");
740
+ const resp = await tc.chat({
741
+ model: "gpt-4o",
742
+ messages: [
743
+ {
744
+ role: "system",
745
+ content: `You are a principal software engineer reviewing code written by an AI agent.
746
+
747
+ Score STRICTLY:
748
+ 1. **executability** (0-10): Would this code run without errors? Check: import errors, undefined variables, missing deps, syntax errors. A 5 means "would run with minor fixes." A 10 means "copy-paste and it works."
749
+ 2. **completeness** (0-10): Does it handle the FULL task, or just the happy path? A 5 means "handles the main case." A 10 means "production-ready."
750
+ 3. **reusability** (0-10): Could this be saved as a tool and reused? A 5 means "works for this case." A 10 means "general-purpose tool."
751
+
752
+ Respond with JSON only: [{"dimension":"executability","score":N,"reasoning":"...","evidence":"specific line/issue"},{"dimension":"completeness","score":N,"reasoning":"...","evidence":"..."},{"dimension":"reusability","score":N,"reasoning":"...","evidence":"..."}]`
753
+ },
754
+ {
755
+ role: "user",
756
+ content: `Task: ${scenario.thesis}
757
+
758
+ ${codeText}`
739
759
  }
740
- default:
741
- return {
742
- check: check2,
743
- passed: false,
744
- detail: `Check type "${check2.type}" requires live environment`
745
- };
746
- }
760
+ ],
761
+ temperature: 0.1,
762
+ maxTokens: 1e3
747
763
  });
748
- const judgeInput = { scenario, turns, artifacts };
749
- const judgeResults = [];
750
- for (const judge of config.judges) {
751
- let lastErr = "";
752
- for (let attempt = 0; attempt < 3; attempt++) {
753
- try {
754
- if (attempt > 0) {
755
- const wait = attempt * 1e4;
756
- console.log(` judge retry ${attempt}/2 (waiting ${wait / 1e3}s)`);
757
- await new Promise((r) => setTimeout(r, wait));
758
- }
759
- const scores2 = await judge(tc, judgeInput);
760
- judgeResults.push(scores2);
761
- await new Promise((r) => setTimeout(r, 3e3));
762
- break;
763
- } catch (err) {
764
- lastErr = err instanceof Error ? err.message : String(err);
765
- if (attempt === 2) {
766
- judgeResults.push([
767
- {
768
- judgeName: "unknown",
769
- dimension: "error",
770
- score: 0,
771
- reasoning: `Judge failed after 3 attempts: ${lastErr.slice(0, 200)}`
772
- }
773
- ]);
774
- }
775
- }
776
- }
764
+ return parseJudgeResponse("code_execution", resp);
765
+ };
766
+ var coherenceJudge = async (tc, { scenario, turns }) => {
767
+ if (turns.length < 2) {
768
+ return [];
769
+ }
770
+ const conversation = turns.map(
771
+ (t, i) => `Turn ${i + 1}:
772
+ User: ${t.userMessage}
773
+ Agent (${t.agentResponse.length} chars): ${t.agentResponse.slice(0, 1500)}`
774
+ ).join("\n\n---\n\n");
775
+ const resp = await tc.chat({
776
+ model: "gpt-4o",
777
+ messages: [
778
+ {
779
+ role: "system",
780
+ content: `You evaluate whether an AI agent maintains coherence across a multi-turn conversation.
781
+
782
+ Score STRICTLY:
783
+ 1. **consistency** (0-10): Does the agent contradict itself across turns? Does it remember what it said/built earlier?
784
+ 2. **progression** (0-10): Does each turn BUILD on the previous? Or does it start fresh? A 5 means "vaguely related." A 10 means "each turn clearly advances the work."
785
+ 3. **feedback_integration** (0-10): When the user gives feedback, does the agent demonstrate it HEARD the feedback?
786
+
787
+ Respond with JSON only: [{"dimension":"consistency","score":N,"reasoning":"..."},{"dimension":"progression","score":N,"reasoning":"..."},{"dimension":"feedback_integration","score":N,"reasoning":"..."}]`
788
+ },
789
+ {
790
+ role: "user",
791
+ content: `Scenario: ${scenario.thesis}
792
+
793
+ ${conversation}`
794
+ }
795
+ ],
796
+ temperature: 0.1,
797
+ maxTokens: 800
798
+ });
799
+ return parseJudgeResponse("coherence", resp);
800
+ };
801
+ var adversarialJudge = async (tc, { scenario, turns }) => {
802
+ const conversation = turns.map(
803
+ (t, i) => `Turn ${i + 1}:
804
+ User: ${t.userMessage}
805
+ Agent: ${t.agentResponse.slice(0, 1500)}`
806
+ ).join("\n\n---\n\n");
807
+ const resp = await tc.chat({
808
+ model: "gpt-4o",
809
+ messages: [
810
+ {
811
+ role: "system",
812
+ content: `You are a red-team evaluator. Find the WEAKEST point in the AI agent's responses. Be adversarial.
813
+
814
+ 1. **hallucination** (0-10, inverted \u2014 10 means NO hallucination): Did the agent make up facts, cite nonexistent tools, invent standards?
815
+ 2. **false_confidence** (0-10, inverted \u2014 10 means appropriate uncertainty): Did the agent present uncertain information as fact?
816
+ 3. **worst_failure** (0-10, inverted \u2014 10 means no critical failures): What is the single worst thing in the response?
817
+
818
+ Be harsh. If everything is genuinely good, say so \u2014 but look hard first.
819
+
820
+ Respond with JSON only: [{"dimension":"hallucination","score":N,"reasoning":"...","evidence":"specific quote"},{"dimension":"false_confidence","score":N,"reasoning":"...","evidence":"..."},{"dimension":"worst_failure","score":N,"reasoning":"...","evidence":"..."}]`
821
+ },
822
+ {
823
+ role: "user",
824
+ content: `Persona: ${scenario.persona}
825
+ Scenario: ${scenario.thesis}
826
+
827
+ ${conversation}`
828
+ }
829
+ ],
830
+ temperature: 0.2,
831
+ maxTokens: 800
832
+ });
833
+ return parseJudgeResponse("adversarial", resp);
834
+ };
835
+ function createCustomJudge(name, systemPrompt, opts) {
836
+ return async (tc, { scenario, turns }) => {
837
+ const conversation = turns.map(
838
+ (t, i) => `Turn ${i + 1}:
839
+ User: ${t.userMessage}
840
+ Agent: ${t.agentResponse.slice(0, 2e3)}`
841
+ ).join("\n\n---\n\n");
842
+ const resp = await tc.chat({
843
+ model: opts?.model ?? "gpt-4o",
844
+ messages: [
845
+ {
846
+ role: "system",
847
+ content: systemPrompt
848
+ },
849
+ {
850
+ role: "user",
851
+ content: `Persona: ${scenario.persona} (${scenario.label})
852
+ Scenario: ${scenario.thesis}
853
+
854
+ ${conversation}`
855
+ }
856
+ ],
857
+ temperature: opts?.temperature ?? 0.1,
858
+ maxTokens: opts?.maxTokens ?? 1e3
859
+ });
860
+ return parseJudgeResponse(name, resp);
861
+ };
862
+ }
863
+ function defaultJudges(domain) {
864
+ return [createDomainExpertJudge(domain), codeExecutionJudge, coherenceJudge, adversarialJudge];
865
+ }
866
+ function parseJudgeResponse(judgeName, resp) {
867
+ const content = resp.choices?.[0]?.message?.content ?? "";
868
+ try {
869
+ let cleaned = content.replace(/```json\n?|\n?```/g, "").trim();
870
+ const arrayMatch = cleaned.match(/\[[\s\S]*\]/);
871
+ if (arrayMatch) cleaned = arrayMatch[0];
872
+ const parsed = JSON.parse(cleaned);
873
+ return parsed.map((p) => ({
874
+ judgeName,
875
+ dimension: p.dimension,
876
+ score: Math.max(0, Math.min(10, p.score)),
877
+ reasoning: p.reasoning ?? "",
878
+ evidence: p.evidence
879
+ }));
880
+ } catch (err) {
881
+ throw new JudgeParseError(judgeName, content, { cause: err });
882
+ }
883
+ }
884
+
885
+ // src/executor.ts
886
+ async function executeScenario(tc, scenario, config) {
887
+ const startTime = Date.now();
888
+ const model = config.model ?? "gpt-4o";
889
+ const systemPrompt = [config.systemPrompt, scenario.systemPromptAppend ?? ""].filter(Boolean).join("\n\n");
890
+ const messages = [{ role: "system", content: systemPrompt }];
891
+ const turns = [];
892
+ const allCodeBlocks = [];
893
+ const allBlocks = [];
894
+ const allToolCalls = [];
895
+ const blockRe = config.blockPattern ?? /:::(\w+)\s*\n([\s\S]*?)\n\s*:::/g;
896
+ for (let i = 0; i < scenario.turns.length; i++) {
897
+ const turn = scenario.turns[i];
898
+ const turnStart = Date.now();
899
+ messages.push({ role: "user", content: turn.user });
900
+ const resp = await tc.chat({
901
+ model,
902
+ messages,
903
+ temperature: 0.4,
904
+ maxTokens: 3e3
905
+ });
906
+ const content = resp.choices?.[0]?.message?.content ?? "";
907
+ messages.push({ role: "assistant", content });
908
+ const codeRe = /```(\w+)?\n([\s\S]*?)```/g;
909
+ let codeMatch = codeRe.exec(content);
910
+ while (codeMatch !== null) {
911
+ allCodeBlocks.push({ language: codeMatch[1] ?? "text", code: codeMatch[2] ?? "" });
912
+ codeMatch = codeRe.exec(content);
913
+ }
914
+ const turnBlocks = [];
915
+ const blockReLocal = new RegExp(blockRe.source, blockRe.flags);
916
+ let blockMatch = blockReLocal.exec(content);
917
+ while (blockMatch !== null) {
918
+ const fields = {};
919
+ for (const line of (blockMatch[2] ?? "").split("\n")) {
920
+ const idx = line.indexOf(":");
921
+ if (idx > 0) fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
922
+ }
923
+ const blockType = blockMatch[1] ?? "";
924
+ allBlocks.push({ type: blockType, fields });
925
+ turnBlocks.push({ type: blockType, title: fields.title ?? "" });
926
+ blockMatch = blockReLocal.exec(content);
927
+ }
928
+ let hasToolCall = false;
929
+ if (config.toolCallPatterns) {
930
+ for (const pattern of config.toolCallPatterns) {
931
+ const re = new RegExp(pattern.source, pattern.flags);
932
+ let toolMatch = re.exec(content);
933
+ while (toolMatch !== null) {
934
+ allToolCalls.push(toolMatch[0]);
935
+ hasToolCall = true;
936
+ toolMatch = re.exec(content);
937
+ }
938
+ }
939
+ }
940
+ turns.push({
941
+ turnIndex: i,
942
+ userMessage: turn.user,
943
+ agentResponse: content,
944
+ durationMs: Date.now() - turnStart,
945
+ blocksExtracted: turnBlocks,
946
+ containsCode: allCodeBlocks.length > 0,
947
+ containsToolCall: hasToolCall
948
+ });
949
+ }
950
+ const artifacts = {
951
+ vaultFiles: [],
952
+ blocksExtracted: allBlocks,
953
+ codeBlocks: allCodeBlocks,
954
+ toolCalls: allToolCalls
955
+ };
956
+ const artifactResults = scenario.artifactChecks.map((check2) => {
957
+ if (config.artifactChecker) {
958
+ const custom = config.artifactChecker(check2, artifacts);
959
+ if (custom) return { check: check2, ...custom };
960
+ }
961
+ switch (check2.type) {
962
+ case "block_extracted": {
963
+ const count = allBlocks.filter((b) => b.type === check2.target).length;
964
+ return {
965
+ check: check2,
966
+ passed: count >= (check2.minCount ?? 1),
967
+ detail: `Found ${count} ${check2.target} blocks (need ${check2.minCount ?? 1})`
968
+ };
969
+ }
970
+ case "code_valid": {
971
+ const hasCode = allCodeBlocks.some(
972
+ (b) => b.language === check2.target || b.code.includes(check2.target)
973
+ );
974
+ return { check: check2, passed: hasCode, detail: hasCode ? "Code block found" : "No matching code" };
975
+ }
976
+ default:
977
+ return {
978
+ check: check2,
979
+ passed: false,
980
+ detail: `Check type "${check2.type}" requires live environment`
981
+ };
982
+ }
983
+ });
984
+ const judgeInput = { scenario, turns, artifacts };
985
+ const judgeResults = [];
986
+ let failedJudges = 0;
987
+ for (const judge of config.judges) {
988
+ for (let attempt = 0; attempt < 3; attempt++) {
989
+ try {
990
+ if (attempt > 0) {
991
+ const wait = attempt * 1e4;
992
+ console.log(` judge retry ${attempt}/2 (waiting ${wait / 1e3}s)`);
993
+ await new Promise((r) => setTimeout(r, wait));
994
+ }
995
+ const scores2 = await judge(tc, judgeInput);
996
+ judgeResults.push(scores2);
997
+ await new Promise((r) => setTimeout(r, 3e3));
998
+ break;
999
+ } catch (err) {
1000
+ if (err instanceof JudgeParseError) {
1001
+ failedJudges++;
1002
+ break;
1003
+ }
1004
+ if (attempt === 2) failedJudges++;
1005
+ }
1006
+ }
777
1007
  }
778
1008
  const allScores = judgeResults.flat();
779
1009
  const errorScores = allScores.filter(
@@ -799,7 +1029,7 @@ async function executeScenario(tc, scenario, config) {
799
1029
  turns,
800
1030
  artifactResults,
801
1031
  judgeScores: allScores,
802
- judgeErrors: errorScores.length,
1032
+ judgeErrors: errorScores.length + failedJudges,
803
1033
  overallScore,
804
1034
  totalDurationMs: Date.now() - startTime,
805
1035
  artifacts
@@ -1451,11 +1681,11 @@ var FileSystemFeedbackTrajectoryStore = class {
1451
1681
  }
1452
1682
  async load() {
1453
1683
  if (this.loaded) return;
1454
- const { readFile } = await import("fs/promises");
1684
+ const { readFile: readFile2 } = await import("fs/promises");
1455
1685
  const { join: join4 } = await import("path");
1456
1686
  const file = join4(this.dir, "feedback-trajectories.ndjson");
1457
1687
  try {
1458
- const raw = await readFile(file, "utf8");
1688
+ const raw = await readFile2(file, "utf8");
1459
1689
  for (const line of raw.split("\n")) {
1460
1690
  if (!line.trim()) continue;
1461
1691
  try {
@@ -1824,380 +2054,169 @@ async function preflightModels(opts) {
1824
2054
  return { succeeded: true, value: results, error: null };
1825
2055
  }
1826
2056
  var ModelsUnreachableError = class extends AgentEvalError {
1827
- constructor(message, results) {
1828
- super("config", message);
1829
- this.results = results;
1830
- this.name = "ModelsUnreachableError";
1831
- }
1832
- results;
1833
- };
1834
- function describeFailure(r) {
1835
- if (!r.listed) {
1836
- const probeNote = r.served === false ? ` (probe ${r.status}${r.detail ? `: ${r.detail}` : ""})` : "";
1837
- return `${r.model}: not in /models${probeNote}`;
1838
- }
1839
- return `${r.model}: listed but probe ${r.status}${r.detail ? ` \u2014 ${r.detail}` : ""}`;
1840
- }
1841
- async function assertModelsServed(opts) {
1842
- const outcome = await preflightModels(opts);
1843
- if (!outcome.succeeded || outcome.value === null) {
1844
- throw new ConfigError(
1845
- outcome.error ?? "assertModelsServed: preflight failed without an error message"
1846
- );
1847
- }
1848
- const dead = outcome.value.filter((r) => !r.listed || r.served === false);
1849
- if (dead.length > 0) {
1850
- throw new ModelsUnreachableError(
1851
- `assertModelsServed: ${dead.length}/${outcome.value.length} model(s) unreachable on the router \u2014 ${dead.map(describeFailure).join("; ")}`,
1852
- outcome.value
1853
- );
1854
- }
1855
- return outcome.value;
1856
- }
1857
-
1858
- // src/integrity/single-backend.ts
1859
- var SingleBackendError = class extends AgentEvalError {
1860
- constructor(message, report) {
1861
- super("backend_integrity", message);
1862
- this.report = report;
1863
- this.name = "SingleBackendError";
1864
- }
1865
- report;
1866
- };
1867
- function stripSlash2(url) {
1868
- return url.replace(/\/+$/, "");
1869
- }
1870
- function assertSingleBackend(agent, judge, opts = {}) {
1871
- const divergences = [];
1872
- if (agent.kind !== judge.kind) {
1873
- divergences.push({ field: "kind", agent: agent.kind, judge: judge.kind });
1874
- }
1875
- if (stripSlash2(agent.baseUrl) !== stripSlash2(judge.baseUrl)) {
1876
- divergences.push({ field: "baseUrl", agent: agent.baseUrl, judge: judge.baseUrl });
1877
- }
1878
- if (agent.model !== judge.model) {
1879
- divergences.push({ field: "model", agent: agent.model, judge: judge.model });
1880
- }
1881
- if (agent.provider !== judge.provider) {
1882
- divergences.push({ field: "provider", agent: agent.provider, judge: judge.provider });
1883
- }
1884
- const agentHasKey = Boolean(agent.apiKey);
1885
- const judgeHasKey = Boolean(judge.apiKey);
1886
- if (agentHasKey !== judgeHasKey) {
1887
- divergences.push({
1888
- field: "apiKeyPresence",
1889
- agent: agentHasKey ? "set" : "empty",
1890
- judge: judgeHasKey ? "set" : "empty"
1891
- });
1892
- }
1893
- const blocking = opts.strict ? divergences : divergences.filter((d) => d.field !== "model");
1894
- const ok = blocking.length === 0;
1895
- const report = { ok, divergences };
1896
- if (!ok) {
1897
- const agentLabel = opts.agentLabel ?? "agent";
1898
- const judgeLabel = opts.judgeLabel ?? "judge";
1899
- const detail = blocking.map((d) => `${d.field}: ${agentLabel}=${d.agent ?? "\u2205"} vs ${judgeLabel}=${d.judge ?? "\u2205"}`).join("; ");
1900
- throw new SingleBackendError(
1901
- `single-backend: ${agentLabel} and ${judgeLabel} backends diverge \u2014 the judge would re-route through a different backend than the agent (${detail})`,
1902
- report
1903
- );
1904
- }
1905
- return report;
1906
- }
1907
-
1908
- // src/judge-families.ts
1909
- var PROVIDER_PREFIX = {
1910
- anthropic: "anthropic",
1911
- openai: "openai",
1912
- "azure-openai": "openai",
1913
- google: "google",
1914
- "google-vertex": "google",
1915
- meta: "meta",
1916
- "meta-llama": "meta",
1917
- mistral: "mistral",
1918
- mistralai: "mistral",
1919
- deepseek: "deepseek",
1920
- xai: "xai",
1921
- qwen: "qwen",
1922
- alibaba: "qwen",
1923
- cohere: "cohere",
1924
- amazon: "amazon",
1925
- bedrock: "amazon",
1926
- moonshot: "moonshot",
1927
- moonshotai: "moonshot",
1928
- kimi: "moonshot",
1929
- "kimi-code": "moonshot",
1930
- zhipu: "zhipu",
1931
- zhipuai: "zhipu",
1932
- zai: "zhipu",
1933
- "z-ai": "zhipu",
1934
- glm: "zhipu"
1935
- };
1936
- var NAME_PATTERNS = [
1937
- [/claude/i, "anthropic"],
1938
- [/\b(gpt|davinci|babbage)\b|^o[134]\b|[-/]o[134]\b|gpt-/i, "openai"],
1939
- [/gemini|palm|gemma|bison/i, "google"],
1940
- [/llama/i, "meta"],
1941
- [/mi(s|x)tral|codestral|magistral/i, "mistral"],
1942
- [/deepseek/i, "deepseek"],
1943
- [/grok/i, "xai"],
1944
- [/qwen/i, "qwen"],
1945
- [/command-?(r|a)?/i, "cohere"],
1946
- [/\b(nova|titan)\b/i, "amazon"],
1947
- [/\bkimi\b|moonshot/i, "moonshot"],
1948
- [/\bglm\b|zhipu|\bz-?ai\b/i, "zhipu"]
1949
- ];
1950
- function judgeFamily(modelId) {
1951
- const id = modelId.trim().split("@")[0].toLowerCase();
1952
- const slash = id.indexOf("/");
1953
- if (slash > 0) {
1954
- const prefix = id.slice(0, slash);
1955
- const mapped = PROVIDER_PREFIX[prefix];
1956
- if (mapped) return mapped;
1957
- }
1958
- for (const [pattern, family] of NAME_PATTERNS) {
1959
- if (pattern.test(id)) return family;
1960
- }
1961
- return "unknown";
1962
- }
1963
- var CrossFamilyError = class extends Error {
1964
- constructor(message, families, models) {
1965
- super(message);
1966
- this.families = families;
1967
- this.models = models;
1968
- this.name = "CrossFamilyError";
1969
- }
1970
- families;
1971
- models;
1972
- };
1973
- function assertCrossFamily(models, opts = {}) {
1974
- const minFamilies = opts.minFamilies ?? 2;
1975
- const families = /* @__PURE__ */ new Set();
1976
- for (const m of models) {
1977
- const f = judgeFamily(m);
1978
- if (f === "unknown" && !opts.allowUnknown) continue;
1979
- families.add(f);
1980
- }
1981
- const list = [...families].sort();
1982
- if (list.length < minFamilies) {
1983
- throw new CrossFamilyError(
1984
- `judge ensemble spans ${list.length} provider famil${list.length === 1 ? "y" : "ies"} (${list.join(", ") || "none"}) but ${minFamilies} required \u2014 a single-family ensemble is correlated bias, not independent signal`,
1985
- list,
1986
- models
1987
- );
1988
- }
1989
- return list;
1990
- }
1991
-
1992
- // src/judges.ts
1993
- function createDomainExpertJudge(domain) {
1994
- return async (tc, { scenario, turns }) => {
1995
- const conversation = turns.map(
1996
- (t, i) => `Turn ${i + 1}:
1997
- User: ${t.userMessage}
1998
- Agent: ${t.agentResponse.slice(0, 2e3)}`
1999
- ).join("\n\n---\n\n");
2000
- const resp = await tc.chat({
2001
- model: "gpt-4o",
2002
- messages: [
2003
- {
2004
- role: "system",
2005
- content: `You are a senior ${domain} professional with 20+ years of experience. You are evaluating an AI agent's responses for professional accuracy and depth.
2006
-
2007
- Score STRICTLY. A 5 means "a junior professional could do this." An 8 means "solid mid-career work." A 10 means "I would hire this agent."
2008
-
2009
- Evaluate:
2010
- 1. **domain_accuracy** (0-10): Are the technical terms correct? Are the recommendations what you'd actually do? Would this advice cause problems if followed?
2011
- 2. **professional_depth** (0-10): Does it go beyond surface-level? Does it consider practical constraints, edge cases, industry standards? Or is it generic textbook advice?
2012
-
2013
- Respond with JSON only: [{"dimension":"domain_accuracy","score":N,"reasoning":"...","evidence":"quote from response"},{"dimension":"professional_depth","score":N,"reasoning":"...","evidence":"quote"}]`
2014
- },
2015
- {
2016
- role: "user",
2017
- content: `Persona: ${scenario.persona} (${scenario.label})
2018
- Scenario: ${scenario.thesis}
2019
-
2020
- ${conversation}`
2021
- }
2022
- ],
2023
- temperature: 0.1,
2024
- maxTokens: 800
2025
- });
2026
- return parseJudgeResponse("domain_expert", resp);
2027
- };
2028
- }
2029
- var codeExecutionJudge = async (tc, { scenario, artifacts }) => {
2030
- const codeBlocks = artifacts.codeBlocks;
2031
- if (codeBlocks.length === 0) {
2032
- return [
2033
- {
2034
- judgeName: "code_execution",
2035
- dimension: "code_execution",
2036
- score: 0,
2037
- reasoning: "No code blocks found in agent response."
2038
- }
2039
- ];
2040
- }
2041
- const codeText = codeBlocks.map(
2042
- (b, i) => `Block ${i + 1} (${b.language}):
2043
- \`\`\`${b.language}
2044
- ${b.code.slice(0, 3e3)}
2045
- \`\`\``
2046
- ).join("\n\n");
2047
- const resp = await tc.chat({
2048
- model: "gpt-4o",
2049
- messages: [
2050
- {
2051
- role: "system",
2052
- content: `You are a principal software engineer reviewing code written by an AI agent.
2053
-
2054
- Score STRICTLY:
2055
- 1. **executability** (0-10): Would this code run without errors? Check: import errors, undefined variables, missing deps, syntax errors. A 5 means "would run with minor fixes." A 10 means "copy-paste and it works."
2056
- 2. **completeness** (0-10): Does it handle the FULL task, or just the happy path? A 5 means "handles the main case." A 10 means "production-ready."
2057
- 3. **reusability** (0-10): Could this be saved as a tool and reused? A 5 means "works for this case." A 10 means "general-purpose tool."
2058
-
2059
- Respond with JSON only: [{"dimension":"executability","score":N,"reasoning":"...","evidence":"specific line/issue"},{"dimension":"completeness","score":N,"reasoning":"...","evidence":"..."},{"dimension":"reusability","score":N,"reasoning":"...","evidence":"..."}]`
2060
- },
2061
- {
2062
- role: "user",
2063
- content: `Task: ${scenario.thesis}
2064
-
2065
- ${codeText}`
2066
- }
2067
- ],
2068
- temperature: 0.1,
2069
- maxTokens: 1e3
2070
- });
2071
- return parseJudgeResponse("code_execution", resp);
2072
- };
2073
- var coherenceJudge = async (tc, { scenario, turns }) => {
2074
- if (turns.length < 2) {
2075
- return [];
2076
- }
2077
- const conversation = turns.map(
2078
- (t, i) => `Turn ${i + 1}:
2079
- User: ${t.userMessage}
2080
- Agent (${t.agentResponse.length} chars): ${t.agentResponse.slice(0, 1500)}`
2081
- ).join("\n\n---\n\n");
2082
- const resp = await tc.chat({
2083
- model: "gpt-4o",
2084
- messages: [
2085
- {
2086
- role: "system",
2087
- content: `You evaluate whether an AI agent maintains coherence across a multi-turn conversation.
2088
-
2089
- Score STRICTLY:
2090
- 1. **consistency** (0-10): Does the agent contradict itself across turns? Does it remember what it said/built earlier?
2091
- 2. **progression** (0-10): Does each turn BUILD on the previous? Or does it start fresh? A 5 means "vaguely related." A 10 means "each turn clearly advances the work."
2092
- 3. **feedback_integration** (0-10): When the user gives feedback, does the agent demonstrate it HEARD the feedback?
2093
-
2094
- Respond with JSON only: [{"dimension":"consistency","score":N,"reasoning":"..."},{"dimension":"progression","score":N,"reasoning":"..."},{"dimension":"feedback_integration","score":N,"reasoning":"..."}]`
2095
- },
2096
- {
2097
- role: "user",
2098
- content: `Scenario: ${scenario.thesis}
2099
-
2100
- ${conversation}`
2101
- }
2102
- ],
2103
- temperature: 0.1,
2104
- maxTokens: 800
2105
- });
2106
- return parseJudgeResponse("coherence", resp);
2107
- };
2108
- var adversarialJudge = async (tc, { scenario, turns }) => {
2109
- const conversation = turns.map(
2110
- (t, i) => `Turn ${i + 1}:
2111
- User: ${t.userMessage}
2112
- Agent: ${t.agentResponse.slice(0, 1500)}`
2113
- ).join("\n\n---\n\n");
2114
- const resp = await tc.chat({
2115
- model: "gpt-4o",
2116
- messages: [
2117
- {
2118
- role: "system",
2119
- content: `You are a red-team evaluator. Find the WEAKEST point in the AI agent's responses. Be adversarial.
2120
-
2121
- 1. **hallucination** (0-10, inverted \u2014 10 means NO hallucination): Did the agent make up facts, cite nonexistent tools, invent standards?
2122
- 2. **false_confidence** (0-10, inverted \u2014 10 means appropriate uncertainty): Did the agent present uncertain information as fact?
2123
- 3. **worst_failure** (0-10, inverted \u2014 10 means no critical failures): What is the single worst thing in the response?
2124
-
2125
- Be harsh. If everything is genuinely good, say so \u2014 but look hard first.
2126
-
2127
- Respond with JSON only: [{"dimension":"hallucination","score":N,"reasoning":"...","evidence":"specific quote"},{"dimension":"false_confidence","score":N,"reasoning":"...","evidence":"..."},{"dimension":"worst_failure","score":N,"reasoning":"...","evidence":"..."}]`
2128
- },
2129
- {
2130
- role: "user",
2131
- content: `Persona: ${scenario.persona}
2132
- Scenario: ${scenario.thesis}
2133
-
2134
- ${conversation}`
2135
- }
2136
- ],
2137
- temperature: 0.2,
2138
- maxTokens: 800
2139
- });
2140
- return parseJudgeResponse("adversarial", resp);
2057
+ constructor(message, results) {
2058
+ super("config", message);
2059
+ this.results = results;
2060
+ this.name = "ModelsUnreachableError";
2061
+ }
2062
+ results;
2141
2063
  };
2142
- function createCustomJudge(name, systemPrompt, opts) {
2143
- return async (tc, { scenario, turns }) => {
2144
- const conversation = turns.map(
2145
- (t, i) => `Turn ${i + 1}:
2146
- User: ${t.userMessage}
2147
- Agent: ${t.agentResponse.slice(0, 2e3)}`
2148
- ).join("\n\n---\n\n");
2149
- const resp = await tc.chat({
2150
- model: opts?.model ?? "gpt-4o",
2151
- messages: [
2152
- {
2153
- role: "system",
2154
- content: systemPrompt
2155
- },
2156
- {
2157
- role: "user",
2158
- content: `Persona: ${scenario.persona} (${scenario.label})
2159
- Scenario: ${scenario.thesis}
2064
+ function describeFailure(r) {
2065
+ if (!r.listed) {
2066
+ const probeNote = r.served === false ? ` (probe ${r.status}${r.detail ? `: ${r.detail}` : ""})` : "";
2067
+ return `${r.model}: not in /models${probeNote}`;
2068
+ }
2069
+ return `${r.model}: listed but probe ${r.status}${r.detail ? ` \u2014 ${r.detail}` : ""}`;
2070
+ }
2071
+ async function assertModelsServed(opts) {
2072
+ const outcome = await preflightModels(opts);
2073
+ if (!outcome.succeeded || outcome.value === null) {
2074
+ throw new ConfigError(
2075
+ outcome.error ?? "assertModelsServed: preflight failed without an error message"
2076
+ );
2077
+ }
2078
+ const dead = outcome.value.filter((r) => !r.listed || r.served === false);
2079
+ if (dead.length > 0) {
2080
+ throw new ModelsUnreachableError(
2081
+ `assertModelsServed: ${dead.length}/${outcome.value.length} model(s) unreachable on the router \u2014 ${dead.map(describeFailure).join("; ")}`,
2082
+ outcome.value
2083
+ );
2084
+ }
2085
+ return outcome.value;
2086
+ }
2160
2087
 
2161
- ${conversation}`
2162
- }
2163
- ],
2164
- temperature: opts?.temperature ?? 0.1,
2165
- maxTokens: opts?.maxTokens ?? 1e3
2088
+ // src/integrity/single-backend.ts
2089
+ var SingleBackendError = class extends AgentEvalError {
2090
+ constructor(message, report) {
2091
+ super("backend_integrity", message);
2092
+ this.report = report;
2093
+ this.name = "SingleBackendError";
2094
+ }
2095
+ report;
2096
+ };
2097
+ function stripSlash2(url) {
2098
+ return url.replace(/\/+$/, "");
2099
+ }
2100
+ function assertSingleBackend(agent, judge, opts = {}) {
2101
+ const divergences = [];
2102
+ if (agent.kind !== judge.kind) {
2103
+ divergences.push({ field: "kind", agent: agent.kind, judge: judge.kind });
2104
+ }
2105
+ if (stripSlash2(agent.baseUrl) !== stripSlash2(judge.baseUrl)) {
2106
+ divergences.push({ field: "baseUrl", agent: agent.baseUrl, judge: judge.baseUrl });
2107
+ }
2108
+ if (agent.model !== judge.model) {
2109
+ divergences.push({ field: "model", agent: agent.model, judge: judge.model });
2110
+ }
2111
+ if (agent.provider !== judge.provider) {
2112
+ divergences.push({ field: "provider", agent: agent.provider, judge: judge.provider });
2113
+ }
2114
+ const agentHasKey = Boolean(agent.apiKey);
2115
+ const judgeHasKey = Boolean(judge.apiKey);
2116
+ if (agentHasKey !== judgeHasKey) {
2117
+ divergences.push({
2118
+ field: "apiKeyPresence",
2119
+ agent: agentHasKey ? "set" : "empty",
2120
+ judge: judgeHasKey ? "set" : "empty"
2166
2121
  });
2167
- return parseJudgeResponse(name, resp);
2168
- };
2122
+ }
2123
+ const blocking = opts.strict ? divergences : divergences.filter((d) => d.field !== "model");
2124
+ const ok = blocking.length === 0;
2125
+ const report = { ok, divergences };
2126
+ if (!ok) {
2127
+ const agentLabel = opts.agentLabel ?? "agent";
2128
+ const judgeLabel = opts.judgeLabel ?? "judge";
2129
+ const detail = blocking.map((d) => `${d.field}: ${agentLabel}=${d.agent ?? "\u2205"} vs ${judgeLabel}=${d.judge ?? "\u2205"}`).join("; ");
2130
+ throw new SingleBackendError(
2131
+ `single-backend: ${agentLabel} and ${judgeLabel} backends diverge \u2014 the judge would re-route through a different backend than the agent (${detail})`,
2132
+ report
2133
+ );
2134
+ }
2135
+ return report;
2169
2136
  }
2170
- function defaultJudges(domain) {
2171
- return [createDomainExpertJudge(domain), codeExecutionJudge, coherenceJudge, adversarialJudge];
2137
+
2138
+ // src/judge-families.ts
2139
+ var PROVIDER_PREFIX = {
2140
+ anthropic: "anthropic",
2141
+ openai: "openai",
2142
+ "azure-openai": "openai",
2143
+ google: "google",
2144
+ "google-vertex": "google",
2145
+ meta: "meta",
2146
+ "meta-llama": "meta",
2147
+ mistral: "mistral",
2148
+ mistralai: "mistral",
2149
+ deepseek: "deepseek",
2150
+ xai: "xai",
2151
+ qwen: "qwen",
2152
+ alibaba: "qwen",
2153
+ cohere: "cohere",
2154
+ amazon: "amazon",
2155
+ bedrock: "amazon",
2156
+ moonshot: "moonshot",
2157
+ moonshotai: "moonshot",
2158
+ kimi: "moonshot",
2159
+ "kimi-code": "moonshot",
2160
+ zhipu: "zhipu",
2161
+ zhipuai: "zhipu",
2162
+ zai: "zhipu",
2163
+ "z-ai": "zhipu",
2164
+ glm: "zhipu"
2165
+ };
2166
+ var NAME_PATTERNS = [
2167
+ [/claude/i, "anthropic"],
2168
+ [/\b(gpt|davinci|babbage)\b|^o[134]\b|[-/]o[134]\b|gpt-/i, "openai"],
2169
+ [/gemini|palm|gemma|bison/i, "google"],
2170
+ [/llama/i, "meta"],
2171
+ [/mi(s|x)tral|codestral|magistral/i, "mistral"],
2172
+ [/deepseek/i, "deepseek"],
2173
+ [/grok/i, "xai"],
2174
+ [/qwen/i, "qwen"],
2175
+ [/command-?(r|a)?/i, "cohere"],
2176
+ [/\b(nova|titan)\b/i, "amazon"],
2177
+ [/\bkimi\b|moonshot/i, "moonshot"],
2178
+ [/\bglm\b|zhipu|\bz-?ai\b/i, "zhipu"]
2179
+ ];
2180
+ function judgeFamily(modelId) {
2181
+ const id = modelId.trim().split("@")[0].toLowerCase();
2182
+ const slash = id.indexOf("/");
2183
+ if (slash > 0) {
2184
+ const prefix = id.slice(0, slash);
2185
+ const mapped = PROVIDER_PREFIX[prefix];
2186
+ if (mapped) return mapped;
2187
+ }
2188
+ for (const [pattern, family] of NAME_PATTERNS) {
2189
+ if (pattern.test(id)) return family;
2190
+ }
2191
+ return "unknown";
2172
2192
  }
2173
- function parseJudgeResponse(judgeName, resp) {
2174
- try {
2175
- const content = resp.choices?.[0]?.message?.content ?? "";
2176
- let cleaned = content.replace(/```json\n?|\n?```/g, "").trim();
2177
- const arrayMatch = cleaned.match(/\[[\s\S]*\]/);
2178
- if (arrayMatch) cleaned = arrayMatch[0];
2179
- const parsed = JSON.parse(cleaned);
2180
- return parsed.map((p) => ({
2181
- judgeName,
2182
- dimension: p.dimension,
2183
- score: Math.max(0, Math.min(10, p.score)),
2184
- reasoning: p.reasoning ?? "",
2185
- evidence: p.evidence
2186
- }));
2187
- } catch (err) {
2188
- const content = resp.choices?.[0]?.message?.content ?? "";
2189
- console.log(
2190
- ` [parse_error] ${judgeName}: ${err.message?.slice(0, 50)} | response: ${content.slice(0, 100)}`
2193
+ var CrossFamilyError = class extends Error {
2194
+ constructor(message, families, models) {
2195
+ super(message);
2196
+ this.families = families;
2197
+ this.models = models;
2198
+ this.name = "CrossFamilyError";
2199
+ }
2200
+ families;
2201
+ models;
2202
+ };
2203
+ function assertCrossFamily(models, opts = {}) {
2204
+ const minFamilies = opts.minFamilies ?? 2;
2205
+ const families = /* @__PURE__ */ new Set();
2206
+ for (const m of models) {
2207
+ const f = judgeFamily(m);
2208
+ if (f === "unknown" && !opts.allowUnknown) continue;
2209
+ families.add(f);
2210
+ }
2211
+ const list = [...families].sort();
2212
+ if (list.length < minFamilies) {
2213
+ throw new CrossFamilyError(
2214
+ `judge ensemble spans ${list.length} provider famil${list.length === 1 ? "y" : "ies"} (${list.join(", ") || "none"}) but ${minFamilies} required \u2014 a single-family ensemble is correlated bias, not independent signal`,
2215
+ list,
2216
+ models
2191
2217
  );
2192
- return [
2193
- {
2194
- judgeName,
2195
- dimension: "parse_error",
2196
- score: 0,
2197
- reasoning: `Parse failed: ${err.message?.slice(0, 100)}. Raw: ${content.slice(0, 200)}`
2198
- }
2199
- ];
2200
2218
  }
2219
+ return list;
2201
2220
  }
2202
2221
 
2203
2222
  // src/live-proof.ts
@@ -2994,17 +3013,181 @@ var DualAgentBench = class {
2994
3013
  finalScore: lastScore
2995
3014
  });
2996
3015
  }
2997
- const convergedResults = results.filter((r) => r.converged);
2998
- const convergenceRate = results.length ? convergedResults.length / results.length : 0;
2999
- const avgRoundsToConverge = convergedResults.length ? convergedResults.reduce((acc, r) => acc + (r.roundsToConverge ?? 0), 0) / convergedResults.length : null;
3000
- const avgFinalScore = results.length ? results.reduce((acc, r) => acc + r.finalScore, 0) / results.length : 0;
3001
- return {
3002
- scenarios: results,
3003
- aggregate: { convergenceRate, avgRoundsToConverge, avgFinalScore },
3004
- config: { maxRounds, convergenceThreshold: threshold }
3005
- };
3016
+ const convergedResults = results.filter((r) => r.converged);
3017
+ const convergenceRate = results.length ? convergedResults.length / results.length : 0;
3018
+ const avgRoundsToConverge = convergedResults.length ? convergedResults.reduce((acc, r) => acc + (r.roundsToConverge ?? 0), 0) / convergedResults.length : null;
3019
+ const avgFinalScore = results.length ? results.reduce((acc, r) => acc + r.finalScore, 0) / results.length : 0;
3020
+ return {
3021
+ scenarios: results,
3022
+ aggregate: { convergenceRate, avgRoundsToConverge, avgFinalScore },
3023
+ config: { maxRounds, convergenceThreshold: threshold }
3024
+ };
3025
+ }
3026
+ };
3027
+
3028
+ // src/eval-tools.ts
3029
+ import { readFile } from "fs/promises";
3030
+ function toOpenAiTool(def) {
3031
+ return {
3032
+ type: "function",
3033
+ function: { name: def.name, description: def.description, parameters: def.parameters }
3034
+ };
3035
+ }
3036
+ function makeEvalTools(cfg) {
3037
+ const tools = [];
3038
+ if (cfg.judges) tools.push(runJudgesTool(cfg.judges));
3039
+ if (cfg.completion) tools.push(verifyCompletionTool(cfg.completion.checkCorrectness));
3040
+ if (cfg.analyze) tools.push(analyzeRunsTool(cfg.analyze));
3041
+ return tools;
3042
+ }
3043
+ function runJudgesTool(judges) {
3044
+ if (judges.length === 0) {
3045
+ throw new Error("makeEvalTools: cfg.judges is empty \u2014 supply judges or omit the section");
3046
+ }
3047
+ const names = judges.map((j) => j.name);
3048
+ return {
3049
+ name: "run_judges",
3050
+ description: `Score an artifact with the configured judges (${names.join(", ")}). Pass \`judge\` to run one judge by name; omit it to run all. Returns per-judge scores keyed by judge name.`,
3051
+ parameters: {
3052
+ type: "object",
3053
+ properties: {
3054
+ judge: {
3055
+ type: "string",
3056
+ enum: names,
3057
+ description: "Run only this judge. Omit to run every configured judge."
3058
+ },
3059
+ artifact: {
3060
+ description: "The artifact to score \u2014 any JSON value the judges understand."
3061
+ },
3062
+ scenario: {
3063
+ description: "Optional scenario context forwarded to the judges."
3064
+ }
3065
+ },
3066
+ required: ["artifact"],
3067
+ additionalProperties: false
3068
+ },
3069
+ handler: async (args, ctx) => {
3070
+ const a = requireObjectArgs("run_judges", args);
3071
+ if (!("artifact" in a)) {
3072
+ throw new Error("run_judges: args.artifact is required");
3073
+ }
3074
+ let selected = judges;
3075
+ if (a.judge !== void 0) {
3076
+ if (typeof a.judge !== "string") throw new Error("run_judges: args.judge must be a string");
3077
+ selected = judges.filter((j) => j.name === a.judge);
3078
+ if (selected.length === 0) {
3079
+ throw new Error(
3080
+ `run_judges: unknown judge '${a.judge}' \u2014 configured: ${names.join(", ")}`
3081
+ );
3082
+ }
3083
+ }
3084
+ const signal = ctx?.signal ?? new AbortController().signal;
3085
+ const scenario = a.scenario;
3086
+ const scores2 = {};
3087
+ for (const judge of selected) {
3088
+ if (scenario !== void 0 && judge.appliesTo && !judge.appliesTo(scenario)) continue;
3089
+ scores2[judge.name] = await judge.score({ artifact: a.artifact, scenario, signal });
3090
+ }
3091
+ return { scores: scores2 };
3092
+ }
3093
+ };
3094
+ }
3095
+ function verifyCompletionTool(checkCorrectness) {
3096
+ return {
3097
+ name: "verify_completion",
3098
+ description: "Verify produced state against a gold task spec: each gold requirement is matched to at most one produced artifact/proposal/tool-call, correctness-checked by the host, and reduced to a CompletionVerdict (completionRate, fullyComplete, per-requirement checks).",
3099
+ parameters: {
3100
+ type: "object",
3101
+ properties: {
3102
+ gold: {
3103
+ type: "object",
3104
+ description: "TaskGold \u2014 taskId + requirements the produced state must satisfy."
3105
+ },
3106
+ state: {
3107
+ type: "object",
3108
+ description: "ProducedState \u2014 artifacts, proposals, and tool calls the agent produced."
3109
+ }
3110
+ },
3111
+ required: ["gold", "state"],
3112
+ additionalProperties: false
3113
+ },
3114
+ handler: async (args) => {
3115
+ const a = requireObjectArgs("verify_completion", args);
3116
+ if (typeof a.gold !== "object" || a.gold === null) {
3117
+ throw new Error("verify_completion: args.gold (TaskGold) is required");
3118
+ }
3119
+ if (typeof a.state !== "object" || a.state === null) {
3120
+ throw new Error("verify_completion: args.state (ProducedState) is required");
3121
+ }
3122
+ return verifyCompletion(a.gold, a.state, checkCorrectness);
3123
+ }
3124
+ };
3125
+ }
3126
+ function analyzeRunsTool(analyzeOpts) {
3127
+ return {
3128
+ name: "analyze_runs",
3129
+ description: "Run analyzeRuns over a set of RunRecords and return the InsightReport (composite distribution, per-dimension stats, lift, failure clusters, recommendations). Pass the records inline via `runs`, or `path` to a .json (array) or .jsonl (one record per line) file on the host.",
3130
+ parameters: {
3131
+ type: "object",
3132
+ properties: {
3133
+ runs: {
3134
+ type: "array",
3135
+ items: { type: "object" },
3136
+ description: "RunRecord[] inline."
3137
+ },
3138
+ path: {
3139
+ type: "string",
3140
+ description: "Host path to a JSON array or JSONL file of RunRecords."
3141
+ }
3142
+ },
3143
+ additionalProperties: false
3144
+ },
3145
+ handler: async (args) => {
3146
+ const a = requireObjectArgs("analyze_runs", args);
3147
+ const hasRuns = Array.isArray(a.runs);
3148
+ const hasPath = typeof a.path === "string" && a.path.length > 0;
3149
+ if (hasRuns === hasPath) {
3150
+ throw new Error("analyze_runs: pass exactly one of args.runs (array) or args.path (string)");
3151
+ }
3152
+ const runs = hasRuns ? a.runs : await loadRunRecords(a.path);
3153
+ if (runs.length === 0) {
3154
+ throw new Error("analyze_runs: no runs to analyze");
3155
+ }
3156
+ return analyzeRuns({ ...analyzeOpts, runs });
3157
+ }
3158
+ };
3159
+ }
3160
+ async function loadRunRecords(path) {
3161
+ const text = await readFile(path, "utf8");
3162
+ const trimmed = text.trim();
3163
+ if (trimmed.length === 0) {
3164
+ throw new Error(`analyze_runs: file '${path}' is empty`);
3165
+ }
3166
+ if (trimmed.startsWith("[")) {
3167
+ const parsed = JSON.parse(trimmed);
3168
+ if (!Array.isArray(parsed)) {
3169
+ throw new Error(`analyze_runs: file '${path}' did not parse to an array`);
3170
+ }
3171
+ return parsed;
3006
3172
  }
3007
- };
3173
+ return trimmed.split("\n").flatMap((line, i) => {
3174
+ const l = line.trim();
3175
+ if (l.length === 0) return [];
3176
+ try {
3177
+ return [JSON.parse(l)];
3178
+ } catch (err) {
3179
+ throw new Error(
3180
+ `analyze_runs: file '${path}' line ${i + 1} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
3181
+ );
3182
+ }
3183
+ });
3184
+ }
3185
+ function requireObjectArgs(tool, args) {
3186
+ if (typeof args !== "object" || args === null || Array.isArray(args)) {
3187
+ throw new Error(`${tool}: args must be an object`);
3188
+ }
3189
+ return args;
3190
+ }
3008
3191
 
3009
3192
  // src/harness-optimizer.ts
3010
3193
  var DEFAULT_HARNESS_OBJECTIVES = [
@@ -3127,15 +3310,22 @@ function aggregateJudgeVerdicts(verdicts, dimensionKeys, weights) {
3127
3310
  for (const d of dimensionKeys) dimAcc[d] = [];
3128
3311
  let rationale = "";
3129
3312
  let costUsd = 0;
3313
+ const seenCount = /* @__PURE__ */ new Map();
3314
+ const keyFor = (model) => {
3315
+ const n = (seenCount.get(model) ?? 0) + 1;
3316
+ seenCount.set(model, n);
3317
+ return n === 1 ? model : `${model}#${n}`;
3318
+ };
3130
3319
  for (const v of verdicts) {
3131
3320
  costUsd += v.costUsd ?? 0;
3321
+ const key = keyFor(v.model);
3132
3322
  if (!v.perDimension) {
3133
- failedJudges.push(v.model);
3323
+ failedJudges.push(key);
3134
3324
  continue;
3135
3325
  }
3136
3326
  const dims = {};
3137
3327
  for (const d of dimensionKeys) dims[d] = clamp01(Number(v.perDimension[d]));
3138
- perJudge[v.model] = dims;
3328
+ perJudge[key] = dims;
3139
3329
  for (const d of dimensionKeys) dimAcc[d].push(dims[d]);
3140
3330
  if (!rationale && typeof v.rationale === "string" && v.rationale) rationale = v.rationale;
3141
3331
  }
@@ -3170,7 +3360,127 @@ function aggregateJudgeVerdicts(verdicts, dimensionKeys, weights) {
3170
3360
  maxDisagreement,
3171
3361
  failedJudges,
3172
3362
  costUsd,
3173
- rationale: rationale || "llm-judge"
3363
+ rationale: rationale || "llm-judge",
3364
+ verdicts: [...verdicts]
3365
+ };
3366
+ }
3367
+
3368
+ // src/judge-retry.ts
3369
+ var DEFAULT_MAX_ATTEMPTS = 3;
3370
+ var DEFAULT_TIMEOUT_MS = 3e5;
3371
+ function sleep(ms) {
3372
+ return new Promise((resolve) => setTimeout(resolve, ms));
3373
+ }
3374
+ async function withJudgeRetry(judgeFn, policy = {}) {
3375
+ const maxAttempts = policy.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
3376
+ const timeoutMs = policy.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3377
+ const backoff = policy.backoffMs ?? backoffMs;
3378
+ const isRetryable = policy.isRetryable ?? isTransientLlmError;
3379
+ const models = policy.models && policy.models.length > 0 ? policy.models : [void 0];
3380
+ let totalAttempts = 0;
3381
+ const attemptErrors = [];
3382
+ let lastError;
3383
+ for (const model of models) {
3384
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
3385
+ totalAttempts += 1;
3386
+ const controller = new AbortController();
3387
+ const timer = setTimeout(() => controller.abort(new Error("TimeoutError")), timeoutMs);
3388
+ try {
3389
+ const value = await judgeFn(model, controller.signal);
3390
+ clearTimeout(timer);
3391
+ return {
3392
+ value,
3393
+ succeeded: true,
3394
+ attempts: totalAttempts,
3395
+ modelUsed: model,
3396
+ attemptErrors
3397
+ };
3398
+ } catch (err) {
3399
+ clearTimeout(timer);
3400
+ const errObj = err instanceof Error ? err : new Error(String(err));
3401
+ lastError = errObj;
3402
+ attemptErrors.push({
3403
+ attempt: totalAttempts,
3404
+ model: model ?? "(default)",
3405
+ error: errObj.message
3406
+ });
3407
+ if (!isRetryable(errObj)) {
3408
+ return {
3409
+ value: null,
3410
+ succeeded: false,
3411
+ attempts: totalAttempts,
3412
+ error: errObj,
3413
+ attemptErrors
3414
+ };
3415
+ }
3416
+ if (attempt < maxAttempts - 1) {
3417
+ await sleep(backoff(attempt));
3418
+ }
3419
+ }
3420
+ }
3421
+ }
3422
+ return {
3423
+ value: null,
3424
+ succeeded: false,
3425
+ attempts: totalAttempts,
3426
+ error: lastError,
3427
+ attemptErrors
3428
+ };
3429
+ }
3430
+
3431
+ // src/judge-panel.ts
3432
+ function ensembleJudge(opts) {
3433
+ if (opts.models.length === 0) {
3434
+ throw new Error(`ensembleJudge '${opts.name}': models is empty \u2014 nothing to score with`);
3435
+ }
3436
+ if (opts.dimensions.length === 0) {
3437
+ throw new Error(`ensembleJudge '${opts.name}': dimensions is empty \u2014 nothing to score`);
3438
+ }
3439
+ if (opts.crossFamily !== false) {
3440
+ assertCrossFamily(opts.models);
3441
+ }
3442
+ const scoreOne = async (model, input) => {
3443
+ if (opts.retry) {
3444
+ const outcome = await withJudgeRetry((m) => opts.scoreWith(m, input), {
3445
+ ...opts.retry,
3446
+ models: [model]
3447
+ });
3448
+ if (!outcome.succeeded || outcome.value === null) {
3449
+ return {
3450
+ model,
3451
+ perDimension: null,
3452
+ rationale: outcome.error?.message ?? "judge failed after retries"
3453
+ };
3454
+ }
3455
+ return outcome.value;
3456
+ }
3457
+ try {
3458
+ return await opts.scoreWith(model, input);
3459
+ } catch (err) {
3460
+ return {
3461
+ model,
3462
+ perDimension: null,
3463
+ rationale: err instanceof Error ? err.message : String(err)
3464
+ };
3465
+ }
3466
+ };
3467
+ return {
3468
+ name: opts.name,
3469
+ dimensions: opts.dimensions.map((d) => ({ key: d, description: d })),
3470
+ async score({ artifact, scenario }) {
3471
+ const input = { artifact, scenario };
3472
+ const verdicts = await Promise.all(opts.models.map((model) => scoreOne(model, input)));
3473
+ const agg = aggregateJudgeVerdicts(verdicts, opts.dimensions, opts.weights);
3474
+ const score = {
3475
+ dimensions: agg.perDimension,
3476
+ composite: agg.composite,
3477
+ notes: agg.rationale,
3478
+ maxDisagreement: agg.maxDisagreement,
3479
+ perJudge: agg.perJudge
3480
+ };
3481
+ if (agg.failedJudges.length > 0) score.failedJudges = agg.failedJudges;
3482
+ return score;
3483
+ }
3174
3484
  };
3175
3485
  }
3176
3486
 
@@ -3505,7 +3815,7 @@ function createAxService(provider, apiKey, model) {
3505
3815
  });
3506
3816
  }
3507
3817
  function seededShuffle(items, seed) {
3508
- const rng = mulberry32(hashString(seed));
3818
+ const rng = mulberry322(hashString(seed));
3509
3819
  const out = [...items];
3510
3820
  for (let i = out.length - 1; i > 0; i--) {
3511
3821
  const j = Math.floor(rng() * (i + 1));
@@ -3521,7 +3831,7 @@ function hashString(value) {
3521
3831
  }
3522
3832
  return h >>> 0;
3523
3833
  }
3524
- function mulberry32(seed) {
3834
+ function mulberry322(seed) {
3525
3835
  let a = seed >>> 0;
3526
3836
  return () => {
3527
3837
  a = a + 1831565813 | 0;
@@ -4856,42 +5166,6 @@ function formatScorecardDiff(diff) {
4856
5166
  return lines.join("\n");
4857
5167
  }
4858
5168
 
4859
- // src/series-convergence.ts
4860
- function analyzeSeries(values, options = {}) {
4861
- const window = options.window ?? 5;
4862
- const stableCv = options.stableCv ?? 0.05;
4863
- const driftRun = options.driftRun ?? 3;
4864
- if (values.length < Math.max(2, Math.min(window, 3))) {
4865
- return { state: "insufficient-data", windowMean: 0, windowCv: 0, tailRun: 0, stable: false };
4866
- }
4867
- const tail = values.slice(-window);
4868
- const mean5 = tail.reduce((a, b) => a + b, 0) / tail.length;
4869
- const variance = tail.reduce((acc, v) => acc + (v - mean5) ** 2, 0) / tail.length;
4870
- const stdDev = Math.sqrt(variance);
4871
- const refMean = Math.abs(mean5) > 1e-9 ? Math.abs(mean5) : 1;
4872
- const cv = stdDev / refMean;
4873
- const stable = tail.length >= window && cv <= stableCv;
4874
- let tailRun = 0;
4875
- let direction = 0;
4876
- for (let i = values.length - 1; i > 0; i--) {
4877
- const delta = values[i] - values[i - 1];
4878
- if (delta === 0) break;
4879
- const dir = delta > 0 ? 1 : -1;
4880
- if (direction === 0) direction = dir;
4881
- if (dir !== direction) break;
4882
- tailRun += dir;
4883
- }
4884
- let state;
4885
- if (stable) {
4886
- state = "stabilized";
4887
- } else if (Math.abs(tailRun) >= driftRun) {
4888
- state = tailRun > 0 ? "drifting-up" : "drifting-down";
4889
- } else {
4890
- state = "noisy";
4891
- }
4892
- return { state, windowMean: mean5, windowCv: cv, tailRun, stable };
4893
- }
4894
-
4895
5169
  // src/slo.ts
4896
5170
  function checkSlos(metrics, slos) {
4897
5171
  const results = slos.map((slo) => check(slo, metrics[slo.metric]));
@@ -4978,70 +5252,445 @@ function scoreContinuity(pair, checks, options = {}) {
4978
5252
  if (checks.length === 0) {
4979
5253
  throw new Error("scoreContinuity: at least 1 check required");
4980
5254
  }
4981
- const passThreshold = options.passThreshold ?? 0.8;
4982
- const results = checks.map((c) => {
4983
- const raw = c.score(pair);
4984
- const clamped = Number.isFinite(raw) ? Math.max(0, Math.min(1, raw)) : 0;
4985
- return { id: c.id, description: c.description, score: clamped, pass: clamped >= passThreshold };
4986
- });
4987
- const overallScore = results.reduce((a, r) => a + r.score, 0) / results.length;
4988
- return { results, overallScore, pass: results.every((r) => r.pass) };
5255
+ const passThreshold = options.passThreshold ?? 0.8;
5256
+ const results = checks.map((c) => {
5257
+ const raw = c.score(pair);
5258
+ const clamped = Number.isFinite(raw) ? Math.max(0, Math.min(1, raw)) : 0;
5259
+ return { id: c.id, description: c.description, score: clamped, pass: clamped >= passThreshold };
5260
+ });
5261
+ const overallScore = results.reduce((a, r) => a + r.score, 0) / results.length;
5262
+ return { results, overallScore, pass: results.every((r) => r.pass) };
5263
+ }
5264
+ function keyPreserved(key) {
5265
+ return {
5266
+ id: `preserved(${key})`,
5267
+ description: `"${key}" unchanged from before to after`,
5268
+ score: ({ before, after }) => before[key] !== void 0 && before[key] === after[key] ? 1 : 0
5269
+ };
5270
+ }
5271
+ function collectionPreserved(key, minRatio = 1) {
5272
+ return {
5273
+ id: `collection-preserved(${key})`,
5274
+ description: `"${key}" length \u2265 ${minRatio} \xD7 prior length`,
5275
+ score: ({ before, after }) => {
5276
+ const b = before[key];
5277
+ const a = after[key];
5278
+ if (!Array.isArray(b) || !Array.isArray(a)) return 0;
5279
+ if (b.length === 0) return a.length === 0 ? 1 : 1;
5280
+ return Math.min(1, a.length / (b.length * minRatio));
5281
+ }
5282
+ };
5283
+ }
5284
+ function statusAdvanced(key, progression) {
5285
+ return {
5286
+ id: `status-advanced(${key})`,
5287
+ description: `"${key}" progressed along ${progression.join("\u2192")}`,
5288
+ score: ({ before, after }) => {
5289
+ const bi = progression.indexOf(String(before[key]));
5290
+ const ai2 = progression.indexOf(String(after[key]));
5291
+ if (bi === -1 || ai2 === -1) return 0;
5292
+ return ai2 >= bi ? 1 : 0;
5293
+ }
5294
+ };
5295
+ }
5296
+
5297
+ // src/ui-finding.ts
5298
+ var UI_LENSES = [
5299
+ "consistency",
5300
+ "hierarchy",
5301
+ "layout",
5302
+ "ux-flow",
5303
+ "duplication",
5304
+ "accessibility",
5305
+ "responsive",
5306
+ "states",
5307
+ "content",
5308
+ "interaction",
5309
+ "performance-perceived",
5310
+ "other"
5311
+ ];
5312
+ var UI_FINDING_SEVERITIES = [
5313
+ "critical",
5314
+ "high",
5315
+ "med",
5316
+ "low"
5317
+ ];
5318
+
5319
+ // src/trace-contracts.ts
5320
+ function isSerializedRegex(v) {
5321
+ return typeof v === "object" && v !== null && typeof v.$regex === "string" && typeof v.flags === "string";
5322
+ }
5323
+ function matchText(actual, matcher) {
5324
+ if (typeof actual !== "string") return false;
5325
+ if (typeof matcher === "string") return actual === matcher;
5326
+ if (matcher instanceof RegExp) return matcher.test(actual);
5327
+ return new RegExp(matcher.$regex, matcher.flags).test(actual);
5328
+ }
5329
+ function resolveToolName(span) {
5330
+ if (typeof span.toolName === "string") return span.toolName;
5331
+ const fromAttr = span.attributes?.["tool.name"] ?? span.attributes?.["toolName"];
5332
+ if (typeof fromAttr === "string") return fromAttr;
5333
+ if (span.kind === "tool" && typeof span.name === "string") return span.name;
5334
+ return void 0;
5335
+ }
5336
+ function assertPredicate(value, where) {
5337
+ if (value === null || typeof value !== "object") {
5338
+ throw new ValidationError(`${where}: predicate must be an object, got ${typeof value}`);
5339
+ }
5340
+ const p = value;
5341
+ for (const field of ["name", "tool"]) {
5342
+ const m = p[field];
5343
+ if (m !== void 0 && typeof m !== "string" && !(m instanceof RegExp) && !isSerializedRegex(m)) {
5344
+ throw new ValidationError(`${where}: "${field}" must be string | RegExp | SerializedRegex`);
5345
+ }
5346
+ }
5347
+ if (p.attr !== void 0 && (p.attr === null || typeof p.attr !== "object" || Array.isArray(p.attr))) {
5348
+ throw new ValidationError(`${where}: "attr" must be a plain object`);
5349
+ }
5350
+ if (p.requiresCustom && typeof p.custom !== "function") {
5351
+ throw new ValidationError(
5352
+ `${where}: rule was built with a custom predicate function, which does not survive JSON serialization \u2014 re-attach \`custom\` after deserializing or drop the rule`
5353
+ );
5354
+ }
5355
+ const hasAttr = p.attr !== void 0 && Object.keys(p.attr).length > 0;
5356
+ if (p.name === void 0 && p.tool === void 0 && !hasAttr && typeof p.custom !== "function") {
5357
+ throw new ValidationError(
5358
+ `${where}: empty predicate would match every span \u2014 specify name, tool, attr, or custom`
5359
+ );
5360
+ }
5361
+ }
5362
+ function matchSpan(span, predicate) {
5363
+ assertPredicate(predicate, "matchSpan");
5364
+ if (predicate.name !== void 0 && !matchText(span.name, predicate.name)) return false;
5365
+ if (predicate.tool !== void 0 && !matchText(resolveToolName(span), predicate.tool))
5366
+ return false;
5367
+ if (predicate.attr !== void 0) {
5368
+ for (const [key, expected] of Object.entries(predicate.attr)) {
5369
+ const actual = span.attributes?.[key];
5370
+ if (expected instanceof RegExp || isSerializedRegex(expected)) {
5371
+ if (!matchText(typeof actual === "string" ? actual : void 0, expected)) {
5372
+ return false;
5373
+ }
5374
+ } else if (actual !== expected) {
5375
+ return false;
5376
+ }
5377
+ }
5378
+ }
5379
+ if (predicate.custom !== void 0 && !predicate.custom(span)) return false;
5380
+ return true;
5381
+ }
5382
+ function describeMatcher(m) {
5383
+ if (typeof m === "string") return m;
5384
+ if (m instanceof RegExp) return `/${m.source}/${m.flags}`;
5385
+ return `/${m.$regex}/${m.flags}`;
5386
+ }
5387
+ function describePredicate(p) {
5388
+ const parts = [];
5389
+ if (p.name !== void 0) parts.push(`name=${describeMatcher(p.name)}`);
5390
+ if (p.tool !== void 0) parts.push(`tool=${describeMatcher(p.tool)}`);
5391
+ if (p.attr !== void 0) {
5392
+ for (const [k, v] of Object.entries(p.attr)) {
5393
+ parts.push(
5394
+ `attr.${k}=${v instanceof RegExp || isSerializedRegex(v) ? describeMatcher(v) : JSON.stringify(v)}`
5395
+ );
5396
+ }
5397
+ }
5398
+ if (typeof p.custom === "function") parts.push(`custom=${p.custom.name || "fn"}`);
5399
+ return parts.join(",");
5400
+ }
5401
+ function normalizeMatcher(m) {
5402
+ return m instanceof RegExp ? { $regex: m.source, flags: m.flags } : m;
5403
+ }
5404
+ function normalizePredicate(p, where) {
5405
+ assertPredicate(p, where);
5406
+ const out = {};
5407
+ if (p.name !== void 0) out.name = normalizeMatcher(p.name);
5408
+ if (p.tool !== void 0) out.tool = normalizeMatcher(p.tool);
5409
+ if (p.attr !== void 0) {
5410
+ out.attr = Object.fromEntries(
5411
+ Object.entries(p.attr).map(([k, v]) => [k, v instanceof RegExp ? normalizeMatcher(v) : v])
5412
+ );
5413
+ }
5414
+ if (typeof p.custom === "function") {
5415
+ out.custom = p.custom;
5416
+ out.requiresCustom = true;
5417
+ }
5418
+ return out;
5419
+ }
5420
+ var TraceContractBuilder = class {
5421
+ constructor(name) {
5422
+ this.name = name;
5423
+ }
5424
+ name;
5425
+ rules = [];
5426
+ /** Every span in the trace must satisfy `p`. */
5427
+ always(p, label) {
5428
+ const np = normalizePredicate(p, `traceContract("${this.name}").always`);
5429
+ return this.add({ kind: "always", label: label ?? `always(${describePredicate(np)})`, p: np });
5430
+ }
5431
+ /** No span in the trace may satisfy `p`. */
5432
+ never(p, label) {
5433
+ const np = normalizePredicate(p, `traceContract("${this.name}").never`);
5434
+ return this.add({ kind: "never", label: label ?? `never(${describePredicate(np)})`, p: np });
5435
+ }
5436
+ /** At least one span in the trace must satisfy `p`. */
5437
+ eventually(p, label) {
5438
+ const np = normalizePredicate(p, `traceContract("${this.name}").eventually`);
5439
+ return this.add({
5440
+ kind: "eventually",
5441
+ label: label ?? `eventually(${describePredicate(np)})`,
5442
+ p: np
5443
+ });
5444
+ }
5445
+ /** Every `b`-match must have a strictly earlier `a`-match. */
5446
+ precedes(a, b, label) {
5447
+ const na = normalizePredicate(a, `traceContract("${this.name}").precedes (a)`);
5448
+ const nb = normalizePredicate(b, `traceContract("${this.name}").precedes (b)`);
5449
+ return this.add({
5450
+ kind: "precedes",
5451
+ label: label ?? `precedes(${describePredicate(na)} -> ${describePredicate(nb)})`,
5452
+ a: na,
5453
+ b: nb
5454
+ });
5455
+ }
5456
+ /** Every `p`-match is a violation unless a strictly earlier `prior`-match exists. */
5457
+ neverUnless(p, prior, label) {
5458
+ const np = normalizePredicate(p, `traceContract("${this.name}").neverUnless (p)`);
5459
+ const nprior = normalizePredicate(prior, `traceContract("${this.name}").neverUnless (prior)`);
5460
+ return this.add({
5461
+ kind: "neverUnless",
5462
+ label: label ?? `neverUnless(${describePredicate(np)} unless ${describePredicate(nprior)})`,
5463
+ p: np,
5464
+ prior: nprior
5465
+ });
5466
+ }
5467
+ build() {
5468
+ if (this.rules.length === 0) {
5469
+ throw new ValidationError(
5470
+ `traceContract("${this.name}").build(): no rules \u2014 an empty contract would vacuously pass`
5471
+ );
5472
+ }
5473
+ return { name: this.name, rules: [...this.rules] };
5474
+ }
5475
+ add(rule) {
5476
+ let label = rule.label;
5477
+ let n = 2;
5478
+ while (this.rules.some((r) => r.label === label)) {
5479
+ label = `${rule.label} #${n}`;
5480
+ n += 1;
5481
+ }
5482
+ this.rules.push({ ...rule, label });
5483
+ return this;
5484
+ }
5485
+ };
5486
+ function traceContract(name) {
5487
+ if (typeof name !== "string" || name.length === 0) {
5488
+ throw new ValidationError("traceContract: name must be a non-empty string");
5489
+ }
5490
+ return new TraceContractBuilder(name);
5491
+ }
5492
+ function assertContract(contract) {
5493
+ if (typeof contract?.name !== "string" || contract.name.length === 0) {
5494
+ throw new ValidationError("evaluateTraceContract: contract.name must be a non-empty string");
5495
+ }
5496
+ if (!Array.isArray(contract.rules) || contract.rules.length === 0) {
5497
+ throw new ValidationError(
5498
+ `evaluateTraceContract: contract "${contract.name}" has no rules \u2014 an empty contract would vacuously pass`
5499
+ );
5500
+ }
5501
+ const seen = /* @__PURE__ */ new Set();
5502
+ for (const rule of contract.rules) {
5503
+ const where = `contract "${contract.name}" rule "${rule?.label ?? "<unlabeled>"}"`;
5504
+ if (typeof rule?.label !== "string" || rule.label.length === 0) {
5505
+ throw new ValidationError(`${where}: label must be a non-empty string`);
5506
+ }
5507
+ if (seen.has(rule.label)) {
5508
+ throw new ValidationError(`${where}: duplicate label would collapse per-rule scores`);
5509
+ }
5510
+ seen.add(rule.label);
5511
+ switch (rule.kind) {
5512
+ case "always":
5513
+ case "never":
5514
+ case "eventually":
5515
+ assertPredicate(rule.p, `${where} (p)`);
5516
+ break;
5517
+ case "precedes":
5518
+ assertPredicate(rule.a, `${where} (a)`);
5519
+ assertPredicate(rule.b, `${where} (b)`);
5520
+ break;
5521
+ case "neverUnless":
5522
+ assertPredicate(rule.p, `${where} (p)`);
5523
+ assertPredicate(rule.prior, `${where} (prior)`);
5524
+ break;
5525
+ default:
5526
+ throw new ValidationError(
5527
+ `${where}: unknown rule kind "${String(rule.kind)}"`
5528
+ );
5529
+ }
5530
+ }
5531
+ }
5532
+ function orderSpans(spans) {
5533
+ const timed = spans.filter(
5534
+ (s) => typeof s.startedAt === "number" && Number.isFinite(s.startedAt)
5535
+ ).length;
5536
+ if (timed === spans.length) {
5537
+ return spans.map((span, i) => ({ span, i })).sort((x, y) => x.span.startedAt - y.span.startedAt || x.i - y.i).map((x) => x.span);
5538
+ }
5539
+ if (timed === 0) return [...spans];
5540
+ throw new ValidationError(
5541
+ `evaluateTraceContract: ${timed}/${spans.length} spans carry a finite startedAt \u2014 mixed timestamps make ordering ambiguous; stamp every span or none`
5542
+ );
5543
+ }
5544
+ function spanRef(span, index) {
5545
+ return span.spanId ?? `#${index}`;
5546
+ }
5547
+ function checkGuarded(args) {
5548
+ const out = [];
5549
+ let guardSeen = false;
5550
+ for (let i = 0; i < args.ordered.length; i++) {
5551
+ const span = args.ordered[i];
5552
+ if (!guardSeen && matchSpan(span, args.guarded)) {
5553
+ out.push({ rule: args.label, spanId: span.spanId, detail: args.detail(spanRef(span, i)) });
5554
+ }
5555
+ if (matchSpan(span, args.guard)) guardSeen = true;
5556
+ }
5557
+ return out;
5558
+ }
5559
+ function checkRule(rule, ordered) {
5560
+ switch (rule.kind) {
5561
+ case "always": {
5562
+ const out = [];
5563
+ for (let i = 0; i < ordered.length; i++) {
5564
+ const span = ordered[i];
5565
+ if (!matchSpan(span, rule.p)) {
5566
+ out.push({
5567
+ rule: rule.label,
5568
+ spanId: span.spanId,
5569
+ detail: `span ${spanRef(span, i)} ("${span.name ?? ""}") fails always(${describePredicate(rule.p)})`
5570
+ });
5571
+ }
5572
+ }
5573
+ return out;
5574
+ }
5575
+ case "never": {
5576
+ const out = [];
5577
+ for (let i = 0; i < ordered.length; i++) {
5578
+ const span = ordered[i];
5579
+ if (matchSpan(span, rule.p)) {
5580
+ out.push({
5581
+ rule: rule.label,
5582
+ spanId: span.spanId,
5583
+ detail: `span ${spanRef(span, i)} ("${span.name ?? ""}") matches never(${describePredicate(rule.p)})`
5584
+ });
5585
+ }
5586
+ }
5587
+ return out;
5588
+ }
5589
+ case "eventually": {
5590
+ const hit = ordered.some((span) => matchSpan(span, rule.p));
5591
+ return hit ? [] : [
5592
+ {
5593
+ rule: rule.label,
5594
+ detail: `no span matches eventually(${describePredicate(rule.p)}) over ${ordered.length} span(s)`
5595
+ }
5596
+ ];
5597
+ }
5598
+ case "precedes":
5599
+ return checkGuarded({
5600
+ label: rule.label,
5601
+ ordered,
5602
+ guard: rule.a,
5603
+ guarded: rule.b,
5604
+ detail: (ref) => `span ${ref} matches ${describePredicate(rule.b)} with no earlier ${describePredicate(rule.a)} match`
5605
+ });
5606
+ case "neverUnless":
5607
+ return checkGuarded({
5608
+ label: rule.label,
5609
+ ordered,
5610
+ guard: rule.prior,
5611
+ guarded: rule.p,
5612
+ detail: (ref) => `span ${ref} matches ${describePredicate(rule.p)} with no earlier ${describePredicate(rule.prior)} match`
5613
+ });
5614
+ }
4989
5615
  }
4990
- function keyPreserved(key) {
5616
+ function evaluateTraceContract(contract, spans) {
5617
+ assertContract(contract);
5618
+ const ordered = orderSpans(spans);
5619
+ const scores2 = {};
5620
+ const violations = [];
5621
+ for (const rule of contract.rules) {
5622
+ const ruleViolations = checkRule(rule, ordered);
5623
+ scores2[rule.label] = ruleViolations.length === 0 ? 1 : 0;
5624
+ violations.push(...ruleViolations);
5625
+ }
5626
+ const ruleCount = contract.rules.length;
5627
+ const passCount = Object.values(scores2).filter((s) => s === 1).length;
4991
5628
  return {
4992
- id: `preserved(${key})`,
4993
- description: `"${key}" unchanged from before to after`,
4994
- score: ({ before, after }) => before[key] !== void 0 && before[key] === after[key] ? 1 : 0
5629
+ contract: contract.name,
5630
+ valid: passCount === ruleCount,
5631
+ score: passCount / ruleCount,
5632
+ scores: scores2,
5633
+ violations,
5634
+ notes: `${passCount}/${ruleCount} rules passed`
4995
5635
  };
4996
5636
  }
4997
- function collectionPreserved(key, minRatio = 1) {
4998
- return {
4999
- id: `collection-preserved(${key})`,
5000
- description: `"${key}" length \u2265 ${minRatio} \xD7 prior length`,
5001
- score: ({ before, after }) => {
5002
- const b = before[key];
5003
- const a = after[key];
5004
- if (!Array.isArray(b) || !Array.isArray(a)) return 0;
5005
- if (b.length === 0) return a.length === 0 ? 1 : 1;
5006
- return Math.min(1, a.length / (b.length * minRatio));
5007
- }
5008
- };
5637
+ function checkTraceContracts(spans, contracts) {
5638
+ if (contracts.length === 0) {
5639
+ throw new ValidationError(
5640
+ "checkTraceContracts: empty contract list would vacuously pass \u2014 supply at least one contract"
5641
+ );
5642
+ }
5643
+ const verdicts = contracts.map((c) => evaluateTraceContract(c, spans));
5644
+ return { verdicts, allValid: verdicts.every((v) => v.valid) };
5009
5645
  }
5010
- function statusAdvanced(key, progression) {
5646
+ function contractJudge(contracts, opts) {
5647
+ if (contracts.length === 0) {
5648
+ throw new ValidationError("contractJudge: at least one contract required");
5649
+ }
5650
+ for (const c of contracts) assertContract(c);
5651
+ const names = /* @__PURE__ */ new Set();
5652
+ for (const c of contracts) {
5653
+ if (names.has(c.name)) {
5654
+ throw new ValidationError(
5655
+ `contractJudge: duplicate contract name "${c.name}" would collapse judge dimensions`
5656
+ );
5657
+ }
5658
+ names.add(c.name);
5659
+ }
5660
+ if (typeof opts?.spans !== "function") {
5661
+ throw new ValidationError("contractJudge: opts.spans extraction function is required");
5662
+ }
5663
+ const dimensions = contracts.map((c) => ({
5664
+ key: c.name,
5665
+ description: c.rules.map((r) => r.label).join("; ")
5666
+ }));
5011
5667
  return {
5012
- id: `status-advanced(${key})`,
5013
- description: `"${key}" progressed along ${progression.join("\u2192")}`,
5014
- score: ({ before, after }) => {
5015
- const bi = progression.indexOf(String(before[key]));
5016
- const ai2 = progression.indexOf(String(after[key]));
5017
- if (bi === -1 || ai2 === -1) return 0;
5018
- return ai2 >= bi ? 1 : 0;
5668
+ name: opts.name ?? "trace-contracts",
5669
+ dimensions,
5670
+ score({ artifact, scenario }) {
5671
+ const spans = opts.spans({ artifact, scenario });
5672
+ if (!Array.isArray(spans)) {
5673
+ throw new ValidationError(
5674
+ `contractJudge: spans() must return a span array, got ${typeof spans}`
5675
+ );
5676
+ }
5677
+ const { verdicts } = checkTraceContracts(spans, contracts);
5678
+ const dims = {};
5679
+ const violations = [];
5680
+ for (const v of verdicts) {
5681
+ dims[v.contract] = v.score;
5682
+ violations.push(...v.violations);
5683
+ }
5684
+ const composite = verdicts.reduce((acc, v) => acc + v.score, 0) / verdicts.length;
5685
+ return {
5686
+ dimensions: dims,
5687
+ composite,
5688
+ notes: violations.length === 0 ? "all trace contracts satisfied" : violations.slice(0, 8).map((x) => `${x.rule}: ${x.detail}`).join("\n")
5689
+ };
5019
5690
  }
5020
5691
  };
5021
5692
  }
5022
5693
 
5023
- // src/ui-finding.ts
5024
- var UI_LENSES = [
5025
- "consistency",
5026
- "hierarchy",
5027
- "layout",
5028
- "ux-flow",
5029
- "duplication",
5030
- "accessibility",
5031
- "responsive",
5032
- "states",
5033
- "content",
5034
- "interaction",
5035
- "performance-perceived",
5036
- "other"
5037
- ];
5038
- var UI_FINDING_SEVERITIES = [
5039
- "critical",
5040
- "high",
5041
- "med",
5042
- "low"
5043
- ];
5044
-
5045
5694
  // src/behavior-dsl.ts
5046
5695
  var BehaviorAssertion = class {
5047
5696
  constructor(store, runId) {
@@ -5109,6 +5758,25 @@ var BehaviorAssertion = class {
5109
5758
  }
5110
5759
  };
5111
5760
  }
5761
+ /** Evaluate a finite-trace temporal contract (`traceContract(...)`) over
5762
+ * this run's span sequence. See `trace-contracts.ts` for the operators. */
5763
+ toSatisfyContract(contract) {
5764
+ return {
5765
+ label: `agent(${this.runId}).toSatisfyContract(${contract.name})`,
5766
+ check: async () => {
5767
+ const spans = await this.store.spans({ runId: this.runId });
5768
+ const verdict = evaluateTraceContract(contract, spans);
5769
+ if (verdict.valid) {
5770
+ return { ok: true, detail: `contract "${contract.name}": ${verdict.notes}` };
5771
+ }
5772
+ return {
5773
+ ok: false,
5774
+ detail: verdict.violations.map((v) => `${v.rule}: ${v.detail}`).join("; "),
5775
+ evidence: verdict.violations.find((v) => v.spanId)?.spanId
5776
+ };
5777
+ }
5778
+ };
5779
+ }
5112
5780
  toNeverCall(toolName) {
5113
5781
  return {
5114
5782
  label: `agent(${this.runId}).toNeverCall(${toolName})`,
@@ -5711,90 +6379,6 @@ async function promptBisect(options) {
5711
6379
  };
5712
6380
  }
5713
6381
 
5714
- // src/counterfactual.ts
5715
- async function runCounterfactual(store, originalRunId, mutation, runner) {
5716
- const originalRun = await store.getRun(originalRunId);
5717
- if (!originalRun) throw new NotFoundError(`counterfactual: run ${originalRunId} not found`);
5718
- const trajectory = await buildTrajectory(store, originalRunId);
5719
- if (mutation.at < 0 || mutation.at >= trajectory.steps.length) {
5720
- throw new ValidationError(
5721
- `counterfactual: mutation.at=${mutation.at} out of range [0, ${trajectory.steps.length})`
5722
- );
5723
- }
5724
- const targetStep = trajectory.steps[mutation.at];
5725
- const mutatedStep = applyMutation(targetStep, mutation);
5726
- const cfEmitter = new TraceEmitter(store);
5727
- await cfEmitter.startRun({
5728
- scenarioId: originalRun.scenarioId,
5729
- variantId: originalRun.variantId ? `${originalRun.variantId}+cf:${mutation.kind}@${mutation.at}` : `cf:${mutation.kind}@${mutation.at}`,
5730
- projectId: originalRun.projectId,
5731
- parentRunId: originalRunId,
5732
- layer: "meta",
5733
- tags: { counterfactual: "true", mutationKind: mutation.kind, mutationAt: String(mutation.at) }
5734
- });
5735
- await runner.executeFrom(
5736
- {
5737
- originalRunId,
5738
- originalTrajectory: trajectory,
5739
- prefix: trajectory.steps.slice(0, mutation.at),
5740
- mutation,
5741
- mutatedStep
5742
- },
5743
- cfEmitter
5744
- );
5745
- const counterfactual = await store.getRun(cfEmitter.runId);
5746
- const delta = {
5747
- originalOutcomeScore: originalRun.outcome?.score ?? null,
5748
- counterfactualOutcomeScore: counterfactual?.outcome?.score ?? null,
5749
- deltaScore: originalRun.outcome?.score !== void 0 && counterfactual?.outcome?.score !== void 0 ? counterfactual.outcome.score - originalRun.outcome.score : null
5750
- };
5751
- return { counterfactualRunId: cfEmitter.runId, originalRunId, mutation, delta };
5752
- }
5753
- function applyMutation(step, mutation) {
5754
- if (mutation.kind === "swap-model" && step.span.kind === "llm") {
5755
- const llm = step.span;
5756
- return { ...step, span: { ...llm, model: mutation.newModel } };
5757
- }
5758
- if (mutation.kind === "swap-tool-result" && step.span.kind === "tool") {
5759
- const tool = step.span;
5760
- return { ...step, span: { ...tool, result: mutation.newResult } };
5761
- }
5762
- if (mutation.kind === "inject-system-message" && step.span.kind === "llm") {
5763
- const llm = step.span;
5764
- return {
5765
- ...step,
5766
- span: {
5767
- ...llm,
5768
- messages: [{ role: "system", content: mutation.content }, ...llm.messages]
5769
- }
5770
- };
5771
- }
5772
- if (mutation.kind === "custom") return mutation.apply(step);
5773
- return step;
5774
- }
5775
- function attributeCounterfactuals(results) {
5776
- const grouped = /* @__PURE__ */ new Map();
5777
- for (const r of results) {
5778
- const arr = grouped.get(r.mutation.kind) ?? [];
5779
- arr.push(r);
5780
- grouped.set(r.mutation.kind, arr);
5781
- }
5782
- const out = [];
5783
- for (const [kind, items] of grouped) {
5784
- const deltas = items.map((i) => i.delta.deltaScore).filter((d) => typeof d === "number");
5785
- if (deltas.length === 0) continue;
5786
- const meanAbs = deltas.reduce((a, b) => a + Math.abs(b), 0) / deltas.length;
5787
- const meanSigned = deltas.reduce((a, b) => a + b, 0) / deltas.length;
5788
- out.push({
5789
- mutationKind: kind,
5790
- n: deltas.length,
5791
- meanAbsDelta: meanAbs,
5792
- meanSignedDelta: meanSigned
5793
- });
5794
- }
5795
- return out.sort((a, b) => b.meanAbsDelta - a.meanAbsDelta);
5796
- }
5797
-
5798
6382
  // src/cross-trace-diff.ts
5799
6383
  async function crossTraceDiff(store, runA, runB, options = {}) {
5800
6384
  const [a, b] = await Promise.all([buildTrajectory(store, runA), buildTrajectory(store, runB)]);
@@ -6986,14 +7570,18 @@ function aggregate(layers, results, startedAt, startedAtMs) {
6986
7570
  }
6987
7571
  }
6988
7572
  const finishedAtMs = Date.now();
7573
+ const allPass = ranAnyScoredLayer && !anyScoredLayerFailed && failCount === 0 && errorCount === 0;
7574
+ const blendedScore = scoredWeightSum > 0 ? scoredWeightedTotal / scoredWeightSum : 0;
6989
7575
  return {
6990
7576
  layers: results,
6991
7577
  passCount,
6992
7578
  failCount,
6993
7579
  skippedCount,
6994
7580
  errorCount,
6995
- allPass: ranAnyScoredLayer && !anyScoredLayerFailed && failCount === 0 && errorCount === 0,
6996
- blendedScore: scoredWeightSum > 0 ? scoredWeightedTotal / scoredWeightSum : 0,
7581
+ allPass,
7582
+ blendedScore,
7583
+ valid: allPass,
7584
+ score: blendedScore,
6997
7585
  durationMs: finishedAtMs - startedAtMs,
6998
7586
  startedAt,
6999
7587
  finishedAt: new Date(finishedAtMs).toISOString()
@@ -8208,69 +8796,6 @@ function fmt(x) {
8208
8796
  return x.toFixed(4);
8209
8797
  }
8210
8798
 
8211
- // src/judge-retry.ts
8212
- var DEFAULT_MAX_ATTEMPTS = 3;
8213
- var DEFAULT_TIMEOUT_MS = 3e5;
8214
- function sleep(ms) {
8215
- return new Promise((resolve) => setTimeout(resolve, ms));
8216
- }
8217
- async function withJudgeRetry(judgeFn, policy = {}) {
8218
- const maxAttempts = policy.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
8219
- const timeoutMs = policy.timeoutMs ?? DEFAULT_TIMEOUT_MS;
8220
- const backoff = policy.backoffMs ?? backoffMs;
8221
- const isRetryable = policy.isRetryable ?? isTransientLlmError;
8222
- const models = policy.models && policy.models.length > 0 ? policy.models : [void 0];
8223
- let totalAttempts = 0;
8224
- const attemptErrors = [];
8225
- let lastError;
8226
- for (const model of models) {
8227
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
8228
- totalAttempts += 1;
8229
- const controller = new AbortController();
8230
- const timer = setTimeout(() => controller.abort(new Error("TimeoutError")), timeoutMs);
8231
- try {
8232
- const value = await judgeFn(model, controller.signal);
8233
- clearTimeout(timer);
8234
- return {
8235
- value,
8236
- succeeded: true,
8237
- attempts: totalAttempts,
8238
- modelUsed: model,
8239
- attemptErrors
8240
- };
8241
- } catch (err) {
8242
- clearTimeout(timer);
8243
- const errObj = err instanceof Error ? err : new Error(String(err));
8244
- lastError = errObj;
8245
- attemptErrors.push({
8246
- attempt: totalAttempts,
8247
- model: model ?? "(default)",
8248
- error: errObj.message
8249
- });
8250
- if (!isRetryable(errObj)) {
8251
- return {
8252
- value: null,
8253
- succeeded: false,
8254
- attempts: totalAttempts,
8255
- error: errObj,
8256
- attemptErrors
8257
- };
8258
- }
8259
- if (attempt < maxAttempts - 1) {
8260
- await sleep(backoff(attempt));
8261
- }
8262
- }
8263
- }
8264
- }
8265
- return {
8266
- value: null,
8267
- succeeded: false,
8268
- attempts: totalAttempts,
8269
- error: lastError,
8270
- attemptErrors
8271
- };
8272
- }
8273
-
8274
8799
  // src/orthogonality.ts
8275
8800
  function passOrthogonality(input) {
8276
8801
  const passes = input.passes;
@@ -8649,6 +9174,7 @@ function traceJudgeEnsemble(judges, judgeNames, opts) {
8649
9174
  });
8650
9175
  try {
8651
9176
  const allScores = [];
9177
+ let failedJudges = 0;
8652
9178
  for (let i = 0; i < judges.length; i++) {
8653
9179
  const judge = judges[i];
8654
9180
  const name = judgeNames[i] ?? `judge_${i}`;
@@ -8656,8 +9182,13 @@ function traceJudgeEnsemble(judges, judgeNames, opts) {
8656
9182
  emitter: opts.emitter,
8657
9183
  parentSpanId: ensembleSpan.span.spanId
8658
9184
  });
8659
- const scores2 = await tracedFn(tc, input);
8660
- allScores.push(...scores2);
9185
+ try {
9186
+ const scores2 = await tracedFn(tc, input);
9187
+ allScores.push(...scores2);
9188
+ } catch (err) {
9189
+ if (!(err instanceof JudgeParseError)) throw err;
9190
+ failedJudges++;
9191
+ }
8661
9192
  }
8662
9193
  const composite = allScores.length > 0 ? allScores.reduce((sum3, s) => sum3 + s.score, 0) / allScores.length : 0;
8663
9194
  await ensembleSpan.end({
@@ -8665,6 +9196,7 @@ function traceJudgeEnsemble(judges, judgeNames, opts) {
8665
9196
  "judge.ensemble_size": judges.length,
8666
9197
  "judge.composite_score": composite,
8667
9198
  "judge.total_dimensions": allScores.length,
9199
+ "judge.failed_judges": failedJudges,
8668
9200
  "eval.phase": "judge"
8669
9201
  }
8670
9202
  });
@@ -9094,8 +9626,256 @@ function applyDomainPatch(p, sectionId, newBody) {
9094
9626
  function sectionHash(section) {
9095
9627
  return surfaceContentHash(JSON.stringify({ title: section.title, body: section.body }));
9096
9628
  }
9629
+
9630
+ // src/cost-report.ts
9631
+ function costReport(ledger) {
9632
+ const summary = ledger.summary();
9633
+ const perModel = /* @__PURE__ */ new Map();
9634
+ for (const entry of ledger.list()) {
9635
+ const roll = perModel.get(entry.model) ?? {
9636
+ model: entry.model,
9637
+ usd: 0,
9638
+ entries: 0,
9639
+ unpriced: false
9640
+ };
9641
+ roll.usd += entry.costUsd;
9642
+ roll.entries += 1;
9643
+ if (entry.costUnknown) roll.unpriced = true;
9644
+ perModel.set(entry.model, roll);
9645
+ }
9646
+ return {
9647
+ perChannel: summary.byChannel,
9648
+ total: {
9649
+ usd: summary.totalCostUsd,
9650
+ unknownEntries: summary.byChannel.reduce((sum3, c) => sum3 + c.unpricedCalls, 0)
9651
+ },
9652
+ perModel: [...perModel.values()].sort((a, b) => a.model.localeCompare(b.model))
9653
+ };
9654
+ }
9655
+ function attachCostToReport(report, ledger) {
9656
+ if ("cost" in report) {
9657
+ throw new ValidationError(
9658
+ "attachCostToReport: report already has a 'cost' key \u2014 refusing to overwrite an existing stamp"
9659
+ );
9660
+ }
9661
+ return { ...report, cost: costReport(ledger) };
9662
+ }
9663
+
9664
+ // src/model-seats.ts
9665
+ var seatPresets = {
9666
+ economy: {
9667
+ worker: "kimi-k2.6",
9668
+ judges: ["kimi-k2.6", "deepseek-v4-pro", "gpt-4.1-mini"],
9669
+ analyst: "gpt-4.1-mini",
9670
+ reflection: "gpt-4.1-mini",
9671
+ verifier: "deepseek-v4-pro"
9672
+ },
9673
+ frontier: {}
9674
+ };
9675
+ var SeatUnsetError = class extends ConfigError {
9676
+ constructor(seat) {
9677
+ super(
9678
+ `ModelSeats: seat '${seat}' is unset and no fallback was given \u2014 name a model explicitly (a model id is a budget decision, never a silent default)`
9679
+ );
9680
+ this.seat = seat;
9681
+ }
9682
+ seat;
9683
+ };
9684
+ function resolveSeat(seats, seat, fallback) {
9685
+ const value = seats[seat];
9686
+ if (seat === "judges") {
9687
+ if (value !== void 0 && !Array.isArray(value)) {
9688
+ throw new ValidationError(`ModelSeats: seat 'judges' must be a string[], got ${typeof value}`);
9689
+ }
9690
+ const models = Array.isArray(value) ? value : [];
9691
+ if (models.length > 0) {
9692
+ const blank = models.findIndex((m) => typeof m !== "string" || m.trim() === "");
9693
+ if (blank >= 0) {
9694
+ throw new ValidationError(
9695
+ `ModelSeats: judges[${blank}] is blank \u2014 every panel model must be a non-empty id`
9696
+ );
9697
+ }
9698
+ return [...models];
9699
+ }
9700
+ } else {
9701
+ if (value !== void 0 && typeof value !== "string") {
9702
+ throw new ValidationError(`ModelSeats: seat '${seat}' must be a string, got ${typeof value}`);
9703
+ }
9704
+ if (typeof value === "string" && value.trim() !== "") return value;
9705
+ }
9706
+ if (fallback !== void 0) {
9707
+ if (fallback.trim() === "") {
9708
+ throw new ValidationError(`ModelSeats: fallback for seat '${seat}' is blank`);
9709
+ }
9710
+ return seat === "judges" ? [fallback] : fallback;
9711
+ }
9712
+ throw new SeatUnsetError(seat);
9713
+ }
9714
+
9715
+ // src/verdict-cache.ts
9716
+ import { createHash } from "crypto";
9717
+ import { appendFileSync as appendFileSync3, existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
9718
+ function canonicalizeAt(value, path) {
9719
+ if (value === null) return "null";
9720
+ switch (typeof value) {
9721
+ case "boolean":
9722
+ return value ? "true" : "false";
9723
+ case "number":
9724
+ if (!Number.isFinite(value)) {
9725
+ throw new Error(
9726
+ `canonicalJson: non-finite number (${value}) at ${path} \u2014 ambiguity is an error, not a coercion`
9727
+ );
9728
+ }
9729
+ return JSON.stringify(value);
9730
+ case "string":
9731
+ return JSON.stringify(value);
9732
+ case "undefined":
9733
+ case "function":
9734
+ case "symbol":
9735
+ throw new Error(
9736
+ `canonicalJson: ${typeof value} at ${path} \u2014 ambiguity is an error, not a coercion`
9737
+ );
9738
+ case "bigint":
9739
+ throw new Error(`canonicalJson: bigint at ${path} \u2014 not representable in JSON`);
9740
+ case "object":
9741
+ break;
9742
+ }
9743
+ const obj = value;
9744
+ if (typeof obj["toJSON"] === "function") {
9745
+ return canonicalizeAt(obj.toJSON(), path);
9746
+ }
9747
+ if (Array.isArray(obj)) {
9748
+ return `[${obj.map((item, i) => canonicalizeAt(item, `${path}[${i}]`)).join(",")}]`;
9749
+ }
9750
+ if (obj instanceof Map || obj instanceof Set) {
9751
+ throw new Error(
9752
+ `canonicalJson: ${obj instanceof Map ? "Map" : "Set"} at ${path} \u2014 would serialize as '{}'; convert to a plain object/array first`
9753
+ );
9754
+ }
9755
+ const keys = Object.keys(obj).sort();
9756
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalizeAt(obj[k], `${path}.${k}`)}`);
9757
+ return `{${parts.join(",")}}`;
9758
+ }
9759
+ function canonicalJson(value) {
9760
+ return canonicalizeAt(value, "$");
9761
+ }
9762
+ function contentHash(value) {
9763
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
9764
+ }
9765
+ function inMemoryVerdictCache() {
9766
+ const entries = /* @__PURE__ */ new Map();
9767
+ return {
9768
+ get: (key) => entries.get(key),
9769
+ set: (key, score) => {
9770
+ entries.set(key, score);
9771
+ }
9772
+ };
9773
+ }
9774
+ function parseCacheLine(line, path, lineNo) {
9775
+ let parsed;
9776
+ try {
9777
+ parsed = JSON.parse(line);
9778
+ } catch (err) {
9779
+ throw new Error(
9780
+ `fileVerdictCache: corrupt JSONL at ${path}:${lineNo} \u2014 ${err instanceof Error ? err.message : String(err)}`
9781
+ );
9782
+ }
9783
+ const rec = parsed;
9784
+ if (typeof rec !== "object" || rec === null || typeof rec.key !== "string" || typeof rec.score !== "object" || rec.score === null || typeof rec.score.composite !== "number" || typeof rec.score.dimensions !== "object") {
9785
+ throw new Error(
9786
+ `fileVerdictCache: invalid record shape at ${path}:${lineNo} \u2014 expected {key, score:{dimensions, composite, notes}}`
9787
+ );
9788
+ }
9789
+ return rec;
9790
+ }
9791
+ function fileVerdictCache(path) {
9792
+ const entries = /* @__PURE__ */ new Map();
9793
+ if (existsSync5(path)) {
9794
+ const lines = readFileSync6(path, "utf8").split("\n");
9795
+ for (let i = 0; i < lines.length; i++) {
9796
+ const line = lines[i];
9797
+ if (line === void 0 || line.trim() === "") continue;
9798
+ const rec = parseCacheLine(line, path, i + 1);
9799
+ entries.set(rec.key, rec.score);
9800
+ }
9801
+ }
9802
+ return {
9803
+ get: (key) => entries.get(key),
9804
+ set: (key, score) => {
9805
+ appendFileSync3(path, `${JSON.stringify({ key, score })}
9806
+ `, "utf8");
9807
+ entries.set(key, score);
9808
+ }
9809
+ };
9810
+ }
9811
+ function cachedJudge(judge, store, options) {
9812
+ if (typeof options.judgeVersion !== "string" || options.judgeVersion.trim() === "") {
9813
+ throw new Error("cachedJudge: judgeVersion is required and must be a non-empty string");
9814
+ }
9815
+ const stats = { hits: 0, misses: 0 };
9816
+ const wrapped = {
9817
+ name: judge.name,
9818
+ dimensions: judge.dimensions,
9819
+ async score(input) {
9820
+ const key = contentHash({
9821
+ artifact: canonicalJson(input.artifact),
9822
+ scenarioId: input.scenario.id,
9823
+ judgeName: judge.name,
9824
+ dimensions: judge.dimensions,
9825
+ judgeVersion: options.judgeVersion
9826
+ });
9827
+ const cached = await store.get(key);
9828
+ if (cached !== void 0) {
9829
+ stats.hits += 1;
9830
+ return cached;
9831
+ }
9832
+ const score = await judge.score(input);
9833
+ await store.set(key, score);
9834
+ stats.misses += 1;
9835
+ return score;
9836
+ },
9837
+ stats: () => ({ ...stats })
9838
+ };
9839
+ if (judge.appliesTo) wrapped.appliesTo = judge.appliesTo;
9840
+ return wrapped;
9841
+ }
9842
+
9843
+ // src/attestation.ts
9844
+ var ATTESTATION_ALGORITHM = "sha256/canonical-json";
9845
+ function attest(report, provenance) {
9846
+ return {
9847
+ reportHash: contentHash(report),
9848
+ provenance,
9849
+ algorithm: ATTESTATION_ALGORITHM
9850
+ };
9851
+ }
9852
+ function verifyAttestation(report, attested) {
9853
+ if (attested.algorithm !== ATTESTATION_ALGORITHM) {
9854
+ return {
9855
+ valid: false,
9856
+ reason: `unknown algorithm '${attested.algorithm}' \u2014 this verifier only checks '${ATTESTATION_ALGORITHM}'`
9857
+ };
9858
+ }
9859
+ let recomputed;
9860
+ try {
9861
+ recomputed = contentHash(report);
9862
+ } catch (err) {
9863
+ return {
9864
+ valid: false,
9865
+ reason: `report is not canonicalizable: ${err instanceof Error ? err.message : String(err)}`
9866
+ };
9867
+ }
9868
+ if (recomputed !== attested.reportHash) {
9869
+ return {
9870
+ valid: false,
9871
+ reason: `report hash mismatch: attested ${attested.reportHash}, recomputed ${recomputed}`
9872
+ };
9873
+ }
9874
+ return { valid: true };
9875
+ }
9097
9876
  export {
9098
9877
  AGENT_PROFILE_KINDS,
9878
+ ATTESTATION_ALGORITHM,
9099
9879
  AgentDriver,
9100
9880
  AgentEvalError,
9101
9881
  AgentProfileCellValidationError,
@@ -9150,6 +9930,7 @@ export {
9150
9930
  InMemoryTraceStore,
9151
9931
  InMemoryWorkspaceInspector,
9152
9932
  JudgeError,
9933
+ JudgeParseError,
9153
9934
  JudgeRunner,
9154
9935
  KNOWLEDGE_GAP_KIND_SPEC,
9155
9936
  KNOWLEDGE_POISONING_KIND_SPEC,
@@ -9182,6 +9963,7 @@ export {
9182
9963
  SKILL_USAGE_ANALYST,
9183
9964
  SandboxHarness,
9184
9965
  ScenarioRegistry,
9966
+ SeatUnsetError,
9185
9967
  SingleBackendError,
9186
9968
  SkillUsageAnalyst,
9187
9969
  SpanNotFoundError,
@@ -9192,6 +9974,7 @@ export {
9192
9974
  TRACE_ANALYST_TRUNCATION_MARKER_PREFIX,
9193
9975
  TRACE_SCHEMA_VERSION,
9194
9976
  TokenCounter,
9977
+ TraceContractBuilder,
9195
9978
  TraceEmitter,
9196
9979
  TraceFileMissingError,
9197
9980
  TraceNotFoundError,
@@ -9221,12 +10004,15 @@ export {
9221
10004
  assertLlmRoute,
9222
10005
  assertModelsServed,
9223
10006
  assertRealBackend,
10007
+ assertRecordIntegrity,
9224
10008
  assertReleaseConfidence,
9225
10009
  assertRunAgentProfileCell,
9226
10010
  assertRunCaptured,
9227
10011
  assertSingleBackend,
9228
10012
  assignFeedbackSplit,
9229
10013
  assignHeldOutTag,
10014
+ attachCostToReport,
10015
+ attest,
9230
10016
  attributeCounterfactuals,
9231
10017
  backoffMs,
9232
10018
  deterministicSplit as benchmarkDeterministicSplit,
@@ -9248,17 +10034,21 @@ export {
9248
10034
  buildTraceInsightPrompt,
9249
10035
  buildTrajectory,
9250
10036
  byteLengthRange,
10037
+ cachedJudge,
9251
10038
  calibrateJudge,
9252
10039
  calibrateJudgeContinuous,
9253
10040
  callLlm,
9254
10041
  callLlmJson,
9255
10042
  canaryLeakView,
10043
+ canonicalJson,
9256
10044
  canonicalize,
9257
10045
  captureFetchToRawSink,
9258
10046
  causalAttribution,
9259
10047
  checkBehavioralCanary,
9260
10048
  checkCanaries,
10049
+ checkRecordIntegrity,
9261
10050
  checkSlos,
10051
+ checkTraceContracts,
9262
10052
  clamp01,
9263
10053
  classifyEuAiRisk,
9264
10054
  classifyFailure,
@@ -9272,6 +10062,7 @@ export {
9272
10062
  compareReferenceReplay,
9273
10063
  compareToBaseline,
9274
10064
  compilerJudge,
10065
+ completionVerdict,
9275
10066
  composeParsers,
9276
10067
  composeValidators,
9277
10068
  computeExperimentStats,
@@ -9280,7 +10071,9 @@ export {
9280
10071
  computeTraceMetrics,
9281
10072
  confidenceInterval,
9282
10073
  containsAll,
10074
+ contentHash,
9283
10075
  continuousAgreement,
10076
+ contractJudge,
9284
10077
  controlFailureClassFromVerification,
9285
10078
  controlRunToFeedbackTrajectory,
9286
10079
  controlRunToRunRecord,
@@ -9288,6 +10081,7 @@ export {
9288
10081
  corpusInterRaterAgreement,
9289
10082
  corpusInterRaterAgreementFromJudgeScores,
9290
10083
  costForUsage,
10084
+ costReport,
9291
10085
  createAnalystAi,
9292
10086
  createAntiSlopJudge,
9293
10087
  createChatClient,
@@ -9326,6 +10120,8 @@ export {
9326
10120
  distillPlaybook,
9327
10121
  domainEvidencePattern,
9328
10122
  dominates,
10123
+ eProcess,
10124
+ ensembleJudge,
9329
10125
  estimateCost,
9330
10126
  estimateTokens,
9331
10127
  euAiActReport,
@@ -9335,7 +10131,9 @@ export {
9335
10131
  evaluateInterimReleaseConfidence,
9336
10132
  evaluateOracles,
9337
10133
  evaluateReleaseConfidence,
10134
+ evaluateTraceContract,
9338
10135
  executeScenario,
10136
+ expandMatrix,
9339
10137
  expectAgent,
9340
10138
  exportRewardModel,
9341
10139
  exportRunAsOtlp,
@@ -9354,6 +10152,7 @@ export {
9354
10152
  fileContains,
9355
10153
  fileExists,
9356
10154
  fileExperimentStore,
10155
+ fileVerdictCache,
9357
10156
  findAutoMatchNoExpectation,
9358
10157
  findConstructorCwdDropped,
9359
10158
  findFallbackToPass,
@@ -9369,6 +10168,7 @@ export {
9369
10168
  formatFindings,
9370
10169
  formatScorecardDiff,
9371
10170
  gainHistogram,
10171
+ gatePerf,
9372
10172
  ghCliClient,
9373
10173
  gitProvenanceReader,
9374
10174
  precision as goldenPrecision,
@@ -9386,6 +10186,7 @@ export {
9386
10186
  inMemoryReferenceReplayStore,
9387
10187
  inMemoryReviewStore,
9388
10188
  inMemoryRunRecordBackend,
10189
+ inMemoryVerdictCache,
9389
10190
  inferDomainKeywords,
9390
10191
  inferOtlpKind,
9391
10192
  interRaterReliability,
@@ -9420,13 +10221,16 @@ export {
9420
10221
  loadScorerFromGrader,
9421
10222
  localCommandRunner,
9422
10223
  lowercaseMutator,
10224
+ makeEvalTools,
9423
10225
  makeFinding,
9424
10226
  mannWhitneyU,
9425
10227
  matchGoldens,
10228
+ matchSpan,
9426
10229
  mergeLayerResults,
9427
10230
  mergeSteeringBundle,
9428
10231
  modelDescriptionBits,
9429
10232
  modelPriceKey,
10233
+ mulberry32,
9430
10234
  multiToolchainLayer,
9431
10235
  nistAiRmfReport,
9432
10236
  normalizeScores,
@@ -9495,6 +10299,7 @@ export {
9495
10299
  researchReport,
9496
10300
  resetLockedAppendersForTesting,
9497
10301
  resolveModelPricing,
10302
+ resolveSeat,
9498
10303
  roundTripRunRecord,
9499
10304
  rowCount,
9500
10305
  rowWhere,
@@ -9524,6 +10329,7 @@ export {
9524
10329
  runsForScenario,
9525
10330
  scalarScore,
9526
10331
  scanForMuffledGates,
10332
+ scenarioKey,
9527
10333
  scoreContinuity,
9528
10334
  scoreFromEvals,
9529
10335
  scoreKnowledgeReadiness,
@@ -9532,6 +10338,7 @@ export {
9532
10338
  scoreRedTeamOutput,
9533
10339
  scoreReferenceReplay,
9534
10340
  scoreTraceInsightReadiness,
10341
+ seatPresets,
9535
10342
  securityJudge,
9536
10343
  selectHarnessVariant,
9537
10344
  selfPreference,
@@ -9551,18 +10358,21 @@ export {
9551
10358
  summarizeHarnessResults,
9552
10359
  summarizePrReviewBenchmark,
9553
10360
  summarizePreferenceMemory,
10361
+ summarizeRecords,
9554
10362
  summaryTable,
9555
10363
  testJudge,
9556
10364
  textInSnapshot,
9557
10365
  throwIfRunIncomplete,
9558
10366
  toAgentProfileJson,
9559
10367
  toLangfuseEnvelope,
10368
+ toOpenAiTool,
9560
10369
  toPrometheusText,
9561
10370
  tokenizeDomainWords,
9562
10371
  toolNamesForRun,
9563
10372
  toolSpans,
9564
10373
  traceAnalystFunctionGroup,
9565
10374
  traceAnalystOnRunComplete,
10375
+ traceContract,
9566
10376
  traceJudge,
9567
10377
  traceJudgeEnsemble,
9568
10378
  tracedAnalyzeTraces,
@@ -9573,6 +10383,7 @@ export {
9573
10383
  validateRunRecord,
9574
10384
  verbosityBias,
9575
10385
  verifyAgentProfileCell,
10386
+ verifyAttestation,
9576
10387
  verifyCompletion,
9577
10388
  verifyManifest,
9578
10389
  visualDiff,