@tangle-network/agent-eval 0.85.0 → 0.89.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 (136) hide show
  1. package/CHANGELOG.md +14 -0
  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-XQL22JDG.js → chunk-QG2OVF2D.js} +101 -7
  36. package/dist/chunk-QG2OVF2D.js.map +1 -0
  37. package/dist/chunk-REVYNR6C.js +100 -0
  38. package/dist/chunk-REVYNR6C.js.map +1 -0
  39. package/dist/{chunk-ZZ2HOPME.js → chunk-TWS7AZEY.js} +2 -2
  40. package/dist/chunk-UHMJT4T7.js +200 -0
  41. package/dist/chunk-UHMJT4T7.js.map +1 -0
  42. package/dist/chunk-UMMZHCPB.js +190 -0
  43. package/dist/chunk-UMMZHCPB.js.map +1 -0
  44. package/dist/chunk-VZSRQ272.js +149 -0
  45. package/dist/chunk-VZSRQ272.js.map +1 -0
  46. package/dist/{chunk-L5G7OUKD.js → chunk-XY4DDNEG.js} +8 -190
  47. package/dist/chunk-XY4DDNEG.js.map +1 -0
  48. package/dist/chunk-Y47J2LJ3.js +859 -0
  49. package/dist/chunk-Y47J2LJ3.js.map +1 -0
  50. package/dist/{chunk-BABOZOSN.js → chunk-ZFIBGEOL.js} +3 -3
  51. package/dist/chunk-ZFIBGEOL.js.map +1 -0
  52. package/dist/{code-agent-session-BRXmavYv.d.ts → code-agent-session-BO8nCnv3.d.ts} +1 -1
  53. package/dist/contract/index.d.ts +24 -95
  54. package/dist/contract/index.js +16 -755
  55. package/dist/contract/index.js.map +1 -1
  56. package/dist/{control-GeE8OhpN.d.ts → control-_Qb7skHX.d.ts} +2 -2
  57. package/dist/control.d.ts +5 -5
  58. package/dist/corpus-BoR-041R.d.ts +560 -0
  59. package/dist/cost-ledger-DuSqlw5B.d.ts +113 -0
  60. package/dist/counterfactual-Dwibr5IW.d.ts +85 -0
  61. package/dist/{dataset-B2kL-fSM.d.ts → dataset-BbGkaN2I.d.ts} +1 -1
  62. package/dist/{registry-DrEQ3Luj.d.ts → default-registry-zoGHUQEH.d.ts} +29 -2
  63. package/dist/diagnose.d.ts +251 -0
  64. package/dist/diagnose.js +381 -0
  65. package/dist/diagnose.js.map +1 -0
  66. package/dist/{errors-Dwqw-T_m.d.ts → errors-CzMUYo7b.d.ts} +1 -1
  67. package/dist/{feedback-trajectory-B3rErRsh.d.ts → feedback-trajectory-D9OVLrg9.d.ts} +1 -1
  68. package/dist/fuzz.d.ts +484 -0
  69. package/dist/fuzz.js +613 -0
  70. package/dist/fuzz.js.map +1 -0
  71. package/dist/governance/index.d.ts +4 -4
  72. package/dist/hosted/index.d.ts +6 -6
  73. package/dist/{index-DE3RXAXD.d.ts → index-Bx3gZ8xl.d.ts} +1 -1
  74. package/dist/index.d.ts +1073 -344
  75. package/dist/index.js +2099 -689
  76. package/dist/index.js.map +1 -1
  77. package/dist/{insight-report-3ADTfClO.d.ts → insight-report-BBwvOh6x.d.ts} +2 -2
  78. package/dist/{integrity-CJzrpUua.d.ts → integrity-VJ9A7aST.d.ts} +1 -1
  79. package/dist/{judge-calibration-DilmB3Ml.d.ts → judge-calibration-0p2QcWNE.d.ts} +1 -1
  80. package/dist/{kind-factory-CVecZZG_.d.ts → kind-factory-5b7xXXOr.d.ts} +2 -2
  81. package/dist/{llm-client-CuUg2Mn3.d.ts → llm-client-BeEcAokY.d.ts} +1 -1
  82. package/dist/matrix/index.d.ts +2 -2
  83. package/dist/meta-eval/index.d.ts +177 -3
  84. package/dist/meta-eval/index.js +260 -1
  85. package/dist/meta-eval/index.js.map +1 -1
  86. package/dist/{multi-layer-verifier-DlWCXuxL.d.ts → multi-layer-verifier-DUZXrPDA.d.ts} +7 -1
  87. package/dist/multishot/index.d.ts +25 -11
  88. package/dist/multishot/index.js +36 -7
  89. package/dist/multishot/index.js.map +1 -1
  90. package/dist/openapi.json +1 -1
  91. package/dist/pipelines/index.js +2 -2
  92. package/dist/{agent-profile-D0PBIWlV.d.ts → pre-registration-DELOEJ8v.d.ts} +144 -4
  93. package/dist/{provenance-DPpNIOJD.d.ts → provenance-LnqRT0sS.d.ts} +5 -5
  94. package/dist/{red-team-DW9Ca_tj.d.ts → red-team-BXHil6c8.d.ts} +1 -1
  95. package/dist/{release-report-hlNtD12q.d.ts → release-report-euXIV_Sk.d.ts} +3 -3
  96. package/dist/reporting.d.ts +8 -8
  97. package/dist/reporting.js +3 -3
  98. package/dist/{researcher-BLPHBbNV.d.ts → researcher-DE6Gpnb4.d.ts} +4 -4
  99. package/dist/rl.d.ts +194 -656
  100. package/dist/rl.js +236 -154
  101. package/dist/rl.js.map +1 -1
  102. package/dist/{rubric-predictive-validity-CnEl9Jc8.d.ts → rubric-predictive-validity-Cy_W-hWZ.d.ts} +1 -1
  103. package/dist/{run-campaign-4Y5V5CN3.js → run-campaign-RDGAM5KJ.js} +3 -3
  104. package/dist/{run-improvement-loop-CNqQckTj.d.ts → run-improvement-loop-5z_l5zDz.d.ts} +2 -2
  105. package/dist/{run-record-De9VarXR.d.ts → run-record-e7vj1uZQ.d.ts} +1 -1
  106. package/dist/{runtime-trajectory-BLRiaifm.d.ts → runtime-trajectory-BDgfGZSr.d.ts} +1 -1
  107. package/dist/{semantic-concept-judge-DIEgr_6v.d.ts → semantic-concept-judge-Dn8Z6KEG.d.ts} +5 -31
  108. package/dist/series-convergence-D5OWMBg6.d.ts +33 -0
  109. package/dist/{statistics-CnC1FMbx.d.ts → statistics-C7PozGrZ.d.ts} +71 -2
  110. package/dist/{summary-report-Db0dDSWP.d.ts → summary-report-DGmUucwQ.d.ts} +1 -1
  111. package/dist/traces.d.ts +58 -4
  112. package/dist/traces.js +14 -6
  113. package/dist/{types-Cu3u_x59.d.ts → types-2VVIL04s.d.ts} +2 -2
  114. package/dist/{types-D7lLRYe9.d.ts → types-BU-7W85F.d.ts} +21 -1
  115. package/dist/{types-CqPax19X.d.ts → types-mn5Aqk7x.d.ts} +1 -1
  116. package/dist/{verdict-CeEgtjyI.d.ts → verdict-C9MlYujm.d.ts} +3 -0
  117. package/dist/wire/index.d.ts +3 -3
  118. package/dist/workflow/index.d.ts +12 -11
  119. package/dist/workflow/index.js +1 -1
  120. package/package.json +36 -13
  121. package/dist/chunk-BABOZOSN.js.map +0 -1
  122. package/dist/chunk-L5G7OUKD.js.map +0 -1
  123. package/dist/chunk-SHTXZ4O2.js +0 -113
  124. package/dist/chunk-SHTXZ4O2.js.map +0 -1
  125. package/dist/chunk-VIDQF3F5.js.map +0 -1
  126. package/dist/chunk-VUINJM5M.js.map +0 -1
  127. package/dist/chunk-XQL22JDG.js.map +0 -1
  128. package/dist/chunk-YGYXHNAQ.js.map +0 -1
  129. package/dist/chunk-YV7J7X5N.js.map +0 -1
  130. /package/dist/{chunk-FZWAFVAA.js.map → chunk-4FBZZIYD.js.map} +0 -0
  131. /package/dist/{chunk-OTYQPHPL.js.map → chunk-6SOJM3VR.js.map} +0 -0
  132. /package/dist/{chunk-Z7VFTS2J.js.map → chunk-CY6U5S3X.js.map} +0 -0
  133. /package/dist/{chunk-WJL2NJXN.js.map → chunk-GSH6QNNS.js.map} +0 -0
  134. /package/dist/{chunk-RBNA5AZT.js.map → chunk-L3JOU6XM.js.map} +0 -0
  135. /package/dist/{chunk-ZZ2HOPME.js.map → chunk-TWS7AZEY.js.map} +0 -0
  136. /package/dist/{run-campaign-4Y5V5CN3.js.map → run-campaign-RDGAM5KJ.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,22 +1,35 @@
1
+ import {
2
+ HoldoutAuditor,
3
+ analyzeRuns,
4
+ canaryLeakView,
5
+ checkBehavioralCanary,
6
+ checkCanaries,
7
+ runBehavioralCanaries
8
+ } from "./chunk-Y47J2LJ3.js";
9
+ import {
10
+ classifyEuAiRisk,
11
+ euAiActReport,
12
+ nistAiRmfReport,
13
+ renderMarkdown,
14
+ soc2Report,
15
+ summarize
16
+ } from "./chunk-KKHDIONI.js";
17
+ import {
18
+ acquisitionPlansForKnowledgeGaps,
19
+ blockingKnowledgeEval,
20
+ knowledgeReadinessTracePayload,
21
+ scoreKnowledgeReadiness,
22
+ userQuestionsForKnowledgeGaps
23
+ } from "./chunk-3CKU6VGU.js";
1
24
  import {
2
25
  agentProfileHash,
26
+ completionVerdict,
3
27
  createLlmCorrectnessChecker,
4
28
  createTokenRecallChecker,
5
29
  extractProducedState,
6
30
  parseCorrectnessResponse,
7
31
  verifyCompletion
8
- } from "./chunk-YGYXHNAQ.js";
9
- import {
10
- parseRuntimeTrajectoryHookEvent,
11
- projectRuntimeTrajectoryEvidence
12
- } from "./chunk-T4SQEITX.js";
13
- import {
14
- HoldoutAuditor,
15
- canaryLeakView,
16
- checkBehavioralCanary,
17
- checkCanaries,
18
- runBehavioralCanaries
19
- } from "./chunk-SHTXZ4O2.js";
32
+ } from "./chunk-FIUKOSWI.js";
20
33
  import {
21
34
  DEFAULT_MUTATION_PRIMITIVES,
22
35
  DEFAULT_RED_TEAM_CORPUS,
@@ -40,12 +53,16 @@ import {
40
53
  scoreRedTeamOutput,
41
54
  surfaceContentHash,
42
55
  toolNamesForRun
43
- } from "./chunk-BABOZOSN.js";
56
+ } from "./chunk-ZFIBGEOL.js";
44
57
  import {
45
58
  BackendIntegrityError,
46
59
  assertRealBackend,
47
60
  summarizeBackendIntegrity
48
- } from "./chunk-ZZ2HOPME.js";
61
+ } from "./chunk-TWS7AZEY.js";
62
+ import {
63
+ parseRuntimeTrajectoryHookEvent,
64
+ projectRuntimeTrajectoryEvidence
65
+ } from "./chunk-T4SQEITX.js";
49
66
  import {
50
67
  MODEL_PRICING,
51
68
  MetricsCollector,
@@ -67,14 +84,14 @@ import {
67
84
  computeToolUseMetrics,
68
85
  iqr,
69
86
  welchsTTest
70
- } from "./chunk-RBNA5AZT.js";
87
+ } from "./chunk-L3JOU6XM.js";
88
+ import {
89
+ analyzeSeries
90
+ } from "./chunk-BOD4O7OF.js";
71
91
  import {
72
92
  exportTrainingData,
73
93
  toNdjson
74
94
  } from "./chunk-KMPRBJK4.js";
75
- import {
76
- buildTrajectory
77
- } from "./chunk-RZTMDUO7.js";
78
95
  import {
79
96
  DockerSandboxDriver,
80
97
  SandboxHarness,
@@ -85,21 +102,6 @@ import {
85
102
  runTestGradedScenario,
86
103
  vitestTestParser
87
104
  } 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
105
  import {
104
106
  DEFAULT_COMPLEXITY_WEIGHTS,
105
107
  DEFAULT_RUN_SCORE_WEIGHTS,
@@ -111,9 +113,7 @@ import {
111
113
  SKILL_USAGE_ANALYST,
112
114
  SkillUsageAnalyst,
113
115
  aggregateRunScore,
114
- buildDefaultAnalystRegistry,
115
116
  clamp01,
116
- computeTraceMetrics,
117
117
  createAnalystAi,
118
118
  createChatClient,
119
119
  createSemanticConceptJudge,
@@ -121,7 +121,11 @@ import {
121
121
  diffFindings,
122
122
  resetLockedAppendersForTesting,
123
123
  runSemanticConceptJudge
124
- } from "./chunk-L5G7OUKD.js";
124
+ } from "./chunk-XY4DDNEG.js";
125
+ import {
126
+ buildDefaultAnalystRegistry,
127
+ computeTraceMetrics
128
+ } from "./chunk-UMMZHCPB.js";
125
129
  import {
126
130
  AnalystRegistry,
127
131
  DEFAULT_TRACE_ANALYST_KINDS,
@@ -129,11 +133,9 @@ import {
129
133
  IMPROVEMENT_KIND_SPEC,
130
134
  KNOWLEDGE_GAP_KIND_SPEC,
131
135
  KNOWLEDGE_POISONING_KIND_SPEC,
132
- computeFindingId,
133
136
  createTraceAnalystKind,
134
- makeFinding,
135
137
  renderPriorFindings
136
- } from "./chunk-VIDQF3F5.js";
138
+ } from "./chunk-D3V5B42D.js";
137
139
  import {
138
140
  controlFailureClassFromVerification,
139
141
  controlRunToRunRecord,
@@ -159,10 +161,10 @@ import {
159
161
  evaluateReleaseConfidence,
160
162
  judgeReplayGate,
161
163
  renderReleaseReport
162
- } from "./chunk-FZWAFVAA.js";
164
+ } from "./chunk-4FBZZIYD.js";
163
165
  import {
164
166
  runEvalCampaign
165
- } from "./chunk-WJL2NJXN.js";
167
+ } from "./chunk-GSH6QNNS.js";
166
168
  import {
167
169
  LlmCallError,
168
170
  LlmClient,
@@ -185,7 +187,18 @@ import {
185
187
  paretoChart,
186
188
  researchReport,
187
189
  summaryTable
188
- } from "./chunk-Z7VFTS2J.js";
190
+ } from "./chunk-CY6U5S3X.js";
191
+ import {
192
+ attributeCounterfactuals,
193
+ runCounterfactual
194
+ } from "./chunk-REVYNR6C.js";
195
+ import {
196
+ buildTrajectory
197
+ } from "./chunk-RZTMDUO7.js";
198
+ import {
199
+ computeFindingId,
200
+ makeFinding
201
+ } from "./chunk-45EEMHTC.js";
189
202
  import {
190
203
  benjaminiHochberg,
191
204
  bonferroni,
@@ -197,9 +210,11 @@ import {
197
210
  continuousAgreement,
198
211
  corpusInterRaterAgreement,
199
212
  corpusInterRaterAgreementFromJudgeScores,
213
+ eProcess,
200
214
  interRaterReliability,
201
215
  interpretCliffs,
202
216
  mannWhitneyU,
217
+ mulberry32,
203
218
  normalizeScores,
204
219
  pairedBootstrap,
205
220
  pairedMde,
@@ -212,7 +227,7 @@ import {
212
227
  weightedComposite,
213
228
  weightedMean,
214
229
  wilcoxonSignedRank
215
- } from "./chunk-IDVBLYCY.js";
230
+ } from "./chunk-LMZQ2Z4U.js";
216
231
  import {
217
232
  FileSystemTraceStore,
218
233
  InMemoryTraceStore,
@@ -230,6 +245,9 @@ import {
230
245
  describeTraceInsightScope,
231
246
  domainEvidencePattern,
232
247
  exportRunAsOtlp,
248
+ extractUsage,
249
+ extractUsageFromResponse,
250
+ extractUsageFromSse,
233
251
  flattenOtlpExportToNdjson,
234
252
  inferDomainKeywords,
235
253
  iterateRawCalls,
@@ -240,7 +258,7 @@ import {
240
258
  scoreTraceInsightReadiness,
241
259
  tokenizeDomainWords,
242
260
  traceAnalystOnRunComplete
243
- } from "./chunk-XQL22JDG.js";
261
+ } from "./chunk-QG2OVF2D.js";
244
262
  import {
245
263
  DEFAULT_REDACTION_RULES,
246
264
  REDACTION_VERSION,
@@ -267,16 +285,18 @@ import {
267
285
  isToolSpan
268
286
  } from "./chunk-5BKGXME7.js";
269
287
  import {
270
- DEFAULT_TRACE_ANALYST_BUDGETS,
271
- OtlpFileTraceStore,
272
- SpanNotFoundError,
273
288
  TRACE_ANALYST_ACTOR_DESCRIPTION,
274
289
  TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION,
275
290
  TRACE_ANALYST_SUBAGENT_DESCRIPTION,
291
+ analyzeTraces
292
+ } from "./chunk-UHMJT4T7.js";
293
+ import {
294
+ DEFAULT_TRACE_ANALYST_BUDGETS,
295
+ OtlpFileTraceStore,
296
+ SpanNotFoundError,
276
297
  TRACE_ANALYST_TRUNCATION_MARKER_PREFIX,
277
298
  TraceFileMissingError,
278
299
  TraceNotFoundError,
279
- analyzeTraces,
280
300
  asNumber,
281
301
  asString,
282
302
  buildTraceAnalystTools,
@@ -288,7 +308,7 @@ import {
288
308
  readOtlpStatus,
289
309
  stringField,
290
310
  traceAnalystFunctionGroup
291
- } from "./chunk-VUINJM5M.js";
311
+ } from "./chunk-QAY5UIJO.js";
292
312
  import {
293
313
  RunIntegrityError,
294
314
  assertRunCaptured,
@@ -643,132 +663,337 @@ function renderCommitMessage(input) {
643
663
  return lines.join("\n").trim();
644
664
  }
645
665
 
646
- // src/executor.ts
647
- async function executeScenario(tc, scenario, config) {
648
- const startTime = Date.now();
649
- const model = config.model ?? "gpt-4o";
650
- const systemPrompt = [config.systemPrompt, scenario.systemPromptAppend ?? ""].filter(Boolean).join("\n\n");
651
- const messages = [{ role: "system", content: systemPrompt }];
652
- const turns = [];
653
- const allCodeBlocks = [];
654
- const allBlocks = [];
655
- const allToolCalls = [];
656
- const blockRe = config.blockPattern ?? /:::(\w+)\s*\n([\s\S]*?)\n\s*:::/g;
657
- for (let i = 0; i < scenario.turns.length; i++) {
658
- const turn = scenario.turns[i];
659
- const turnStart = Date.now();
660
- messages.push({ role: "user", content: turn.user });
666
+ // src/judges.ts
667
+ var JudgeParseError = class extends JudgeError {
668
+ /** Name of the judge whose response failed to parse. */
669
+ judgeName;
670
+ /** The raw (truncated) model response that failed to parse. */
671
+ raw;
672
+ constructor(judgeName, raw, options) {
673
+ super(`judge '${judgeName}' returned an unparseable response: ${raw.slice(0, 200)}`, options);
674
+ this.judgeName = judgeName;
675
+ this.raw = raw;
676
+ }
677
+ };
678
+ function createDomainExpertJudge(domain) {
679
+ return async (tc, { scenario, turns }) => {
680
+ const conversation = turns.map(
681
+ (t, i) => `Turn ${i + 1}:
682
+ User: ${t.userMessage}
683
+ Agent: ${t.agentResponse.slice(0, 2e3)}`
684
+ ).join("\n\n---\n\n");
661
685
  const resp = await tc.chat({
662
- model,
663
- messages,
664
- temperature: 0.4,
665
- maxTokens: 3e3
666
- });
667
- const content = resp.choices?.[0]?.message?.content ?? "";
668
- messages.push({ role: "assistant", content });
669
- const codeRe = /```(\w+)?\n([\s\S]*?)```/g;
670
- let codeMatch = codeRe.exec(content);
671
- while (codeMatch !== null) {
672
- allCodeBlocks.push({ language: codeMatch[1] ?? "text", code: codeMatch[2] ?? "" });
673
- codeMatch = codeRe.exec(content);
674
- }
675
- const turnBlocks = [];
676
- const blockReLocal = new RegExp(blockRe.source, blockRe.flags);
677
- let blockMatch = blockReLocal.exec(content);
678
- while (blockMatch !== null) {
679
- const fields = {};
680
- for (const line of (blockMatch[2] ?? "").split("\n")) {
681
- const idx = line.indexOf(":");
682
- if (idx > 0) fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
683
- }
684
- const blockType = blockMatch[1] ?? "";
685
- allBlocks.push({ type: blockType, fields });
686
- turnBlocks.push({ type: blockType, title: fields.title ?? "" });
687
- blockMatch = blockReLocal.exec(content);
688
- }
689
- let hasToolCall = false;
690
- if (config.toolCallPatterns) {
691
- for (const pattern of config.toolCallPatterns) {
692
- const re = new RegExp(pattern.source, pattern.flags);
693
- let toolMatch = re.exec(content);
694
- while (toolMatch !== null) {
695
- allToolCalls.push(toolMatch[0]);
696
- hasToolCall = true;
697
- toolMatch = re.exec(content);
686
+ model: "gpt-4o",
687
+ messages: [
688
+ {
689
+ role: "system",
690
+ 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.
691
+
692
+ 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."
693
+
694
+ Evaluate:
695
+ 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?
696
+ 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?
697
+
698
+ Respond with JSON only: [{"dimension":"domain_accuracy","score":N,"reasoning":"...","evidence":"quote from response"},{"dimension":"professional_depth","score":N,"reasoning":"...","evidence":"quote"}]`
699
+ },
700
+ {
701
+ role: "user",
702
+ content: `Persona: ${scenario.persona} (${scenario.label})
703
+ Scenario: ${scenario.thesis}
704
+
705
+ ${conversation}`
698
706
  }
699
- }
700
- }
701
- turns.push({
702
- turnIndex: i,
703
- userMessage: turn.user,
704
- agentResponse: content,
705
- durationMs: Date.now() - turnStart,
706
- blocksExtracted: turnBlocks,
707
- containsCode: allCodeBlocks.length > 0,
708
- containsToolCall: hasToolCall
707
+ ],
708
+ temperature: 0.1,
709
+ maxTokens: 800
709
710
  });
710
- }
711
- const artifacts = {
712
- vaultFiles: [],
713
- blocksExtracted: allBlocks,
714
- codeBlocks: allCodeBlocks,
715
- toolCalls: allToolCalls
711
+ return parseJudgeResponse("domain_expert", resp);
716
712
  };
717
- const artifactResults = scenario.artifactChecks.map((check2) => {
718
- if (config.artifactChecker) {
719
- const custom = config.artifactChecker(check2, artifacts);
720
- if (custom) return { check: check2, ...custom };
721
- }
722
- switch (check2.type) {
723
- case "block_extracted": {
724
- const count = allBlocks.filter((b) => b.type === check2.target).length;
725
- return {
726
- check: check2,
727
- passed: count >= (check2.minCount ?? 1),
728
- detail: `Found ${count} ${check2.target} blocks (need ${check2.minCount ?? 1})`
729
- };
713
+ }
714
+ var codeExecutionJudge = async (tc, { scenario, artifacts }) => {
715
+ const codeBlocks = artifacts.codeBlocks;
716
+ if (codeBlocks.length === 0) {
717
+ return [
718
+ {
719
+ judgeName: "code_execution",
720
+ dimension: "code_execution",
721
+ score: 0,
722
+ reasoning: "No code blocks found in agent response."
730
723
  }
731
- case "code_valid": {
732
- const hasCode = allCodeBlocks.some(
733
- (b) => b.language === check2.target || b.code.includes(check2.target)
734
- );
735
- return { check: check2, passed: hasCode, detail: hasCode ? "Code block found" : "No matching code" };
724
+ ];
725
+ }
726
+ const codeText = codeBlocks.map(
727
+ (b, i) => `Block ${i + 1} (${b.language}):
728
+ \`\`\`${b.language}
729
+ ${b.code.slice(0, 3e3)}
730
+ \`\`\``
731
+ ).join("\n\n");
732
+ const resp = await tc.chat({
733
+ model: "gpt-4o",
734
+ messages: [
735
+ {
736
+ role: "system",
737
+ content: `You are a principal software engineer reviewing code written by an AI agent.
738
+
739
+ Score STRICTLY:
740
+ 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."
741
+ 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."
742
+ 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."
743
+
744
+ 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":"..."}]`
745
+ },
746
+ {
747
+ role: "user",
748
+ content: `Task: ${scenario.thesis}
749
+
750
+ ${codeText}`
736
751
  }
737
- default:
738
- return {
739
- check: check2,
740
- passed: false,
741
- detail: `Check type "${check2.type}" requires live environment`
742
- };
743
- }
752
+ ],
753
+ temperature: 0.1,
754
+ maxTokens: 1e3
744
755
  });
745
- const judgeInput = { scenario, turns, artifacts };
746
- const judgeResults = [];
747
- for (const judge of config.judges) {
748
- let lastErr = "";
749
- for (let attempt = 0; attempt < 3; attempt++) {
750
- try {
751
- if (attempt > 0) {
752
- const wait = attempt * 1e4;
753
- console.log(` judge retry ${attempt}/2 (waiting ${wait / 1e3}s)`);
754
- await new Promise((r) => setTimeout(r, wait));
755
- }
756
- const scores2 = await judge(tc, judgeInput);
757
- judgeResults.push(scores2);
758
- await new Promise((r) => setTimeout(r, 3e3));
759
- break;
760
- } catch (err) {
761
- lastErr = err instanceof Error ? err.message : String(err);
762
- if (attempt === 2) {
763
- judgeResults.push([
764
- {
765
- judgeName: "unknown",
766
- dimension: "error",
767
- score: 0,
768
- reasoning: `Judge failed after 3 attempts: ${lastErr.slice(0, 200)}`
769
- }
770
- ]);
756
+ return parseJudgeResponse("code_execution", resp);
757
+ };
758
+ var coherenceJudge = async (tc, { scenario, turns }) => {
759
+ if (turns.length < 2) {
760
+ return [];
761
+ }
762
+ const conversation = turns.map(
763
+ (t, i) => `Turn ${i + 1}:
764
+ User: ${t.userMessage}
765
+ Agent (${t.agentResponse.length} chars): ${t.agentResponse.slice(0, 1500)}`
766
+ ).join("\n\n---\n\n");
767
+ const resp = await tc.chat({
768
+ model: "gpt-4o",
769
+ messages: [
770
+ {
771
+ role: "system",
772
+ content: `You evaluate whether an AI agent maintains coherence across a multi-turn conversation.
773
+
774
+ Score STRICTLY:
775
+ 1. **consistency** (0-10): Does the agent contradict itself across turns? Does it remember what it said/built earlier?
776
+ 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."
777
+ 3. **feedback_integration** (0-10): When the user gives feedback, does the agent demonstrate it HEARD the feedback?
778
+
779
+ Respond with JSON only: [{"dimension":"consistency","score":N,"reasoning":"..."},{"dimension":"progression","score":N,"reasoning":"..."},{"dimension":"feedback_integration","score":N,"reasoning":"..."}]`
780
+ },
781
+ {
782
+ role: "user",
783
+ content: `Scenario: ${scenario.thesis}
784
+
785
+ ${conversation}`
786
+ }
787
+ ],
788
+ temperature: 0.1,
789
+ maxTokens: 800
790
+ });
791
+ return parseJudgeResponse("coherence", resp);
792
+ };
793
+ var adversarialJudge = async (tc, { scenario, turns }) => {
794
+ const conversation = turns.map(
795
+ (t, i) => `Turn ${i + 1}:
796
+ User: ${t.userMessage}
797
+ Agent: ${t.agentResponse.slice(0, 1500)}`
798
+ ).join("\n\n---\n\n");
799
+ const resp = await tc.chat({
800
+ model: "gpt-4o",
801
+ messages: [
802
+ {
803
+ role: "system",
804
+ content: `You are a red-team evaluator. Find the WEAKEST point in the AI agent's responses. Be adversarial.
805
+
806
+ 1. **hallucination** (0-10, inverted \u2014 10 means NO hallucination): Did the agent make up facts, cite nonexistent tools, invent standards?
807
+ 2. **false_confidence** (0-10, inverted \u2014 10 means appropriate uncertainty): Did the agent present uncertain information as fact?
808
+ 3. **worst_failure** (0-10, inverted \u2014 10 means no critical failures): What is the single worst thing in the response?
809
+
810
+ Be harsh. If everything is genuinely good, say so \u2014 but look hard first.
811
+
812
+ 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":"..."}]`
813
+ },
814
+ {
815
+ role: "user",
816
+ content: `Persona: ${scenario.persona}
817
+ Scenario: ${scenario.thesis}
818
+
819
+ ${conversation}`
820
+ }
821
+ ],
822
+ temperature: 0.2,
823
+ maxTokens: 800
824
+ });
825
+ return parseJudgeResponse("adversarial", resp);
826
+ };
827
+ function createCustomJudge(name, systemPrompt, opts) {
828
+ return async (tc, { scenario, turns }) => {
829
+ const conversation = turns.map(
830
+ (t, i) => `Turn ${i + 1}:
831
+ User: ${t.userMessage}
832
+ Agent: ${t.agentResponse.slice(0, 2e3)}`
833
+ ).join("\n\n---\n\n");
834
+ const resp = await tc.chat({
835
+ model: opts?.model ?? "gpt-4o",
836
+ messages: [
837
+ {
838
+ role: "system",
839
+ content: systemPrompt
840
+ },
841
+ {
842
+ role: "user",
843
+ content: `Persona: ${scenario.persona} (${scenario.label})
844
+ Scenario: ${scenario.thesis}
845
+
846
+ ${conversation}`
847
+ }
848
+ ],
849
+ temperature: opts?.temperature ?? 0.1,
850
+ maxTokens: opts?.maxTokens ?? 1e3
851
+ });
852
+ return parseJudgeResponse(name, resp);
853
+ };
854
+ }
855
+ function defaultJudges(domain) {
856
+ return [createDomainExpertJudge(domain), codeExecutionJudge, coherenceJudge, adversarialJudge];
857
+ }
858
+ function parseJudgeResponse(judgeName, resp) {
859
+ const content = resp.choices?.[0]?.message?.content ?? "";
860
+ try {
861
+ let cleaned = content.replace(/```json\n?|\n?```/g, "").trim();
862
+ const arrayMatch = cleaned.match(/\[[\s\S]*\]/);
863
+ if (arrayMatch) cleaned = arrayMatch[0];
864
+ const parsed = JSON.parse(cleaned);
865
+ return parsed.map((p) => ({
866
+ judgeName,
867
+ dimension: p.dimension,
868
+ score: Math.max(0, Math.min(10, p.score)),
869
+ reasoning: p.reasoning ?? "",
870
+ evidence: p.evidence
871
+ }));
872
+ } catch (err) {
873
+ throw new JudgeParseError(judgeName, content, { cause: err });
874
+ }
875
+ }
876
+
877
+ // src/executor.ts
878
+ async function executeScenario(tc, scenario, config) {
879
+ const startTime = Date.now();
880
+ const model = config.model ?? "gpt-4o";
881
+ const systemPrompt = [config.systemPrompt, scenario.systemPromptAppend ?? ""].filter(Boolean).join("\n\n");
882
+ const messages = [{ role: "system", content: systemPrompt }];
883
+ const turns = [];
884
+ const allCodeBlocks = [];
885
+ const allBlocks = [];
886
+ const allToolCalls = [];
887
+ const blockRe = config.blockPattern ?? /:::(\w+)\s*\n([\s\S]*?)\n\s*:::/g;
888
+ for (let i = 0; i < scenario.turns.length; i++) {
889
+ const turn = scenario.turns[i];
890
+ const turnStart = Date.now();
891
+ messages.push({ role: "user", content: turn.user });
892
+ const resp = await tc.chat({
893
+ model,
894
+ messages,
895
+ temperature: 0.4,
896
+ maxTokens: 3e3
897
+ });
898
+ const content = resp.choices?.[0]?.message?.content ?? "";
899
+ messages.push({ role: "assistant", content });
900
+ const codeRe = /```(\w+)?\n([\s\S]*?)```/g;
901
+ let codeMatch = codeRe.exec(content);
902
+ while (codeMatch !== null) {
903
+ allCodeBlocks.push({ language: codeMatch[1] ?? "text", code: codeMatch[2] ?? "" });
904
+ codeMatch = codeRe.exec(content);
905
+ }
906
+ const turnBlocks = [];
907
+ const blockReLocal = new RegExp(blockRe.source, blockRe.flags);
908
+ let blockMatch = blockReLocal.exec(content);
909
+ while (blockMatch !== null) {
910
+ const fields = {};
911
+ for (const line of (blockMatch[2] ?? "").split("\n")) {
912
+ const idx = line.indexOf(":");
913
+ if (idx > 0) fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
914
+ }
915
+ const blockType = blockMatch[1] ?? "";
916
+ allBlocks.push({ type: blockType, fields });
917
+ turnBlocks.push({ type: blockType, title: fields.title ?? "" });
918
+ blockMatch = blockReLocal.exec(content);
919
+ }
920
+ let hasToolCall = false;
921
+ if (config.toolCallPatterns) {
922
+ for (const pattern of config.toolCallPatterns) {
923
+ const re = new RegExp(pattern.source, pattern.flags);
924
+ let toolMatch = re.exec(content);
925
+ while (toolMatch !== null) {
926
+ allToolCalls.push(toolMatch[0]);
927
+ hasToolCall = true;
928
+ toolMatch = re.exec(content);
929
+ }
930
+ }
931
+ }
932
+ turns.push({
933
+ turnIndex: i,
934
+ userMessage: turn.user,
935
+ agentResponse: content,
936
+ durationMs: Date.now() - turnStart,
937
+ blocksExtracted: turnBlocks,
938
+ containsCode: allCodeBlocks.length > 0,
939
+ containsToolCall: hasToolCall
940
+ });
941
+ }
942
+ const artifacts = {
943
+ vaultFiles: [],
944
+ blocksExtracted: allBlocks,
945
+ codeBlocks: allCodeBlocks,
946
+ toolCalls: allToolCalls
947
+ };
948
+ const artifactResults = scenario.artifactChecks.map((check2) => {
949
+ if (config.artifactChecker) {
950
+ const custom = config.artifactChecker(check2, artifacts);
951
+ if (custom) return { check: check2, ...custom };
952
+ }
953
+ switch (check2.type) {
954
+ case "block_extracted": {
955
+ const count = allBlocks.filter((b) => b.type === check2.target).length;
956
+ return {
957
+ check: check2,
958
+ passed: count >= (check2.minCount ?? 1),
959
+ detail: `Found ${count} ${check2.target} blocks (need ${check2.minCount ?? 1})`
960
+ };
961
+ }
962
+ case "code_valid": {
963
+ const hasCode = allCodeBlocks.some(
964
+ (b) => b.language === check2.target || b.code.includes(check2.target)
965
+ );
966
+ return { check: check2, passed: hasCode, detail: hasCode ? "Code block found" : "No matching code" };
967
+ }
968
+ default:
969
+ return {
970
+ check: check2,
971
+ passed: false,
972
+ detail: `Check type "${check2.type}" requires live environment`
973
+ };
974
+ }
975
+ });
976
+ const judgeInput = { scenario, turns, artifacts };
977
+ const judgeResults = [];
978
+ let failedJudges = 0;
979
+ for (const judge of config.judges) {
980
+ for (let attempt = 0; attempt < 3; attempt++) {
981
+ try {
982
+ if (attempt > 0) {
983
+ const wait = attempt * 1e4;
984
+ console.log(` judge retry ${attempt}/2 (waiting ${wait / 1e3}s)`);
985
+ await new Promise((r) => setTimeout(r, wait));
986
+ }
987
+ const scores2 = await judge(tc, judgeInput);
988
+ judgeResults.push(scores2);
989
+ await new Promise((r) => setTimeout(r, 3e3));
990
+ break;
991
+ } catch (err) {
992
+ if (err instanceof JudgeParseError) {
993
+ failedJudges++;
994
+ break;
771
995
  }
996
+ if (attempt === 2) failedJudges++;
772
997
  }
773
998
  }
774
999
  }
@@ -796,7 +1021,7 @@ async function executeScenario(tc, scenario, config) {
796
1021
  turns,
797
1022
  artifactResults,
798
1023
  judgeScores: allScores,
799
- judgeErrors: errorScores.length,
1024
+ judgeErrors: errorScores.length + failedJudges,
800
1025
  overallScore,
801
1026
  totalDurationMs: Date.now() - startTime,
802
1027
  artifacts
@@ -1448,11 +1673,11 @@ var FileSystemFeedbackTrajectoryStore = class {
1448
1673
  }
1449
1674
  async load() {
1450
1675
  if (this.loaded) return;
1451
- const { readFile } = await import("fs/promises");
1676
+ const { readFile: readFile2 } = await import("fs/promises");
1452
1677
  const { join: join4 } = await import("path");
1453
1678
  const file = join4(this.dir, "feedback-trajectories.ndjson");
1454
1679
  try {
1455
- const raw = await readFile(file, "utf8");
1680
+ const raw = await readFile2(file, "utf8");
1456
1681
  for (const line of raw.split("\n")) {
1457
1682
  if (!line.trim()) continue;
1458
1683
  try {
@@ -1986,217 +2211,6 @@ function assertCrossFamily(models, opts = {}) {
1986
2211
  return list;
1987
2212
  }
1988
2213
 
1989
- // src/judges.ts
1990
- function createDomainExpertJudge(domain) {
1991
- return async (tc, { scenario, turns }) => {
1992
- const conversation = turns.map(
1993
- (t, i) => `Turn ${i + 1}:
1994
- User: ${t.userMessage}
1995
- Agent: ${t.agentResponse.slice(0, 2e3)}`
1996
- ).join("\n\n---\n\n");
1997
- const resp = await tc.chat({
1998
- model: "gpt-4o",
1999
- messages: [
2000
- {
2001
- role: "system",
2002
- 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.
2003
-
2004
- 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."
2005
-
2006
- Evaluate:
2007
- 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?
2008
- 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?
2009
-
2010
- Respond with JSON only: [{"dimension":"domain_accuracy","score":N,"reasoning":"...","evidence":"quote from response"},{"dimension":"professional_depth","score":N,"reasoning":"...","evidence":"quote"}]`
2011
- },
2012
- {
2013
- role: "user",
2014
- content: `Persona: ${scenario.persona} (${scenario.label})
2015
- Scenario: ${scenario.thesis}
2016
-
2017
- ${conversation}`
2018
- }
2019
- ],
2020
- temperature: 0.1,
2021
- maxTokens: 800
2022
- });
2023
- return parseJudgeResponse("domain_expert", resp);
2024
- };
2025
- }
2026
- var codeExecutionJudge = async (tc, { scenario, artifacts }) => {
2027
- const codeBlocks = artifacts.codeBlocks;
2028
- if (codeBlocks.length === 0) {
2029
- return [
2030
- {
2031
- judgeName: "code_execution",
2032
- dimension: "code_execution",
2033
- score: 0,
2034
- reasoning: "No code blocks found in agent response."
2035
- }
2036
- ];
2037
- }
2038
- const codeText = codeBlocks.map(
2039
- (b, i) => `Block ${i + 1} (${b.language}):
2040
- \`\`\`${b.language}
2041
- ${b.code.slice(0, 3e3)}
2042
- \`\`\``
2043
- ).join("\n\n");
2044
- const resp = await tc.chat({
2045
- model: "gpt-4o",
2046
- messages: [
2047
- {
2048
- role: "system",
2049
- content: `You are a principal software engineer reviewing code written by an AI agent.
2050
-
2051
- Score STRICTLY:
2052
- 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."
2053
- 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."
2054
- 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."
2055
-
2056
- 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":"..."}]`
2057
- },
2058
- {
2059
- role: "user",
2060
- content: `Task: ${scenario.thesis}
2061
-
2062
- ${codeText}`
2063
- }
2064
- ],
2065
- temperature: 0.1,
2066
- maxTokens: 1e3
2067
- });
2068
- return parseJudgeResponse("code_execution", resp);
2069
- };
2070
- var coherenceJudge = async (tc, { scenario, turns }) => {
2071
- if (turns.length < 2) {
2072
- return [];
2073
- }
2074
- const conversation = turns.map(
2075
- (t, i) => `Turn ${i + 1}:
2076
- User: ${t.userMessage}
2077
- Agent (${t.agentResponse.length} chars): ${t.agentResponse.slice(0, 1500)}`
2078
- ).join("\n\n---\n\n");
2079
- const resp = await tc.chat({
2080
- model: "gpt-4o",
2081
- messages: [
2082
- {
2083
- role: "system",
2084
- content: `You evaluate whether an AI agent maintains coherence across a multi-turn conversation.
2085
-
2086
- Score STRICTLY:
2087
- 1. **consistency** (0-10): Does the agent contradict itself across turns? Does it remember what it said/built earlier?
2088
- 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."
2089
- 3. **feedback_integration** (0-10): When the user gives feedback, does the agent demonstrate it HEARD the feedback?
2090
-
2091
- Respond with JSON only: [{"dimension":"consistency","score":N,"reasoning":"..."},{"dimension":"progression","score":N,"reasoning":"..."},{"dimension":"feedback_integration","score":N,"reasoning":"..."}]`
2092
- },
2093
- {
2094
- role: "user",
2095
- content: `Scenario: ${scenario.thesis}
2096
-
2097
- ${conversation}`
2098
- }
2099
- ],
2100
- temperature: 0.1,
2101
- maxTokens: 800
2102
- });
2103
- return parseJudgeResponse("coherence", resp);
2104
- };
2105
- var adversarialJudge = async (tc, { scenario, turns }) => {
2106
- const conversation = turns.map(
2107
- (t, i) => `Turn ${i + 1}:
2108
- User: ${t.userMessage}
2109
- Agent: ${t.agentResponse.slice(0, 1500)}`
2110
- ).join("\n\n---\n\n");
2111
- const resp = await tc.chat({
2112
- model: "gpt-4o",
2113
- messages: [
2114
- {
2115
- role: "system",
2116
- content: `You are a red-team evaluator. Find the WEAKEST point in the AI agent's responses. Be adversarial.
2117
-
2118
- 1. **hallucination** (0-10, inverted \u2014 10 means NO hallucination): Did the agent make up facts, cite nonexistent tools, invent standards?
2119
- 2. **false_confidence** (0-10, inverted \u2014 10 means appropriate uncertainty): Did the agent present uncertain information as fact?
2120
- 3. **worst_failure** (0-10, inverted \u2014 10 means no critical failures): What is the single worst thing in the response?
2121
-
2122
- Be harsh. If everything is genuinely good, say so \u2014 but look hard first.
2123
-
2124
- 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":"..."}]`
2125
- },
2126
- {
2127
- role: "user",
2128
- content: `Persona: ${scenario.persona}
2129
- Scenario: ${scenario.thesis}
2130
-
2131
- ${conversation}`
2132
- }
2133
- ],
2134
- temperature: 0.2,
2135
- maxTokens: 800
2136
- });
2137
- return parseJudgeResponse("adversarial", resp);
2138
- };
2139
- function createCustomJudge(name, systemPrompt, opts) {
2140
- return async (tc, { scenario, turns }) => {
2141
- const conversation = turns.map(
2142
- (t, i) => `Turn ${i + 1}:
2143
- User: ${t.userMessage}
2144
- Agent: ${t.agentResponse.slice(0, 2e3)}`
2145
- ).join("\n\n---\n\n");
2146
- const resp = await tc.chat({
2147
- model: opts?.model ?? "gpt-4o",
2148
- messages: [
2149
- {
2150
- role: "system",
2151
- content: systemPrompt
2152
- },
2153
- {
2154
- role: "user",
2155
- content: `Persona: ${scenario.persona} (${scenario.label})
2156
- Scenario: ${scenario.thesis}
2157
-
2158
- ${conversation}`
2159
- }
2160
- ],
2161
- temperature: opts?.temperature ?? 0.1,
2162
- maxTokens: opts?.maxTokens ?? 1e3
2163
- });
2164
- return parseJudgeResponse(name, resp);
2165
- };
2166
- }
2167
- function defaultJudges(domain) {
2168
- return [createDomainExpertJudge(domain), codeExecutionJudge, coherenceJudge, adversarialJudge];
2169
- }
2170
- function parseJudgeResponse(judgeName, resp) {
2171
- try {
2172
- const content = resp.choices?.[0]?.message?.content ?? "";
2173
- let cleaned = content.replace(/```json\n?|\n?```/g, "").trim();
2174
- const arrayMatch = cleaned.match(/\[[\s\S]*\]/);
2175
- if (arrayMatch) cleaned = arrayMatch[0];
2176
- const parsed = JSON.parse(cleaned);
2177
- return parsed.map((p) => ({
2178
- judgeName,
2179
- dimension: p.dimension,
2180
- score: Math.max(0, Math.min(10, p.score)),
2181
- reasoning: p.reasoning ?? "",
2182
- evidence: p.evidence
2183
- }));
2184
- } catch (err) {
2185
- const content = resp.choices?.[0]?.message?.content ?? "";
2186
- console.log(
2187
- ` [parse_error] ${judgeName}: ${err.message?.slice(0, 50)} | response: ${content.slice(0, 100)}`
2188
- );
2189
- return [
2190
- {
2191
- judgeName,
2192
- dimension: "parse_error",
2193
- score: 0,
2194
- reasoning: `Parse failed: ${err.message?.slice(0, 100)}. Raw: ${content.slice(0, 200)}`
2195
- }
2196
- ];
2197
- }
2198
- }
2199
-
2200
2214
  // src/live-proof.ts
2201
2215
  async function runLiveProof(config) {
2202
2216
  const startedAt = Date.now();
@@ -2391,16 +2405,16 @@ function aggregatePrReviewScore(dimensions, weights = {}) {
2391
2405
  return (merged.recall * clamp01(dimensions.recall) + merged.precision * clamp01(dimensions.precision) + merged.actionability * clamp01(dimensions.actionability) + merged.severityCalibration * clamp01(dimensions.severityCalibration) + merged.lowNoise * clamp01(dimensions.lowNoise)) / weightSum;
2392
2406
  }
2393
2407
  function matchReferenceFindings(references, comments) {
2394
- const matches = [];
2408
+ const matches2 = [];
2395
2409
  const usedCommentIds = /* @__PURE__ */ new Set();
2396
2410
  for (const reference of references) {
2397
2411
  const candidates = comments.filter((comment) => !usedCommentIds.has(comment.id)).map((comment) => ({ comment, score: matchScore(reference, comment) })).filter(({ score }) => score >= 0.55).sort((a, b) => b.score - a.score);
2398
2412
  const best = candidates[0];
2399
2413
  if (!best) continue;
2400
2414
  usedCommentIds.add(best.comment.id);
2401
- matches.push({ referenceId: reference.id, commentId: best.comment.id, score: best.score });
2415
+ matches2.push({ referenceId: reference.id, commentId: best.comment.id, score: best.score });
2402
2416
  }
2403
- return matches;
2417
+ return matches2;
2404
2418
  }
2405
2419
  function matchScore(reference, comment) {
2406
2420
  let score = 0;
@@ -2427,9 +2441,9 @@ function isActionableComment(comment) {
2427
2441
  body
2428
2442
  );
2429
2443
  }
2430
- function isSeverityAligned(comment, references, matches) {
2444
+ function isSeverityAligned(comment, references, matches2) {
2431
2445
  if (!comment.severity) return false;
2432
- const match = matches.find((candidate) => candidate.commentId === comment.id);
2446
+ const match = matches2.find((candidate) => candidate.commentId === comment.id);
2433
2447
  if (!match) return comment.severity === "nit" || comment.severity === "low";
2434
2448
  const reference = references.find((candidate) => candidate.id === match.referenceId);
2435
2449
  if (!reference) return false;
@@ -2725,28 +2739,28 @@ function analyzeAntiSlop(outputs, config) {
2725
2739
  }
2726
2740
  }
2727
2741
  for (const re of config.hedgingPatterns) {
2728
- const matches = output.match(
2742
+ const matches2 = output.match(
2729
2743
  new RegExp(re, re.flags.includes("g") ? re.flags : `${re.flags}g`)
2730
2744
  );
2731
- if (matches) {
2732
- counts.hedging += matches.length;
2745
+ if (matches2) {
2746
+ counts.hedging += matches2.length;
2733
2747
  issues.push({
2734
2748
  category: "hedging",
2735
- detail: `${matches.length}x ${re.source}`,
2736
- example: matches[0]
2749
+ detail: `${matches2.length}x ${re.source}`,
2750
+ example: matches2[0]
2737
2751
  });
2738
2752
  }
2739
2753
  }
2740
2754
  for (const re of config.apologyPatterns) {
2741
- const matches = output.match(
2755
+ const matches2 = output.match(
2742
2756
  new RegExp(re, re.flags.includes("g") ? re.flags : `${re.flags}g`)
2743
2757
  );
2744
- if (matches) {
2745
- counts.apology += matches.length;
2758
+ if (matches2) {
2759
+ counts.apology += matches2.length;
2746
2760
  issues.push({
2747
2761
  category: "apology",
2748
- detail: `${matches.length}x ${re.source}`,
2749
- example: matches[0]
2762
+ detail: `${matches2.length}x ${re.source}`,
2763
+ example: matches2[0]
2750
2764
  });
2751
2765
  }
2752
2766
  }
@@ -3003,6 +3017,170 @@ var DualAgentBench = class {
3003
3017
  }
3004
3018
  };
3005
3019
 
3020
+ // src/eval-tools.ts
3021
+ import { readFile } from "fs/promises";
3022
+ function toOpenAiTool(def) {
3023
+ return {
3024
+ type: "function",
3025
+ function: { name: def.name, description: def.description, parameters: def.parameters }
3026
+ };
3027
+ }
3028
+ function makeEvalTools(cfg) {
3029
+ const tools = [];
3030
+ if (cfg.judges) tools.push(runJudgesTool(cfg.judges));
3031
+ if (cfg.completion) tools.push(verifyCompletionTool(cfg.completion.checkCorrectness));
3032
+ if (cfg.analyze) tools.push(analyzeRunsTool(cfg.analyze));
3033
+ return tools;
3034
+ }
3035
+ function runJudgesTool(judges) {
3036
+ if (judges.length === 0) {
3037
+ throw new Error("makeEvalTools: cfg.judges is empty \u2014 supply judges or omit the section");
3038
+ }
3039
+ const names = judges.map((j) => j.name);
3040
+ return {
3041
+ name: "run_judges",
3042
+ 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.`,
3043
+ parameters: {
3044
+ type: "object",
3045
+ properties: {
3046
+ judge: {
3047
+ type: "string",
3048
+ enum: names,
3049
+ description: "Run only this judge. Omit to run every configured judge."
3050
+ },
3051
+ artifact: {
3052
+ description: "The artifact to score \u2014 any JSON value the judges understand."
3053
+ },
3054
+ scenario: {
3055
+ description: "Optional scenario context forwarded to the judges."
3056
+ }
3057
+ },
3058
+ required: ["artifact"],
3059
+ additionalProperties: false
3060
+ },
3061
+ handler: async (args, ctx) => {
3062
+ const a = requireObjectArgs("run_judges", args);
3063
+ if (!("artifact" in a)) {
3064
+ throw new Error("run_judges: args.artifact is required");
3065
+ }
3066
+ let selected = judges;
3067
+ if (a.judge !== void 0) {
3068
+ if (typeof a.judge !== "string") throw new Error("run_judges: args.judge must be a string");
3069
+ selected = judges.filter((j) => j.name === a.judge);
3070
+ if (selected.length === 0) {
3071
+ throw new Error(
3072
+ `run_judges: unknown judge '${a.judge}' \u2014 configured: ${names.join(", ")}`
3073
+ );
3074
+ }
3075
+ }
3076
+ const signal = ctx?.signal ?? new AbortController().signal;
3077
+ const scenario = a.scenario;
3078
+ const scores2 = {};
3079
+ for (const judge of selected) {
3080
+ if (scenario !== void 0 && judge.appliesTo && !judge.appliesTo(scenario)) continue;
3081
+ scores2[judge.name] = await judge.score({ artifact: a.artifact, scenario, signal });
3082
+ }
3083
+ return { scores: scores2 };
3084
+ }
3085
+ };
3086
+ }
3087
+ function verifyCompletionTool(checkCorrectness) {
3088
+ return {
3089
+ name: "verify_completion",
3090
+ 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).",
3091
+ parameters: {
3092
+ type: "object",
3093
+ properties: {
3094
+ gold: {
3095
+ type: "object",
3096
+ description: "TaskGold \u2014 taskId + requirements the produced state must satisfy."
3097
+ },
3098
+ state: {
3099
+ type: "object",
3100
+ description: "ProducedState \u2014 artifacts, proposals, and tool calls the agent produced."
3101
+ }
3102
+ },
3103
+ required: ["gold", "state"],
3104
+ additionalProperties: false
3105
+ },
3106
+ handler: async (args) => {
3107
+ const a = requireObjectArgs("verify_completion", args);
3108
+ if (typeof a.gold !== "object" || a.gold === null) {
3109
+ throw new Error("verify_completion: args.gold (TaskGold) is required");
3110
+ }
3111
+ if (typeof a.state !== "object" || a.state === null) {
3112
+ throw new Error("verify_completion: args.state (ProducedState) is required");
3113
+ }
3114
+ return verifyCompletion(a.gold, a.state, checkCorrectness);
3115
+ }
3116
+ };
3117
+ }
3118
+ function analyzeRunsTool(analyzeOpts) {
3119
+ return {
3120
+ name: "analyze_runs",
3121
+ 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.",
3122
+ parameters: {
3123
+ type: "object",
3124
+ properties: {
3125
+ runs: {
3126
+ type: "array",
3127
+ items: { type: "object" },
3128
+ description: "RunRecord[] inline."
3129
+ },
3130
+ path: {
3131
+ type: "string",
3132
+ description: "Host path to a JSON array or JSONL file of RunRecords."
3133
+ }
3134
+ },
3135
+ additionalProperties: false
3136
+ },
3137
+ handler: async (args) => {
3138
+ const a = requireObjectArgs("analyze_runs", args);
3139
+ const hasRuns = Array.isArray(a.runs);
3140
+ const hasPath = typeof a.path === "string" && a.path.length > 0;
3141
+ if (hasRuns === hasPath) {
3142
+ throw new Error("analyze_runs: pass exactly one of args.runs (array) or args.path (string)");
3143
+ }
3144
+ const runs = hasRuns ? a.runs : await loadRunRecords(a.path);
3145
+ if (runs.length === 0) {
3146
+ throw new Error("analyze_runs: no runs to analyze");
3147
+ }
3148
+ return analyzeRuns({ ...analyzeOpts, runs });
3149
+ }
3150
+ };
3151
+ }
3152
+ async function loadRunRecords(path) {
3153
+ const text = await readFile(path, "utf8");
3154
+ const trimmed = text.trim();
3155
+ if (trimmed.length === 0) {
3156
+ throw new Error(`analyze_runs: file '${path}' is empty`);
3157
+ }
3158
+ if (trimmed.startsWith("[")) {
3159
+ const parsed = JSON.parse(trimmed);
3160
+ if (!Array.isArray(parsed)) {
3161
+ throw new Error(`analyze_runs: file '${path}' did not parse to an array`);
3162
+ }
3163
+ return parsed;
3164
+ }
3165
+ return trimmed.split("\n").flatMap((line, i) => {
3166
+ const l = line.trim();
3167
+ if (l.length === 0) return [];
3168
+ try {
3169
+ return [JSON.parse(l)];
3170
+ } catch (err) {
3171
+ throw new Error(
3172
+ `analyze_runs: file '${path}' line ${i + 1} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
3173
+ );
3174
+ }
3175
+ });
3176
+ }
3177
+ function requireObjectArgs(tool, args) {
3178
+ if (typeof args !== "object" || args === null || Array.isArray(args)) {
3179
+ throw new Error(`${tool}: args must be an object`);
3180
+ }
3181
+ return args;
3182
+ }
3183
+
3006
3184
  // src/harness-optimizer.ts
3007
3185
  var DEFAULT_HARNESS_OBJECTIVES = [
3008
3186
  { name: "aggregate", direction: "maximize", value: (r) => r.aggregateMean },
@@ -3016,14 +3194,14 @@ async function runHarnessExperiment(config) {
3016
3194
  const score = config.score ?? ((trace) => critic.scoreTrace(trace));
3017
3195
  const results = await mapLimit(jobs, config.parallelism ?? 1, async (request) => {
3018
3196
  const trace = await config.adapter.run(request);
3019
- const runScore3 = await score(trace, request);
3197
+ const runScore4 = await score(trace, request);
3020
3198
  const result = {
3021
3199
  variant: request.variant,
3022
3200
  scenario: request.scenario,
3023
3201
  trialIndex: request.trialIndex,
3024
3202
  trace,
3025
- score: runScore3,
3026
- aggregate: aggregateRunScore(runScore3, config.weights)
3203
+ score: runScore4,
3204
+ aggregate: aggregateRunScore(runScore4, config.weights)
3027
3205
  };
3028
3206
  await config.onResult?.(result);
3029
3207
  return result;
@@ -3124,15 +3302,22 @@ function aggregateJudgeVerdicts(verdicts, dimensionKeys, weights) {
3124
3302
  for (const d of dimensionKeys) dimAcc[d] = [];
3125
3303
  let rationale = "";
3126
3304
  let costUsd = 0;
3305
+ const seenCount = /* @__PURE__ */ new Map();
3306
+ const keyFor = (model) => {
3307
+ const n = (seenCount.get(model) ?? 0) + 1;
3308
+ seenCount.set(model, n);
3309
+ return n === 1 ? model : `${model}#${n}`;
3310
+ };
3127
3311
  for (const v of verdicts) {
3128
3312
  costUsd += v.costUsd ?? 0;
3313
+ const key = keyFor(v.model);
3129
3314
  if (!v.perDimension) {
3130
- failedJudges.push(v.model);
3315
+ failedJudges.push(key);
3131
3316
  continue;
3132
3317
  }
3133
3318
  const dims = {};
3134
3319
  for (const d of dimensionKeys) dims[d] = clamp01(Number(v.perDimension[d]));
3135
- perJudge[v.model] = dims;
3320
+ perJudge[key] = dims;
3136
3321
  for (const d of dimensionKeys) dimAcc[d].push(dims[d]);
3137
3322
  if (!rationale && typeof v.rationale === "string" && v.rationale) rationale = v.rationale;
3138
3323
  }
@@ -3167,24 +3352,144 @@ function aggregateJudgeVerdicts(verdicts, dimensionKeys, weights) {
3167
3352
  maxDisagreement,
3168
3353
  failedJudges,
3169
3354
  costUsd,
3170
- rationale: rationale || "llm-judge"
3355
+ rationale: rationale || "llm-judge",
3356
+ verdicts: [...verdicts]
3171
3357
  };
3172
3358
  }
3173
3359
 
3174
- // src/judge-runner.ts
3175
- var JudgeRunner = class {
3176
- driver;
3177
- constructor(driver = new SubprocessSandboxDriver()) {
3178
- this.driver = driver;
3179
- }
3180
- async run(spec) {
3181
- const store = new InMemoryTraceStore();
3182
- const emitter = new TraceEmitter(store, { runId: `judge-${spec.id}` });
3183
- await emitter.startRun({
3184
- scenarioId: spec.id,
3185
- layer: "meta",
3186
- projectId: "judge-runner"
3187
- });
3360
+ // src/judge-retry.ts
3361
+ var DEFAULT_MAX_ATTEMPTS = 3;
3362
+ var DEFAULT_TIMEOUT_MS = 3e5;
3363
+ function sleep(ms) {
3364
+ return new Promise((resolve) => setTimeout(resolve, ms));
3365
+ }
3366
+ async function withJudgeRetry(judgeFn, policy = {}) {
3367
+ const maxAttempts = policy.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
3368
+ const timeoutMs = policy.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3369
+ const backoff = policy.backoffMs ?? backoffMs;
3370
+ const isRetryable = policy.isRetryable ?? isTransientLlmError;
3371
+ const models = policy.models && policy.models.length > 0 ? policy.models : [void 0];
3372
+ let totalAttempts = 0;
3373
+ const attemptErrors = [];
3374
+ let lastError;
3375
+ for (const model of models) {
3376
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
3377
+ totalAttempts += 1;
3378
+ const controller = new AbortController();
3379
+ const timer = setTimeout(() => controller.abort(new Error("TimeoutError")), timeoutMs);
3380
+ try {
3381
+ const value = await judgeFn(model, controller.signal);
3382
+ clearTimeout(timer);
3383
+ return {
3384
+ value,
3385
+ succeeded: true,
3386
+ attempts: totalAttempts,
3387
+ modelUsed: model,
3388
+ attemptErrors
3389
+ };
3390
+ } catch (err) {
3391
+ clearTimeout(timer);
3392
+ const errObj = err instanceof Error ? err : new Error(String(err));
3393
+ lastError = errObj;
3394
+ attemptErrors.push({
3395
+ attempt: totalAttempts,
3396
+ model: model ?? "(default)",
3397
+ error: errObj.message
3398
+ });
3399
+ if (!isRetryable(errObj)) {
3400
+ return {
3401
+ value: null,
3402
+ succeeded: false,
3403
+ attempts: totalAttempts,
3404
+ error: errObj,
3405
+ attemptErrors
3406
+ };
3407
+ }
3408
+ if (attempt < maxAttempts - 1) {
3409
+ await sleep(backoff(attempt));
3410
+ }
3411
+ }
3412
+ }
3413
+ }
3414
+ return {
3415
+ value: null,
3416
+ succeeded: false,
3417
+ attempts: totalAttempts,
3418
+ error: lastError,
3419
+ attemptErrors
3420
+ };
3421
+ }
3422
+
3423
+ // src/judge-panel.ts
3424
+ function ensembleJudge(opts) {
3425
+ if (opts.models.length === 0) {
3426
+ throw new Error(`ensembleJudge '${opts.name}': models is empty \u2014 nothing to score with`);
3427
+ }
3428
+ if (opts.dimensions.length === 0) {
3429
+ throw new Error(`ensembleJudge '${opts.name}': dimensions is empty \u2014 nothing to score`);
3430
+ }
3431
+ if (opts.crossFamily !== false) {
3432
+ assertCrossFamily(opts.models);
3433
+ }
3434
+ const scoreOne = async (model, input) => {
3435
+ if (opts.retry) {
3436
+ const outcome = await withJudgeRetry((m) => opts.scoreWith(m, input), {
3437
+ ...opts.retry,
3438
+ models: [model]
3439
+ });
3440
+ if (!outcome.succeeded || outcome.value === null) {
3441
+ return {
3442
+ model,
3443
+ perDimension: null,
3444
+ rationale: outcome.error?.message ?? "judge failed after retries"
3445
+ };
3446
+ }
3447
+ return outcome.value;
3448
+ }
3449
+ try {
3450
+ return await opts.scoreWith(model, input);
3451
+ } catch (err) {
3452
+ return {
3453
+ model,
3454
+ perDimension: null,
3455
+ rationale: err instanceof Error ? err.message : String(err)
3456
+ };
3457
+ }
3458
+ };
3459
+ return {
3460
+ name: opts.name,
3461
+ dimensions: opts.dimensions.map((d) => ({ key: d, description: d })),
3462
+ async score({ artifact, scenario }) {
3463
+ const input = { artifact, scenario };
3464
+ const verdicts = await Promise.all(opts.models.map((model) => scoreOne(model, input)));
3465
+ const agg = aggregateJudgeVerdicts(verdicts, opts.dimensions, opts.weights);
3466
+ const score = {
3467
+ dimensions: agg.perDimension,
3468
+ composite: agg.composite,
3469
+ notes: agg.rationale,
3470
+ maxDisagreement: agg.maxDisagreement,
3471
+ perJudge: agg.perJudge
3472
+ };
3473
+ if (agg.failedJudges.length > 0) score.failedJudges = agg.failedJudges;
3474
+ return score;
3475
+ }
3476
+ };
3477
+ }
3478
+
3479
+ // src/judge-runner.ts
3480
+ var JudgeRunner = class {
3481
+ driver;
3482
+ constructor(driver = new SubprocessSandboxDriver()) {
3483
+ this.driver = driver;
3484
+ }
3485
+ async run(spec) {
3486
+ const store = new InMemoryTraceStore();
3487
+ const emitter = new TraceEmitter(store, { runId: `judge-${spec.id}` });
3488
+ await emitter.startRun({
3489
+ scenarioId: spec.id,
3490
+ layer: "meta",
3491
+ projectId: "judge-runner"
3492
+ });
3188
3493
  const harness = new SandboxHarness(this.driver);
3189
3494
  const detail = await harness.run(spec.config, emitter);
3190
3495
  await emitter.endRun({ pass: detail.passed, score: detail.score, notes: `${spec.kind} judge` });
@@ -3502,7 +3807,7 @@ function createAxService(provider, apiKey, model) {
3502
3807
  });
3503
3808
  }
3504
3809
  function seededShuffle(items, seed) {
3505
- const rng = mulberry32(hashString(seed));
3810
+ const rng = mulberry322(hashString(seed));
3506
3811
  const out = [...items];
3507
3812
  for (let i = out.length - 1; i > 0; i--) {
3508
3813
  const j = Math.floor(rng() * (i + 1));
@@ -3518,7 +3823,7 @@ function hashString(value) {
3518
3823
  }
3519
3824
  return h >>> 0;
3520
3825
  }
3521
- function mulberry32(seed) {
3826
+ function mulberry322(seed) {
3522
3827
  let a = seed >>> 0;
3523
3828
  return () => {
3524
3829
  a = a + 1831565813 | 0;
@@ -3669,13 +3974,118 @@ var BudgetGuard = class {
3669
3974
  }
3670
3975
  };
3671
3976
 
3977
+ // src/cost-ledger.ts
3978
+ function modelPriceKey(model) {
3979
+ return isModelPriced(model) ? model : null;
3980
+ }
3981
+ function costForUsage(model, usage) {
3982
+ assertNonNegative(usage.inputTokens, "inputTokens");
3983
+ assertNonNegative(usage.outputTokens, "outputTokens");
3984
+ if (usage.cachedTokens !== void 0) assertNonNegative(usage.cachedTokens, "cachedTokens");
3985
+ const pricing = resolveModelPricing(model);
3986
+ if (!pricing) return { costUsd: 0, costUnknown: true };
3987
+ const billedInput = usage.inputTokens + (usage.cachedTokens ?? 0);
3988
+ return { costUsd: estimateCost(billedInput, usage.outputTokens, model), costUnknown: false };
3989
+ }
3990
+ var CostLedger = class {
3991
+ entries = [];
3992
+ completedTasks = 0;
3993
+ /**
3994
+ * Record one LLM call. The cost is computed from pricing unless
3995
+ * `actualCostUsd` is supplied (a finite observed cost from the provider
3996
+ * response), in which case `costUnknown` is false regardless of pricing.
3997
+ */
3998
+ record(input) {
3999
+ const { costUsd, costUnknown } = costForUsage(input.model, input.usage);
4000
+ const hasActual = typeof input.actualCostUsd === "number" && Number.isFinite(input.actualCostUsd);
4001
+ if (hasActual) assertNonNegative(input.actualCostUsd, "actualCostUsd");
4002
+ const entry = {
4003
+ model: input.model,
4004
+ channel: input.channel,
4005
+ inputTokens: input.usage.inputTokens,
4006
+ outputTokens: input.usage.outputTokens,
4007
+ cachedTokens: input.usage.cachedTokens,
4008
+ costUsd: hasActual ? input.actualCostUsd : costUsd,
4009
+ costUnknown: hasActual ? false : costUnknown,
4010
+ actualCostUsd: hasActual ? input.actualCostUsd : void 0,
4011
+ tags: input.tags,
4012
+ timestamp: input.timestamp ?? Date.now()
4013
+ };
4014
+ this.entries.push(entry);
4015
+ return entry;
4016
+ }
4017
+ /** Increment the completed-task counter (used for cost-per-completed-task). */
4018
+ markCompleted(count = 1) {
4019
+ if (!Number.isInteger(count) || count < 0) {
4020
+ throw new ValidationError(
4021
+ `CostLedger.markCompleted: count must be a non-negative integer, got ${count}`
4022
+ );
4023
+ }
4024
+ this.completedTasks += count;
4025
+ }
4026
+ list() {
4027
+ return [...this.entries];
4028
+ }
4029
+ summary() {
4030
+ const byChannel = /* @__PURE__ */ new Map();
4031
+ const unpriced = /* @__PURE__ */ new Set();
4032
+ let totalCost = 0;
4033
+ let inputTokens = 0;
4034
+ let outputTokens = 0;
4035
+ let cachedTokens = 0;
4036
+ for (const e of this.entries) {
4037
+ totalCost += e.costUsd;
4038
+ inputTokens += e.inputTokens;
4039
+ outputTokens += e.outputTokens;
4040
+ cachedTokens += e.cachedTokens ?? 0;
4041
+ if (e.costUnknown) unpriced.add(e.model);
4042
+ const roll = byChannel.get(e.channel) ?? {
4043
+ channel: e.channel,
4044
+ calls: 0,
4045
+ inputTokens: 0,
4046
+ outputTokens: 0,
4047
+ cachedTokens: 0,
4048
+ costUsd: 0,
4049
+ unpricedCalls: 0
4050
+ };
4051
+ roll.calls += 1;
4052
+ roll.inputTokens += e.inputTokens;
4053
+ roll.outputTokens += e.outputTokens;
4054
+ roll.cachedTokens += e.cachedTokens ?? 0;
4055
+ roll.costUsd += e.costUsd;
4056
+ if (e.costUnknown) roll.unpricedCalls += 1;
4057
+ byChannel.set(e.channel, roll);
4058
+ }
4059
+ return {
4060
+ totalCalls: this.entries.length,
4061
+ inputTokens,
4062
+ outputTokens,
4063
+ cachedTokens,
4064
+ totalCostUsd: totalCost,
4065
+ byChannel: [...byChannel.values()].sort((a, b) => a.channel.localeCompare(b.channel)),
4066
+ unpricedModels: [...unpriced].sort(),
4067
+ fullyPriced: unpriced.size === 0
4068
+ };
4069
+ }
4070
+ /** Total spend divided by completed tasks; null when nothing completed. */
4071
+ costPerCompletedTask() {
4072
+ if (this.completedTasks === 0) return null;
4073
+ return this.summary().totalCostUsd / this.completedTasks;
4074
+ }
4075
+ };
4076
+ function assertNonNegative(n, name) {
4077
+ if (!Number.isFinite(n) || n < 0) {
4078
+ throw new ValidationError(`CostLedger: ${name} must be a non-negative finite number, got ${n}`);
4079
+ }
4080
+ }
4081
+
3672
4082
  // src/cost-tracker.ts
3673
4083
  var CostTracker = class {
3674
4084
  byScenario = /* @__PURE__ */ new Map();
3675
4085
  record(entry) {
3676
4086
  const full = { timestamp: entry.timestamp ?? Date.now(), ...entry };
3677
- assertNonNegative(full.inputTokens, "inputTokens");
3678
- assertNonNegative(full.outputTokens, "outputTokens");
4087
+ assertNonNegative2(full.inputTokens, "inputTokens");
4088
+ assertNonNegative2(full.outputTokens, "outputTokens");
3679
4089
  let bucket = this.byScenario.get(full.scenarioId);
3680
4090
  if (!bucket) {
3681
4091
  bucket = {
@@ -3754,12 +4164,433 @@ function costFor(entry) {
3754
4164
  }
3755
4165
  return estimateCost(entry.inputTokens, entry.outputTokens, entry.model);
3756
4166
  }
3757
- function assertNonNegative(n, name) {
4167
+ function assertNonNegative2(n, name) {
3758
4168
  if (!Number.isFinite(n) || n < 0) {
3759
4169
  throw new Error(`CostTracker: ${name} must be a non-negative finite number, got ${n}`);
3760
4170
  }
3761
4171
  }
3762
4172
 
4173
+ // src/eval-trace-store.ts
4174
+ function runScore(record) {
4175
+ const { holdoutScore, searchScore } = record.outcome;
4176
+ if (typeof holdoutScore === "number") return holdoutScore;
4177
+ if (typeof searchScore === "number") return searchScore;
4178
+ throw new ValidationError(
4179
+ `EvalTraceStore: run ${record.runId} has neither holdoutScore nor searchScore`
4180
+ );
4181
+ }
4182
+ function matches(record, f) {
4183
+ if (f.experimentId && record.experimentId !== f.experimentId) return false;
4184
+ if (f.candidateId && record.candidateId !== f.candidateId) return false;
4185
+ if (f.scenarioId && record.scenarioId !== f.scenarioId) return false;
4186
+ if (f.model && record.model !== f.model) return false;
4187
+ if (f.splitTag && record.splitTag !== f.splitTag) return false;
4188
+ if (f.minScore !== void 0 && runScore(record) < f.minScore) return false;
4189
+ if (f.maxScore !== void 0 && runScore(record) > f.maxScore) return false;
4190
+ if (f.rawEquals && record.outcome.raw[f.rawEquals.key] !== f.rawEquals.value) return false;
4191
+ if (f.where && !f.where(record)) return false;
4192
+ return true;
4193
+ }
4194
+ function inMemoryRunRecordBackend(initial = []) {
4195
+ const rows = initial.map((r) => validateRunRecord(r));
4196
+ return {
4197
+ async append(record) {
4198
+ rows.push(record);
4199
+ },
4200
+ async load() {
4201
+ return [...rows];
4202
+ }
4203
+ };
4204
+ }
4205
+ function jsonlRunRecordBackend(path, opts = {}) {
4206
+ return {
4207
+ async append(record) {
4208
+ const fs2 = await import("fs/promises");
4209
+ const pathMod = await import("path");
4210
+ await fs2.mkdir(pathMod.dirname(path), { recursive: true });
4211
+ await fs2.appendFile(path, `${JSON.stringify(record)}
4212
+ `, "utf8");
4213
+ },
4214
+ async load() {
4215
+ const fs2 = await import("fs/promises");
4216
+ let raw;
4217
+ try {
4218
+ raw = await fs2.readFile(path, "utf8");
4219
+ } catch (err) {
4220
+ if (err.code === "ENOENT") return [];
4221
+ throw err;
4222
+ }
4223
+ const out = [];
4224
+ let lineNo = 0;
4225
+ for (const line of raw.split("\n")) {
4226
+ lineNo++;
4227
+ if (!line.trim()) continue;
4228
+ let parsed;
4229
+ try {
4230
+ parsed = JSON.parse(line);
4231
+ } catch (err) {
4232
+ if (opts.skipInvalid) continue;
4233
+ throw new ValidationError(`EvalTraceStore: ${path}:${lineNo} is not valid JSON`);
4234
+ }
4235
+ if (opts.skipInvalid) {
4236
+ if (isRunRecord(parsed)) out.push(parsed);
4237
+ continue;
4238
+ }
4239
+ try {
4240
+ out.push(validateRunRecord(parsed));
4241
+ } catch (err) {
4242
+ throw new ValidationError(
4243
+ `EvalTraceStore: ${path}:${lineNo} is not a valid RunRecord \u2014 ${err.message}`
4244
+ );
4245
+ }
4246
+ }
4247
+ return out;
4248
+ }
4249
+ };
4250
+ }
4251
+ var EvalTraceStore = class {
4252
+ backend;
4253
+ constructor(backend = inMemoryRunRecordBackend()) {
4254
+ this.backend = backend;
4255
+ }
4256
+ /** Validate and append one run. Throws on an invalid record — the corpus
4257
+ * stays paper-grade. */
4258
+ async append(record) {
4259
+ await this.backend.append(validateRunRecord(record));
4260
+ }
4261
+ async all() {
4262
+ return this.backend.load();
4263
+ }
4264
+ async query(filter = {}) {
4265
+ const rows = await this.backend.load();
4266
+ return rows.filter((r) => matches(r, filter));
4267
+ }
4268
+ /**
4269
+ * Highest-scoring run for a scenario (optionally restricted to a candidate).
4270
+ * Returns null when no run matches. Ties resolve to the earliest-appended run
4271
+ * so the result is stable.
4272
+ */
4273
+ async getBest(scenarioId, opts = {}) {
4274
+ const rows = await this.query({
4275
+ scenarioId,
4276
+ candidateId: opts.candidateId,
4277
+ splitTag: opts.splitTag
4278
+ });
4279
+ if (rows.length === 0) return null;
4280
+ let best = rows[0];
4281
+ let bestScore = runScore(best);
4282
+ for (let i = 1; i < rows.length; i++) {
4283
+ const s = runScore(rows[i]);
4284
+ if (s > bestScore) {
4285
+ best = rows[i];
4286
+ bestScore = s;
4287
+ }
4288
+ }
4289
+ return best;
4290
+ }
4291
+ /**
4292
+ * Compare two candidates on the scenarios they BOTH ran. When a candidate
4293
+ * ran a scenario more than once, its best `runScore` for that scenario is
4294
+ * used. Throws when there is no paired scenario — an unpaired "comparison" is
4295
+ * not one.
4296
+ */
4297
+ async compareRuns(candidateA, candidateB) {
4298
+ if (candidateA === candidateB) {
4299
+ throw new ValidationError(
4300
+ `EvalTraceStore.compareRuns: candidates must differ ("${candidateA}")`
4301
+ );
4302
+ }
4303
+ const rows = await this.backend.load();
4304
+ const bestByScenario = (candidate) => {
4305
+ const m = /* @__PURE__ */ new Map();
4306
+ for (const r of rows) {
4307
+ if (r.candidateId !== candidate) continue;
4308
+ const sid = r.scenarioId;
4309
+ if (!sid) continue;
4310
+ const s = runScore(r);
4311
+ const prev = m.get(sid);
4312
+ if (prev === void 0 || s > prev) m.set(sid, s);
4313
+ }
4314
+ return m;
4315
+ };
4316
+ const aScores = bestByScenario(candidateA);
4317
+ const bScores = bestByScenario(candidateB);
4318
+ const paired = [...aScores.keys()].filter((sid) => bScores.has(sid)).sort();
4319
+ if (paired.length === 0) {
4320
+ throw new ValidationError(
4321
+ `EvalTraceStore.compareRuns: "${candidateA}" and "${candidateB}" share no scenario (need scenarioId on records)`
4322
+ );
4323
+ }
4324
+ let sumA = 0;
4325
+ let sumB = 0;
4326
+ let bWins = 0;
4327
+ let aWins = 0;
4328
+ let ties = 0;
4329
+ for (const sid of paired) {
4330
+ const sa = aScores.get(sid);
4331
+ const sb = bScores.get(sid);
4332
+ sumA += sa;
4333
+ sumB += sb;
4334
+ if (sb > sa) bWins++;
4335
+ else if (sb < sa) aWins++;
4336
+ else ties++;
4337
+ }
4338
+ const meanA = sumA / paired.length;
4339
+ const meanB = sumB / paired.length;
4340
+ return {
4341
+ a: candidateA,
4342
+ b: candidateB,
4343
+ pairedScenarioIds: paired,
4344
+ meanA,
4345
+ meanB,
4346
+ meanDelta: meanB - meanA,
4347
+ bWins,
4348
+ ties,
4349
+ aWins
4350
+ };
4351
+ }
4352
+ };
4353
+
4354
+ // src/experiment-tracker.ts
4355
+ import { execSync } from "child_process";
4356
+ var DEFAULTS = {
4357
+ keepThreshold: 5,
4358
+ regressionThreshold: 5,
4359
+ iqrUnstableAbove: 10,
4360
+ stddevUnstableAbove: Number.POSITIVE_INFINITY,
4361
+ minRepsForVerdict: 3
4362
+ };
4363
+ function resolveThresholds(t) {
4364
+ const r = { ...DEFAULTS, ...t ?? {} };
4365
+ if (r.keepThreshold < 0) {
4366
+ throw new ValidationError(
4367
+ `experiment-tracker: keepThreshold must be >= 0, got ${r.keepThreshold}`
4368
+ );
4369
+ }
4370
+ if (r.regressionThreshold < 0) {
4371
+ throw new ValidationError(
4372
+ `experiment-tracker: regressionThreshold must be >= 0, got ${r.regressionThreshold}`
4373
+ );
4374
+ }
4375
+ if (r.minRepsForVerdict < 1) {
4376
+ throw new ValidationError(
4377
+ `experiment-tracker: minRepsForVerdict must be >= 1, got ${r.minRepsForVerdict}`
4378
+ );
4379
+ }
4380
+ return r;
4381
+ }
4382
+ function median(sorted) {
4383
+ const n = sorted.length;
4384
+ if (n === 0) return 0;
4385
+ const mid = Math.floor(n / 2);
4386
+ return n % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
4387
+ }
4388
+ function stddev(values, mean5) {
4389
+ if (values.length < 2) return 0;
4390
+ const variance = values.reduce((acc, v) => acc + (v - mean5) ** 2, 0) / values.length;
4391
+ return Math.sqrt(variance);
4392
+ }
4393
+ function computeExperimentStats(reps, thresholds) {
4394
+ const t = resolveThresholds(thresholds);
4395
+ const n = reps.length;
4396
+ if (n === 0) {
4397
+ return {
4398
+ median: 0,
4399
+ mean: 0,
4400
+ min: 0,
4401
+ max: 0,
4402
+ iqr: 0,
4403
+ stddev: 0,
4404
+ passRate: null,
4405
+ n: 0,
4406
+ stable: false
4407
+ };
4408
+ }
4409
+ const scores2 = reps.map((r) => {
4410
+ if (!Number.isFinite(r.score)) {
4411
+ throw new ValidationError(`experiment-tracker: rep ${r.rep} has non-finite score ${r.score}`);
4412
+ }
4413
+ return r.score;
4414
+ });
4415
+ const sorted = [...scores2].sort((a, b) => a - b);
4416
+ const mean5 = scores2.reduce((s, v) => s + v, 0) / n;
4417
+ const sd = stddev(scores2, mean5);
4418
+ const spread = iqr(scores2);
4419
+ const rated = reps.filter((r) => typeof r.passed === "boolean");
4420
+ const passRate = rated.length === 0 ? null : rated.filter((r) => r.passed).length / rated.length;
4421
+ const stable = spread < t.iqrUnstableAbove && sd < t.stddevUnstableAbove;
4422
+ return {
4423
+ median: median(sorted),
4424
+ mean: mean5,
4425
+ min: sorted[0],
4426
+ max: sorted[n - 1],
4427
+ iqr: spread,
4428
+ stddev: sd,
4429
+ passRate,
4430
+ n,
4431
+ stable
4432
+ };
4433
+ }
4434
+ function improvementVerdict(candidate, parent, thresholds) {
4435
+ const t = resolveThresholds(thresholds);
4436
+ if (!parent) {
4437
+ return {
4438
+ verdict: "ITERATE",
4439
+ medianDelta: null,
4440
+ reason: "no parent experiment to compare against"
4441
+ };
4442
+ }
4443
+ if (candidate.n < t.minRepsForVerdict || parent.n < t.minRepsForVerdict) {
4444
+ return {
4445
+ verdict: "ITERATE",
4446
+ medianDelta: null,
4447
+ reason: `need >= ${t.minRepsForVerdict} reps on both sides (candidate n=${candidate.n}, parent n=${parent.n})`
4448
+ };
4449
+ }
4450
+ if (!candidate.stable) {
4451
+ return {
4452
+ verdict: "NOISE",
4453
+ medianDelta: candidate.median - parent.median,
4454
+ reason: `candidate unstable (iqr=${candidate.iqr}, stddev=${candidate.stddev.toFixed(2)})`
4455
+ };
4456
+ }
4457
+ const medianDelta2 = candidate.median - parent.median;
4458
+ if (medianDelta2 > t.keepThreshold) {
4459
+ return { verdict: "KEEP", medianDelta: medianDelta2, reason: `median +${medianDelta2} > +${t.keepThreshold}` };
4460
+ }
4461
+ if (medianDelta2 < -t.regressionThreshold) {
4462
+ return {
4463
+ verdict: "REGRESSION",
4464
+ medianDelta: medianDelta2,
4465
+ reason: `median ${medianDelta2} < -${t.regressionThreshold}`
4466
+ };
4467
+ }
4468
+ return {
4469
+ verdict: "NOISE",
4470
+ medianDelta: medianDelta2,
4471
+ reason: `median delta ${medianDelta2} inside noise band [-${t.regressionThreshold}, +${t.keepThreshold}]`
4472
+ };
4473
+ }
4474
+ var gitProvenanceReader = () => {
4475
+ const run = (cmd) => execSync(cmd, { encoding: "utf8" }).trim();
4476
+ const commit = run("git rev-parse --short HEAD");
4477
+ const message = run("git log -1 --format=%s");
4478
+ const changedRaw = run("git diff --name-only HEAD~1");
4479
+ const changedFiles = changedRaw.length === 0 ? [] : changedRaw.split("\n").filter(Boolean);
4480
+ return { commit, message, changedFiles };
4481
+ };
4482
+ function inMemoryExperimentStore(initial = []) {
4483
+ let state = initial.map((e) => structuredClone(e));
4484
+ return {
4485
+ async load() {
4486
+ return state.map((e) => structuredClone(e));
4487
+ },
4488
+ async save(experiments) {
4489
+ state = experiments.map((e) => structuredClone(e));
4490
+ }
4491
+ };
4492
+ }
4493
+ function fileExperimentStore(path) {
4494
+ return {
4495
+ async load() {
4496
+ const fs2 = await import("fs/promises");
4497
+ try {
4498
+ const raw = await fs2.readFile(path, "utf8");
4499
+ const parsed = JSON.parse(raw);
4500
+ if (!Array.isArray(parsed)) {
4501
+ throw new ValidationError(`experiment-tracker: store at ${path} is not a JSON array`);
4502
+ }
4503
+ return parsed;
4504
+ } catch (err) {
4505
+ if (err.code === "ENOENT") return [];
4506
+ throw err;
4507
+ }
4508
+ },
4509
+ async save(experiments) {
4510
+ const fs2 = await import("fs/promises");
4511
+ const pathMod = await import("path");
4512
+ await fs2.mkdir(pathMod.dirname(path), { recursive: true });
4513
+ await fs2.writeFile(path, JSON.stringify(experiments, null, 2), "utf8");
4514
+ }
4515
+ };
4516
+ }
4517
+ var ExperimentTracker = class {
4518
+ store;
4519
+ provenanceReader;
4520
+ thresholds;
4521
+ now;
4522
+ constructor(options = {}) {
4523
+ this.store = options.store ?? inMemoryExperimentStore();
4524
+ this.provenanceReader = options.provenanceReader ?? gitProvenanceReader;
4525
+ this.thresholds = resolveThresholds(options.thresholds);
4526
+ this.now = options.now ?? Date.now;
4527
+ }
4528
+ async create(input) {
4529
+ const experiments = await this.store.load();
4530
+ if (experiments.some((e) => e.id === input.id)) {
4531
+ throw new ValidationError(`experiment-tracker: experiment id "${input.id}" already exists`);
4532
+ }
4533
+ if (input.parentId && !experiments.some((e) => e.id === input.parentId)) {
4534
+ throw new ValidationError(
4535
+ `experiment-tracker: parent experiment "${input.parentId}" not found`
4536
+ );
4537
+ }
4538
+ const provenance = input.provenance ?? await this.provenanceReader();
4539
+ const experiment = {
4540
+ id: input.id,
4541
+ label: input.label,
4542
+ provenance,
4543
+ parentId: input.parentId,
4544
+ changeSummary: input.changeSummary,
4545
+ reps: [],
4546
+ stats: computeExperimentStats([], this.thresholds),
4547
+ verdict: "ITERATE",
4548
+ createdAt: new Date(this.now()).toISOString()
4549
+ };
4550
+ experiments.push(experiment);
4551
+ await this.store.save(experiments);
4552
+ return structuredClone(experiment);
4553
+ }
4554
+ /** Append a rep (its `rep` index defaults to the current rep count) and
4555
+ * recompute stats + verdict. Returns the updated experiment. */
4556
+ async addRep(experimentId, rep) {
4557
+ const experiments = await this.store.load();
4558
+ const exp = experiments.find((e) => e.id === experimentId);
4559
+ if (!exp)
4560
+ throw new ValidationError(`experiment-tracker: experiment "${experimentId}" not found`);
4561
+ const fullRep = {
4562
+ rep: rep.rep ?? exp.reps.length,
4563
+ score: rep.score,
4564
+ passed: rep.passed,
4565
+ metrics: rep.metrics,
4566
+ timestamp: rep.timestamp ?? new Date(this.now()).toISOString()
4567
+ };
4568
+ exp.reps.push(fullRep);
4569
+ exp.stats = computeExperimentStats(exp.reps, this.thresholds);
4570
+ const parent = exp.parentId ? experiments.find((e) => e.id === exp.parentId) : void 0;
4571
+ exp.verdict = improvementVerdict(exp.stats, parent?.stats ?? null, this.thresholds).verdict;
4572
+ await this.store.save(experiments);
4573
+ return structuredClone(exp);
4574
+ }
4575
+ async get(experimentId) {
4576
+ const experiments = await this.store.load();
4577
+ const found = experiments.find((e) => e.id === experimentId);
4578
+ return found ? structuredClone(found) : void 0;
4579
+ }
4580
+ async list() {
4581
+ return this.store.load();
4582
+ }
4583
+ /** Full verdict (not just the enum) for an experiment vs its parent. */
4584
+ async verdictFor(experimentId) {
4585
+ const experiments = await this.store.load();
4586
+ const exp = experiments.find((e) => e.id === experimentId);
4587
+ if (!exp)
4588
+ throw new ValidationError(`experiment-tracker: experiment "${experimentId}" not found`);
4589
+ const parent = exp.parentId ? experiments.find((e) => e.id === exp.parentId) : void 0;
4590
+ return improvementVerdict(exp.stats, parent?.stats ?? null, this.thresholds);
4591
+ }
4592
+ };
4593
+
3763
4594
  // src/muffled-gate-scanner.ts
3764
4595
  import { existsSync, readdirSync, readFileSync, statSync } from "fs";
3765
4596
  import { join } from "path";
@@ -3872,7 +4703,7 @@ var DEFAULT_FINDERS = [
3872
4703
  ];
3873
4704
  var UNIVERSAL_FINDERS = [findConstructorCwdDropped];
3874
4705
  function autoDeriveImporters(repoRoot, roots, extensions, importsContain) {
3875
- const matches = [];
4706
+ const matches2 = [];
3876
4707
  const walk = (rel) => {
3877
4708
  const abs = join(repoRoot, rel);
3878
4709
  if (!existsSync(abs)) return;
@@ -3898,12 +4729,12 @@ function autoDeriveImporters(repoRoot, roots, extensions, importsContain) {
3898
4729
  } catch {
3899
4730
  continue;
3900
4731
  }
3901
- if (text.includes(importsContain)) matches.push(sub);
4732
+ if (text.includes(importsContain)) matches2.push(sub);
3902
4733
  }
3903
4734
  }
3904
4735
  };
3905
4736
  for (const r of roots) walk(r);
3906
- return matches;
4737
+ return matches2;
3907
4738
  }
3908
4739
  function scanForMuffledGates(opts) {
3909
4740
  const findings = [];
@@ -4075,16 +4906,80 @@ function isObject(v) {
4075
4906
  return typeof v === "object" && v !== null && !Array.isArray(v);
4076
4907
  }
4077
4908
 
4909
+ // src/partition-held-out.ts
4910
+ function fnv1a32(input) {
4911
+ let h = 2166136261;
4912
+ for (let i = 0; i < input.length; i++) {
4913
+ h ^= input.charCodeAt(i) & 255;
4914
+ h = Math.imul(h, 16777619);
4915
+ }
4916
+ return h >>> 0;
4917
+ }
4918
+ function hashToUnit(id, seed) {
4919
+ return fnv1a32(`${id}
4920
+ ${seed}`) / 4294967296;
4921
+ }
4922
+ function assignHeldOutTag(id, options = {}) {
4923
+ const seed = options.seed ?? "held-out-v1";
4924
+ const holdoutFraction = options.holdoutFraction ?? 0.5;
4925
+ assertFraction(holdoutFraction);
4926
+ return hashToUnit(id, seed) < holdoutFraction ? "holdout" : "search";
4927
+ }
4928
+ function partitionHeldOut(ids, options = {}) {
4929
+ const seed = options.seed ?? "held-out-v1";
4930
+ const holdoutFraction = options.holdoutFraction ?? 0.5;
4931
+ const minHoldout = options.minHoldout ?? 1;
4932
+ const minSearch = options.minSearch ?? 1;
4933
+ assertFraction(holdoutFraction);
4934
+ if (ids.length === 0) {
4935
+ throw new ValidationError("partitionHeldOut: no ids supplied");
4936
+ }
4937
+ const seen = /* @__PURE__ */ new Set();
4938
+ const search = [];
4939
+ const holdout = [];
4940
+ for (const id of ids) {
4941
+ if (typeof id !== "string" || id.length === 0) {
4942
+ throw new ValidationError(
4943
+ `partitionHeldOut: ids must be non-empty strings, got ${JSON.stringify(id)}`
4944
+ );
4945
+ }
4946
+ if (seen.has(id)) {
4947
+ throw new ValidationError(
4948
+ `partitionHeldOut: duplicate id "${id}" \u2014 a paired comparison must not double-count`
4949
+ );
4950
+ }
4951
+ seen.add(id);
4952
+ if (hashToUnit(id, seed) < holdoutFraction) holdout.push(id);
4953
+ else search.push(id);
4954
+ }
4955
+ if (holdout.length < minHoldout) {
4956
+ throw new ValidationError(
4957
+ `partitionHeldOut: holdout set has ${holdout.length} id(s), below the floor of ${minHoldout} (n=${ids.length}, holdoutFraction=${holdoutFraction}) \u2014 too few for a significant comparison`
4958
+ );
4959
+ }
4960
+ if (search.length < minSearch) {
4961
+ throw new ValidationError(
4962
+ `partitionHeldOut: search set has ${search.length} id(s), below the floor of ${minSearch} (n=${ids.length}, holdoutFraction=${holdoutFraction})`
4963
+ );
4964
+ }
4965
+ return { search, holdout, seed, holdoutFraction };
4966
+ }
4967
+ function assertFraction(f) {
4968
+ if (!Number.isFinite(f) || f <= 0 || f >= 1) {
4969
+ throw new ValidationError(`partitionHeldOut: holdoutFraction must be in (0, 1), got ${f}`);
4970
+ }
4971
+ }
4972
+
4078
4973
  // src/scorecard.ts
4079
4974
  import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "fs";
4080
4975
  import { dirname } from "path";
4081
- function median(xs) {
4976
+ function median2(xs) {
4082
4977
  if (xs.length === 0) return 0;
4083
4978
  const sorted = [...xs].sort((a, b) => a - b);
4084
4979
  const mid = Math.floor(sorted.length / 2);
4085
4980
  return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
4086
4981
  }
4087
- function runScore(run) {
4982
+ function runScore2(run) {
4088
4983
  return run.outcome.holdoutScore ?? run.outcome.searchScore;
4089
4984
  }
4090
4985
  function aggregatePerDimension(runs) {
@@ -4118,14 +5013,14 @@ function recordRuns(runs, opts) {
4118
5013
  }
4119
5014
  const lines = [];
4120
5015
  for (const [scenarioId, scenarioRuns] of byScenario) {
4121
- const scored = scenarioRuns.map((run) => ({ run, score: runScore(run) })).filter((s) => s.score !== void 0);
5016
+ const scored = scenarioRuns.map((run) => ({ run, score: runScore2(run) })).filter((s) => s.score !== void 0);
4122
5017
  if (scored.length === 0) continue;
4123
5018
  const scores2 = scored.map((s) => s.score);
4124
5019
  const entry = {
4125
5020
  commitSha: opts.commitSha,
4126
5021
  timestamp,
4127
5022
  scores: scores2,
4128
- composite: median(scores2),
5023
+ composite: median2(scores2),
4129
5024
  runIds: scored.map((s) => s.run.runId)
4130
5025
  };
4131
5026
  const perDimension = aggregatePerDimension(scenarioRuns);
@@ -4263,42 +5158,6 @@ function formatScorecardDiff(diff) {
4263
5158
  return lines.join("\n");
4264
5159
  }
4265
5160
 
4266
- // src/series-convergence.ts
4267
- function analyzeSeries(values, options = {}) {
4268
- const window = options.window ?? 5;
4269
- const stableCv = options.stableCv ?? 0.05;
4270
- const driftRun = options.driftRun ?? 3;
4271
- if (values.length < Math.max(2, Math.min(window, 3))) {
4272
- return { state: "insufficient-data", windowMean: 0, windowCv: 0, tailRun: 0, stable: false };
4273
- }
4274
- const tail = values.slice(-window);
4275
- const mean5 = tail.reduce((a, b) => a + b, 0) / tail.length;
4276
- const variance = tail.reduce((acc, v) => acc + (v - mean5) ** 2, 0) / tail.length;
4277
- const stdDev = Math.sqrt(variance);
4278
- const refMean = Math.abs(mean5) > 1e-9 ? Math.abs(mean5) : 1;
4279
- const cv = stdDev / refMean;
4280
- const stable = tail.length >= window && cv <= stableCv;
4281
- let tailRun = 0;
4282
- let direction = 0;
4283
- for (let i = values.length - 1; i > 0; i--) {
4284
- const delta = values[i] - values[i - 1];
4285
- if (delta === 0) break;
4286
- const dir = delta > 0 ? 1 : -1;
4287
- if (direction === 0) direction = dir;
4288
- if (dir !== direction) break;
4289
- tailRun += dir;
4290
- }
4291
- let state;
4292
- if (stable) {
4293
- state = "stabilized";
4294
- } else if (Math.abs(tailRun) >= driftRun) {
4295
- state = tailRun > 0 ? "drifting-up" : "drifting-down";
4296
- } else {
4297
- state = "noisy";
4298
- }
4299
- return { state, windowMean: mean5, windowCv: cv, tailRun, stable };
4300
- }
4301
-
4302
5161
  // src/slo.ts
4303
5162
  function checkSlos(metrics, slos) {
4304
5163
  const results = slos.map((slo) => check(slo, metrics[slo.metric]));
@@ -4385,70 +5244,445 @@ function scoreContinuity(pair, checks, options = {}) {
4385
5244
  if (checks.length === 0) {
4386
5245
  throw new Error("scoreContinuity: at least 1 check required");
4387
5246
  }
4388
- const passThreshold = options.passThreshold ?? 0.8;
4389
- const results = checks.map((c) => {
4390
- const raw = c.score(pair);
4391
- const clamped = Number.isFinite(raw) ? Math.max(0, Math.min(1, raw)) : 0;
4392
- return { id: c.id, description: c.description, score: clamped, pass: clamped >= passThreshold };
4393
- });
4394
- const overallScore = results.reduce((a, r) => a + r.score, 0) / results.length;
4395
- return { results, overallScore, pass: results.every((r) => r.pass) };
5247
+ const passThreshold = options.passThreshold ?? 0.8;
5248
+ const results = checks.map((c) => {
5249
+ const raw = c.score(pair);
5250
+ const clamped = Number.isFinite(raw) ? Math.max(0, Math.min(1, raw)) : 0;
5251
+ return { id: c.id, description: c.description, score: clamped, pass: clamped >= passThreshold };
5252
+ });
5253
+ const overallScore = results.reduce((a, r) => a + r.score, 0) / results.length;
5254
+ return { results, overallScore, pass: results.every((r) => r.pass) };
5255
+ }
5256
+ function keyPreserved(key) {
5257
+ return {
5258
+ id: `preserved(${key})`,
5259
+ description: `"${key}" unchanged from before to after`,
5260
+ score: ({ before, after }) => before[key] !== void 0 && before[key] === after[key] ? 1 : 0
5261
+ };
5262
+ }
5263
+ function collectionPreserved(key, minRatio = 1) {
5264
+ return {
5265
+ id: `collection-preserved(${key})`,
5266
+ description: `"${key}" length \u2265 ${minRatio} \xD7 prior length`,
5267
+ score: ({ before, after }) => {
5268
+ const b = before[key];
5269
+ const a = after[key];
5270
+ if (!Array.isArray(b) || !Array.isArray(a)) return 0;
5271
+ if (b.length === 0) return a.length === 0 ? 1 : 1;
5272
+ return Math.min(1, a.length / (b.length * minRatio));
5273
+ }
5274
+ };
5275
+ }
5276
+ function statusAdvanced(key, progression) {
5277
+ return {
5278
+ id: `status-advanced(${key})`,
5279
+ description: `"${key}" progressed along ${progression.join("\u2192")}`,
5280
+ score: ({ before, after }) => {
5281
+ const bi = progression.indexOf(String(before[key]));
5282
+ const ai2 = progression.indexOf(String(after[key]));
5283
+ if (bi === -1 || ai2 === -1) return 0;
5284
+ return ai2 >= bi ? 1 : 0;
5285
+ }
5286
+ };
5287
+ }
5288
+
5289
+ // src/ui-finding.ts
5290
+ var UI_LENSES = [
5291
+ "consistency",
5292
+ "hierarchy",
5293
+ "layout",
5294
+ "ux-flow",
5295
+ "duplication",
5296
+ "accessibility",
5297
+ "responsive",
5298
+ "states",
5299
+ "content",
5300
+ "interaction",
5301
+ "performance-perceived",
5302
+ "other"
5303
+ ];
5304
+ var UI_FINDING_SEVERITIES = [
5305
+ "critical",
5306
+ "high",
5307
+ "med",
5308
+ "low"
5309
+ ];
5310
+
5311
+ // src/trace-contracts.ts
5312
+ function isSerializedRegex(v) {
5313
+ return typeof v === "object" && v !== null && typeof v.$regex === "string" && typeof v.flags === "string";
5314
+ }
5315
+ function matchText(actual, matcher) {
5316
+ if (typeof actual !== "string") return false;
5317
+ if (typeof matcher === "string") return actual === matcher;
5318
+ if (matcher instanceof RegExp) return matcher.test(actual);
5319
+ return new RegExp(matcher.$regex, matcher.flags).test(actual);
5320
+ }
5321
+ function resolveToolName(span) {
5322
+ if (typeof span.toolName === "string") return span.toolName;
5323
+ const fromAttr = span.attributes?.["tool.name"] ?? span.attributes?.["toolName"];
5324
+ if (typeof fromAttr === "string") return fromAttr;
5325
+ if (span.kind === "tool" && typeof span.name === "string") return span.name;
5326
+ return void 0;
5327
+ }
5328
+ function assertPredicate(value, where) {
5329
+ if (value === null || typeof value !== "object") {
5330
+ throw new ValidationError(`${where}: predicate must be an object, got ${typeof value}`);
5331
+ }
5332
+ const p = value;
5333
+ for (const field of ["name", "tool"]) {
5334
+ const m = p[field];
5335
+ if (m !== void 0 && typeof m !== "string" && !(m instanceof RegExp) && !isSerializedRegex(m)) {
5336
+ throw new ValidationError(`${where}: "${field}" must be string | RegExp | SerializedRegex`);
5337
+ }
5338
+ }
5339
+ if (p.attr !== void 0 && (p.attr === null || typeof p.attr !== "object" || Array.isArray(p.attr))) {
5340
+ throw new ValidationError(`${where}: "attr" must be a plain object`);
5341
+ }
5342
+ if (p.requiresCustom && typeof p.custom !== "function") {
5343
+ throw new ValidationError(
5344
+ `${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`
5345
+ );
5346
+ }
5347
+ const hasAttr = p.attr !== void 0 && Object.keys(p.attr).length > 0;
5348
+ if (p.name === void 0 && p.tool === void 0 && !hasAttr && typeof p.custom !== "function") {
5349
+ throw new ValidationError(
5350
+ `${where}: empty predicate would match every span \u2014 specify name, tool, attr, or custom`
5351
+ );
5352
+ }
5353
+ }
5354
+ function matchSpan(span, predicate) {
5355
+ assertPredicate(predicate, "matchSpan");
5356
+ if (predicate.name !== void 0 && !matchText(span.name, predicate.name)) return false;
5357
+ if (predicate.tool !== void 0 && !matchText(resolveToolName(span), predicate.tool))
5358
+ return false;
5359
+ if (predicate.attr !== void 0) {
5360
+ for (const [key, expected] of Object.entries(predicate.attr)) {
5361
+ const actual = span.attributes?.[key];
5362
+ if (expected instanceof RegExp || isSerializedRegex(expected)) {
5363
+ if (!matchText(typeof actual === "string" ? actual : void 0, expected)) {
5364
+ return false;
5365
+ }
5366
+ } else if (actual !== expected) {
5367
+ return false;
5368
+ }
5369
+ }
5370
+ }
5371
+ if (predicate.custom !== void 0 && !predicate.custom(span)) return false;
5372
+ return true;
5373
+ }
5374
+ function describeMatcher(m) {
5375
+ if (typeof m === "string") return m;
5376
+ if (m instanceof RegExp) return `/${m.source}/${m.flags}`;
5377
+ return `/${m.$regex}/${m.flags}`;
5378
+ }
5379
+ function describePredicate(p) {
5380
+ const parts = [];
5381
+ if (p.name !== void 0) parts.push(`name=${describeMatcher(p.name)}`);
5382
+ if (p.tool !== void 0) parts.push(`tool=${describeMatcher(p.tool)}`);
5383
+ if (p.attr !== void 0) {
5384
+ for (const [k, v] of Object.entries(p.attr)) {
5385
+ parts.push(
5386
+ `attr.${k}=${v instanceof RegExp || isSerializedRegex(v) ? describeMatcher(v) : JSON.stringify(v)}`
5387
+ );
5388
+ }
5389
+ }
5390
+ if (typeof p.custom === "function") parts.push(`custom=${p.custom.name || "fn"}`);
5391
+ return parts.join(",");
5392
+ }
5393
+ function normalizeMatcher(m) {
5394
+ return m instanceof RegExp ? { $regex: m.source, flags: m.flags } : m;
5395
+ }
5396
+ function normalizePredicate(p, where) {
5397
+ assertPredicate(p, where);
5398
+ const out = {};
5399
+ if (p.name !== void 0) out.name = normalizeMatcher(p.name);
5400
+ if (p.tool !== void 0) out.tool = normalizeMatcher(p.tool);
5401
+ if (p.attr !== void 0) {
5402
+ out.attr = Object.fromEntries(
5403
+ Object.entries(p.attr).map(([k, v]) => [k, v instanceof RegExp ? normalizeMatcher(v) : v])
5404
+ );
5405
+ }
5406
+ if (typeof p.custom === "function") {
5407
+ out.custom = p.custom;
5408
+ out.requiresCustom = true;
5409
+ }
5410
+ return out;
5411
+ }
5412
+ var TraceContractBuilder = class {
5413
+ constructor(name) {
5414
+ this.name = name;
5415
+ }
5416
+ name;
5417
+ rules = [];
5418
+ /** Every span in the trace must satisfy `p`. */
5419
+ always(p, label) {
5420
+ const np = normalizePredicate(p, `traceContract("${this.name}").always`);
5421
+ return this.add({ kind: "always", label: label ?? `always(${describePredicate(np)})`, p: np });
5422
+ }
5423
+ /** No span in the trace may satisfy `p`. */
5424
+ never(p, label) {
5425
+ const np = normalizePredicate(p, `traceContract("${this.name}").never`);
5426
+ return this.add({ kind: "never", label: label ?? `never(${describePredicate(np)})`, p: np });
5427
+ }
5428
+ /** At least one span in the trace must satisfy `p`. */
5429
+ eventually(p, label) {
5430
+ const np = normalizePredicate(p, `traceContract("${this.name}").eventually`);
5431
+ return this.add({
5432
+ kind: "eventually",
5433
+ label: label ?? `eventually(${describePredicate(np)})`,
5434
+ p: np
5435
+ });
5436
+ }
5437
+ /** Every `b`-match must have a strictly earlier `a`-match. */
5438
+ precedes(a, b, label) {
5439
+ const na = normalizePredicate(a, `traceContract("${this.name}").precedes (a)`);
5440
+ const nb = normalizePredicate(b, `traceContract("${this.name}").precedes (b)`);
5441
+ return this.add({
5442
+ kind: "precedes",
5443
+ label: label ?? `precedes(${describePredicate(na)} -> ${describePredicate(nb)})`,
5444
+ a: na,
5445
+ b: nb
5446
+ });
5447
+ }
5448
+ /** Every `p`-match is a violation unless a strictly earlier `prior`-match exists. */
5449
+ neverUnless(p, prior, label) {
5450
+ const np = normalizePredicate(p, `traceContract("${this.name}").neverUnless (p)`);
5451
+ const nprior = normalizePredicate(prior, `traceContract("${this.name}").neverUnless (prior)`);
5452
+ return this.add({
5453
+ kind: "neverUnless",
5454
+ label: label ?? `neverUnless(${describePredicate(np)} unless ${describePredicate(nprior)})`,
5455
+ p: np,
5456
+ prior: nprior
5457
+ });
5458
+ }
5459
+ build() {
5460
+ if (this.rules.length === 0) {
5461
+ throw new ValidationError(
5462
+ `traceContract("${this.name}").build(): no rules \u2014 an empty contract would vacuously pass`
5463
+ );
5464
+ }
5465
+ return { name: this.name, rules: [...this.rules] };
5466
+ }
5467
+ add(rule) {
5468
+ let label = rule.label;
5469
+ let n = 2;
5470
+ while (this.rules.some((r) => r.label === label)) {
5471
+ label = `${rule.label} #${n}`;
5472
+ n += 1;
5473
+ }
5474
+ this.rules.push({ ...rule, label });
5475
+ return this;
5476
+ }
5477
+ };
5478
+ function traceContract(name) {
5479
+ if (typeof name !== "string" || name.length === 0) {
5480
+ throw new ValidationError("traceContract: name must be a non-empty string");
5481
+ }
5482
+ return new TraceContractBuilder(name);
5483
+ }
5484
+ function assertContract(contract) {
5485
+ if (typeof contract?.name !== "string" || contract.name.length === 0) {
5486
+ throw new ValidationError("evaluateTraceContract: contract.name must be a non-empty string");
5487
+ }
5488
+ if (!Array.isArray(contract.rules) || contract.rules.length === 0) {
5489
+ throw new ValidationError(
5490
+ `evaluateTraceContract: contract "${contract.name}" has no rules \u2014 an empty contract would vacuously pass`
5491
+ );
5492
+ }
5493
+ const seen = /* @__PURE__ */ new Set();
5494
+ for (const rule of contract.rules) {
5495
+ const where = `contract "${contract.name}" rule "${rule?.label ?? "<unlabeled>"}"`;
5496
+ if (typeof rule?.label !== "string" || rule.label.length === 0) {
5497
+ throw new ValidationError(`${where}: label must be a non-empty string`);
5498
+ }
5499
+ if (seen.has(rule.label)) {
5500
+ throw new ValidationError(`${where}: duplicate label would collapse per-rule scores`);
5501
+ }
5502
+ seen.add(rule.label);
5503
+ switch (rule.kind) {
5504
+ case "always":
5505
+ case "never":
5506
+ case "eventually":
5507
+ assertPredicate(rule.p, `${where} (p)`);
5508
+ break;
5509
+ case "precedes":
5510
+ assertPredicate(rule.a, `${where} (a)`);
5511
+ assertPredicate(rule.b, `${where} (b)`);
5512
+ break;
5513
+ case "neverUnless":
5514
+ assertPredicate(rule.p, `${where} (p)`);
5515
+ assertPredicate(rule.prior, `${where} (prior)`);
5516
+ break;
5517
+ default:
5518
+ throw new ValidationError(
5519
+ `${where}: unknown rule kind "${String(rule.kind)}"`
5520
+ );
5521
+ }
5522
+ }
5523
+ }
5524
+ function orderSpans(spans) {
5525
+ const timed = spans.filter(
5526
+ (s) => typeof s.startedAt === "number" && Number.isFinite(s.startedAt)
5527
+ ).length;
5528
+ if (timed === spans.length) {
5529
+ return spans.map((span, i) => ({ span, i })).sort((x, y) => x.span.startedAt - y.span.startedAt || x.i - y.i).map((x) => x.span);
5530
+ }
5531
+ if (timed === 0) return [...spans];
5532
+ throw new ValidationError(
5533
+ `evaluateTraceContract: ${timed}/${spans.length} spans carry a finite startedAt \u2014 mixed timestamps make ordering ambiguous; stamp every span or none`
5534
+ );
5535
+ }
5536
+ function spanRef(span, index) {
5537
+ return span.spanId ?? `#${index}`;
5538
+ }
5539
+ function checkGuarded(args) {
5540
+ const out = [];
5541
+ let guardSeen = false;
5542
+ for (let i = 0; i < args.ordered.length; i++) {
5543
+ const span = args.ordered[i];
5544
+ if (!guardSeen && matchSpan(span, args.guarded)) {
5545
+ out.push({ rule: args.label, spanId: span.spanId, detail: args.detail(spanRef(span, i)) });
5546
+ }
5547
+ if (matchSpan(span, args.guard)) guardSeen = true;
5548
+ }
5549
+ return out;
5550
+ }
5551
+ function checkRule(rule, ordered) {
5552
+ switch (rule.kind) {
5553
+ case "always": {
5554
+ const out = [];
5555
+ for (let i = 0; i < ordered.length; i++) {
5556
+ const span = ordered[i];
5557
+ if (!matchSpan(span, rule.p)) {
5558
+ out.push({
5559
+ rule: rule.label,
5560
+ spanId: span.spanId,
5561
+ detail: `span ${spanRef(span, i)} ("${span.name ?? ""}") fails always(${describePredicate(rule.p)})`
5562
+ });
5563
+ }
5564
+ }
5565
+ return out;
5566
+ }
5567
+ case "never": {
5568
+ const out = [];
5569
+ for (let i = 0; i < ordered.length; i++) {
5570
+ const span = ordered[i];
5571
+ if (matchSpan(span, rule.p)) {
5572
+ out.push({
5573
+ rule: rule.label,
5574
+ spanId: span.spanId,
5575
+ detail: `span ${spanRef(span, i)} ("${span.name ?? ""}") matches never(${describePredicate(rule.p)})`
5576
+ });
5577
+ }
5578
+ }
5579
+ return out;
5580
+ }
5581
+ case "eventually": {
5582
+ const hit = ordered.some((span) => matchSpan(span, rule.p));
5583
+ return hit ? [] : [
5584
+ {
5585
+ rule: rule.label,
5586
+ detail: `no span matches eventually(${describePredicate(rule.p)}) over ${ordered.length} span(s)`
5587
+ }
5588
+ ];
5589
+ }
5590
+ case "precedes":
5591
+ return checkGuarded({
5592
+ label: rule.label,
5593
+ ordered,
5594
+ guard: rule.a,
5595
+ guarded: rule.b,
5596
+ detail: (ref) => `span ${ref} matches ${describePredicate(rule.b)} with no earlier ${describePredicate(rule.a)} match`
5597
+ });
5598
+ case "neverUnless":
5599
+ return checkGuarded({
5600
+ label: rule.label,
5601
+ ordered,
5602
+ guard: rule.prior,
5603
+ guarded: rule.p,
5604
+ detail: (ref) => `span ${ref} matches ${describePredicate(rule.p)} with no earlier ${describePredicate(rule.prior)} match`
5605
+ });
5606
+ }
4396
5607
  }
4397
- function keyPreserved(key) {
5608
+ function evaluateTraceContract(contract, spans) {
5609
+ assertContract(contract);
5610
+ const ordered = orderSpans(spans);
5611
+ const scores2 = {};
5612
+ const violations = [];
5613
+ for (const rule of contract.rules) {
5614
+ const ruleViolations = checkRule(rule, ordered);
5615
+ scores2[rule.label] = ruleViolations.length === 0 ? 1 : 0;
5616
+ violations.push(...ruleViolations);
5617
+ }
5618
+ const ruleCount = contract.rules.length;
5619
+ const passCount = Object.values(scores2).filter((s) => s === 1).length;
4398
5620
  return {
4399
- id: `preserved(${key})`,
4400
- description: `"${key}" unchanged from before to after`,
4401
- score: ({ before, after }) => before[key] !== void 0 && before[key] === after[key] ? 1 : 0
5621
+ contract: contract.name,
5622
+ valid: passCount === ruleCount,
5623
+ score: passCount / ruleCount,
5624
+ scores: scores2,
5625
+ violations,
5626
+ notes: `${passCount}/${ruleCount} rules passed`
4402
5627
  };
4403
5628
  }
4404
- function collectionPreserved(key, minRatio = 1) {
4405
- return {
4406
- id: `collection-preserved(${key})`,
4407
- description: `"${key}" length \u2265 ${minRatio} \xD7 prior length`,
4408
- score: ({ before, after }) => {
4409
- const b = before[key];
4410
- const a = after[key];
4411
- if (!Array.isArray(b) || !Array.isArray(a)) return 0;
4412
- if (b.length === 0) return a.length === 0 ? 1 : 1;
4413
- return Math.min(1, a.length / (b.length * minRatio));
4414
- }
4415
- };
5629
+ function checkTraceContracts(spans, contracts) {
5630
+ if (contracts.length === 0) {
5631
+ throw new ValidationError(
5632
+ "checkTraceContracts: empty contract list would vacuously pass \u2014 supply at least one contract"
5633
+ );
5634
+ }
5635
+ const verdicts = contracts.map((c) => evaluateTraceContract(c, spans));
5636
+ return { verdicts, allValid: verdicts.every((v) => v.valid) };
4416
5637
  }
4417
- function statusAdvanced(key, progression) {
5638
+ function contractJudge(contracts, opts) {
5639
+ if (contracts.length === 0) {
5640
+ throw new ValidationError("contractJudge: at least one contract required");
5641
+ }
5642
+ for (const c of contracts) assertContract(c);
5643
+ const names = /* @__PURE__ */ new Set();
5644
+ for (const c of contracts) {
5645
+ if (names.has(c.name)) {
5646
+ throw new ValidationError(
5647
+ `contractJudge: duplicate contract name "${c.name}" would collapse judge dimensions`
5648
+ );
5649
+ }
5650
+ names.add(c.name);
5651
+ }
5652
+ if (typeof opts?.spans !== "function") {
5653
+ throw new ValidationError("contractJudge: opts.spans extraction function is required");
5654
+ }
5655
+ const dimensions = contracts.map((c) => ({
5656
+ key: c.name,
5657
+ description: c.rules.map((r) => r.label).join("; ")
5658
+ }));
4418
5659
  return {
4419
- id: `status-advanced(${key})`,
4420
- description: `"${key}" progressed along ${progression.join("\u2192")}`,
4421
- score: ({ before, after }) => {
4422
- const bi = progression.indexOf(String(before[key]));
4423
- const ai2 = progression.indexOf(String(after[key]));
4424
- if (bi === -1 || ai2 === -1) return 0;
4425
- return ai2 >= bi ? 1 : 0;
5660
+ name: opts.name ?? "trace-contracts",
5661
+ dimensions,
5662
+ score({ artifact, scenario }) {
5663
+ const spans = opts.spans({ artifact, scenario });
5664
+ if (!Array.isArray(spans)) {
5665
+ throw new ValidationError(
5666
+ `contractJudge: spans() must return a span array, got ${typeof spans}`
5667
+ );
5668
+ }
5669
+ const { verdicts } = checkTraceContracts(spans, contracts);
5670
+ const dims = {};
5671
+ const violations = [];
5672
+ for (const v of verdicts) {
5673
+ dims[v.contract] = v.score;
5674
+ violations.push(...v.violations);
5675
+ }
5676
+ const composite = verdicts.reduce((acc, v) => acc + v.score, 0) / verdicts.length;
5677
+ return {
5678
+ dimensions: dims,
5679
+ composite,
5680
+ notes: violations.length === 0 ? "all trace contracts satisfied" : violations.slice(0, 8).map((x) => `${x.rule}: ${x.detail}`).join("\n")
5681
+ };
4426
5682
  }
4427
5683
  };
4428
5684
  }
4429
5685
 
4430
- // src/ui-finding.ts
4431
- var UI_LENSES = [
4432
- "consistency",
4433
- "hierarchy",
4434
- "layout",
4435
- "ux-flow",
4436
- "duplication",
4437
- "accessibility",
4438
- "responsive",
4439
- "states",
4440
- "content",
4441
- "interaction",
4442
- "performance-perceived",
4443
- "other"
4444
- ];
4445
- var UI_FINDING_SEVERITIES = [
4446
- "critical",
4447
- "high",
4448
- "med",
4449
- "low"
4450
- ];
4451
-
4452
5686
  // src/behavior-dsl.ts
4453
5687
  var BehaviorAssertion = class {
4454
5688
  constructor(store, runId) {
@@ -4516,6 +5750,25 @@ var BehaviorAssertion = class {
4516
5750
  }
4517
5751
  };
4518
5752
  }
5753
+ /** Evaluate a finite-trace temporal contract (`traceContract(...)`) over
5754
+ * this run's span sequence. See `trace-contracts.ts` for the operators. */
5755
+ toSatisfyContract(contract) {
5756
+ return {
5757
+ label: `agent(${this.runId}).toSatisfyContract(${contract.name})`,
5758
+ check: async () => {
5759
+ const spans = await this.store.spans({ runId: this.runId });
5760
+ const verdict = evaluateTraceContract(contract, spans);
5761
+ if (verdict.valid) {
5762
+ return { ok: true, detail: `contract "${contract.name}": ${verdict.notes}` };
5763
+ }
5764
+ return {
5765
+ ok: false,
5766
+ detail: verdict.violations.map((v) => `${v.rule}: ${v.detail}`).join("; "),
5767
+ evidence: verdict.violations.find((v) => v.spanId)?.spanId
5768
+ };
5769
+ }
5770
+ };
5771
+ }
4519
5772
  toNeverCall(toolName) {
4520
5773
  return {
4521
5774
  label: `agent(${this.runId}).toNeverCall(${toolName})`,
@@ -5118,90 +6371,6 @@ async function promptBisect(options) {
5118
6371
  };
5119
6372
  }
5120
6373
 
5121
- // src/counterfactual.ts
5122
- async function runCounterfactual(store, originalRunId, mutation, runner) {
5123
- const originalRun = await store.getRun(originalRunId);
5124
- if (!originalRun) throw new NotFoundError(`counterfactual: run ${originalRunId} not found`);
5125
- const trajectory = await buildTrajectory(store, originalRunId);
5126
- if (mutation.at < 0 || mutation.at >= trajectory.steps.length) {
5127
- throw new ValidationError(
5128
- `counterfactual: mutation.at=${mutation.at} out of range [0, ${trajectory.steps.length})`
5129
- );
5130
- }
5131
- const targetStep = trajectory.steps[mutation.at];
5132
- const mutatedStep = applyMutation(targetStep, mutation);
5133
- const cfEmitter = new TraceEmitter(store);
5134
- await cfEmitter.startRun({
5135
- scenarioId: originalRun.scenarioId,
5136
- variantId: originalRun.variantId ? `${originalRun.variantId}+cf:${mutation.kind}@${mutation.at}` : `cf:${mutation.kind}@${mutation.at}`,
5137
- projectId: originalRun.projectId,
5138
- parentRunId: originalRunId,
5139
- layer: "meta",
5140
- tags: { counterfactual: "true", mutationKind: mutation.kind, mutationAt: String(mutation.at) }
5141
- });
5142
- await runner.executeFrom(
5143
- {
5144
- originalRunId,
5145
- originalTrajectory: trajectory,
5146
- prefix: trajectory.steps.slice(0, mutation.at),
5147
- mutation,
5148
- mutatedStep
5149
- },
5150
- cfEmitter
5151
- );
5152
- const counterfactual = await store.getRun(cfEmitter.runId);
5153
- const delta = {
5154
- originalOutcomeScore: originalRun.outcome?.score ?? null,
5155
- counterfactualOutcomeScore: counterfactual?.outcome?.score ?? null,
5156
- deltaScore: originalRun.outcome?.score !== void 0 && counterfactual?.outcome?.score !== void 0 ? counterfactual.outcome.score - originalRun.outcome.score : null
5157
- };
5158
- return { counterfactualRunId: cfEmitter.runId, originalRunId, mutation, delta };
5159
- }
5160
- function applyMutation(step, mutation) {
5161
- if (mutation.kind === "swap-model" && step.span.kind === "llm") {
5162
- const llm = step.span;
5163
- return { ...step, span: { ...llm, model: mutation.newModel } };
5164
- }
5165
- if (mutation.kind === "swap-tool-result" && step.span.kind === "tool") {
5166
- const tool = step.span;
5167
- return { ...step, span: { ...tool, result: mutation.newResult } };
5168
- }
5169
- if (mutation.kind === "inject-system-message" && step.span.kind === "llm") {
5170
- const llm = step.span;
5171
- return {
5172
- ...step,
5173
- span: {
5174
- ...llm,
5175
- messages: [{ role: "system", content: mutation.content }, ...llm.messages]
5176
- }
5177
- };
5178
- }
5179
- if (mutation.kind === "custom") return mutation.apply(step);
5180
- return step;
5181
- }
5182
- function attributeCounterfactuals(results) {
5183
- const grouped = /* @__PURE__ */ new Map();
5184
- for (const r of results) {
5185
- const arr = grouped.get(r.mutation.kind) ?? [];
5186
- arr.push(r);
5187
- grouped.set(r.mutation.kind, arr);
5188
- }
5189
- const out = [];
5190
- for (const [kind, items] of grouped) {
5191
- const deltas = items.map((i) => i.delta.deltaScore).filter((d) => typeof d === "number");
5192
- if (deltas.length === 0) continue;
5193
- const meanAbs = deltas.reduce((a, b) => a + Math.abs(b), 0) / deltas.length;
5194
- const meanSigned = deltas.reduce((a, b) => a + b, 0) / deltas.length;
5195
- out.push({
5196
- mutationKind: kind,
5197
- n: deltas.length,
5198
- meanAbsDelta: meanAbs,
5199
- meanSignedDelta: meanSigned
5200
- });
5201
- }
5202
- return out.sort((a, b) => b.meanAbsDelta - a.meanAbsDelta);
5203
- }
5204
-
5205
6374
  // src/cross-trace-diff.ts
5206
6375
  async function crossTraceDiff(store, runA, runB, options = {}) {
5207
6376
  const [a, b] = await Promise.all([buildTrajectory(store, runA), buildTrajectory(store, runB)]);
@@ -5873,13 +7042,13 @@ function extractErrorCount(text, opts = {}) {
5873
7042
  (p) => !opts.only || opts.only.includes(p.name)
5874
7043
  );
5875
7044
  for (const p of patterns) {
5876
- const matches = Array.from(text.matchAll(p.regex));
5877
- if (matches.length === 0) continue;
5878
- const count = p.transform ? matches.reduce((sum3, m) => sum3 + p.transform(m), 0) : matches.length;
7045
+ const matches2 = Array.from(text.matchAll(p.regex));
7046
+ if (matches2.length === 0) continue;
7047
+ const count = p.transform ? matches2.reduce((sum3, m) => sum3 + p.transform(m), 0) : matches2.length;
5879
7048
  return {
5880
7049
  count,
5881
7050
  matched: p.name,
5882
- samples: matches.slice(0, 5).map((m) => m[0])
7051
+ samples: matches2.slice(0, 5).map((m) => m[0])
5883
7052
  };
5884
7053
  }
5885
7054
  return { count: null, matched: null, samples: [] };
@@ -6393,14 +7562,18 @@ function aggregate(layers, results, startedAt, startedAtMs) {
6393
7562
  }
6394
7563
  }
6395
7564
  const finishedAtMs = Date.now();
7565
+ const allPass = ranAnyScoredLayer && !anyScoredLayerFailed && failCount === 0 && errorCount === 0;
7566
+ const blendedScore = scoredWeightSum > 0 ? scoredWeightedTotal / scoredWeightSum : 0;
6396
7567
  return {
6397
7568
  layers: results,
6398
7569
  passCount,
6399
7570
  failCount,
6400
7571
  skippedCount,
6401
7572
  errorCount,
6402
- allPass: ranAnyScoredLayer && !anyScoredLayerFailed && failCount === 0 && errorCount === 0,
6403
- blendedScore: scoredWeightSum > 0 ? scoredWeightedTotal / scoredWeightSum : 0,
7573
+ allPass,
7574
+ blendedScore,
7575
+ valid: allPass,
7576
+ score: blendedScore,
6404
7577
  durationMs: finishedAtMs - startedAtMs,
6405
7578
  startedAt,
6406
7579
  finishedAt: new Date(finishedAtMs).toISOString()
@@ -6820,7 +7993,7 @@ function scoreScenario(scenario, matcher, threshold, matchStrategy) {
6820
7993
  }
6821
7994
  function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
6822
7995
  const candidatesLeft = scenario.candidates.map((candidate, index) => ({ candidate, index }));
6823
- const matches = [];
7996
+ const matches2 = [];
6824
7997
  for (const reference of scenario.references) {
6825
7998
  let best = null;
6826
7999
  for (const item of candidatesLeft) {
@@ -6833,7 +8006,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
6833
8006
  if (best && best.score >= threshold) {
6834
8007
  const matchIndex = candidatesLeft.findIndex((item) => item.index === best.index);
6835
8008
  if (matchIndex >= 0) candidatesLeft.splice(matchIndex, 1);
6836
- matches.push({
8009
+ matches2.push({
6837
8010
  scenarioId: scenario.id,
6838
8011
  referenceId: reference.id,
6839
8012
  candidateId: best.candidate.id,
@@ -6843,7 +8016,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
6843
8016
  reason: best.reason
6844
8017
  });
6845
8018
  } else {
6846
- matches.push({
8019
+ matches2.push({
6847
8020
  scenarioId: scenario.id,
6848
8021
  referenceId: reference.id,
6849
8022
  candidateId: best?.candidate.id ?? null,
@@ -6854,7 +8027,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
6854
8027
  });
6855
8028
  }
6856
8029
  }
6857
- return buildScenarioScore(scenario, matches, candidatesLeft.length);
8030
+ return buildScenarioScore(scenario, matches2, candidatesLeft.length);
6858
8031
  }
6859
8032
  function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
6860
8033
  const pairs = [];
@@ -6881,7 +8054,7 @@ function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
6881
8054
  selectedByReference.set(pair.referenceIndex, pair);
6882
8055
  selectedCandidates.add(pair.candidateIndex);
6883
8056
  }
6884
- const matches = scenario.references.map((reference, referenceIndex) => {
8057
+ const matches2 = scenario.references.map((reference, referenceIndex) => {
6885
8058
  const weight = reference.weight ?? 1;
6886
8059
  const selected = selectedByReference.get(referenceIndex);
6887
8060
  if (selected) {
@@ -6906,7 +8079,7 @@ function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
6906
8079
  reason: bestRejected?.reason ?? "no candidates"
6907
8080
  };
6908
8081
  });
6909
- return buildScenarioScore(scenario, matches, scenario.candidates.length - selectedCandidates.size);
8082
+ return buildScenarioScore(scenario, matches2, scenario.candidates.length - selectedCandidates.size);
6910
8083
  }
6911
8084
  function scorePair(scenario, matcher, reference, candidate) {
6912
8085
  const result = matcher(reference, candidate, scenario);
@@ -6917,11 +8090,11 @@ function scorePair(scenario, matcher, reference, candidate) {
6917
8090
  }
6918
8091
  return { score: clamp012(result.score), reason: result.reason ?? "" };
6919
8092
  }
6920
- function buildScenarioScore(scenario, matches, falsePositives) {
6921
- const matched = matches.filter((match) => match.matched).length;
8093
+ function buildScenarioScore(scenario, matches2, falsePositives) {
8094
+ const matched = matches2.filter((match) => match.matched).length;
6922
8095
  const total = scenario.references.length;
6923
- const matchedWeight = matches.filter((match) => match.matched).reduce((sum3, match) => sum3 + match.weight, 0);
6924
- const totalWeight = matches.reduce((sum3, match) => sum3 + match.weight, 0);
8096
+ const matchedWeight = matches2.filter((match) => match.matched).reduce((sum3, match) => sum3 + match.weight, 0);
8097
+ const totalWeight = matches2.reduce((sum3, match) => sum3 + match.weight, 0);
6925
8098
  const precision2 = ratio(matched, matched + falsePositives);
6926
8099
  const recall = ratio(matched, total);
6927
8100
  return {
@@ -6935,7 +8108,7 @@ function buildScenarioScore(scenario, matches, falsePositives) {
6935
8108
  precision: precision2,
6936
8109
  recall,
6937
8110
  f1: f1(precision2, recall),
6938
- matches
8111
+ matches: matches2
6939
8112
  };
6940
8113
  }
6941
8114
  function aggregateBySplit(scores2) {
@@ -7196,7 +8369,7 @@ function createDefaultReviewer(options) {
7196
8369
 
7197
8370
  // src/description-length-gate.ts
7198
8371
  import { gzipSync } from "zlib";
7199
- function runScore2(run) {
8372
+ function runScore3(run) {
7200
8373
  const o = run.outcome;
7201
8374
  const s = o.holdoutScore ?? o.searchScore ?? o.raw?.score;
7202
8375
  return typeof s === "number" && Number.isFinite(s) ? s : void 0;
@@ -7207,7 +8380,7 @@ function taskKey(run) {
7207
8380
  function perTaskMeanScore(runs) {
7208
8381
  const acc = /* @__PURE__ */ new Map();
7209
8382
  for (const run of runs) {
7210
- const s = runScore2(run);
8383
+ const s = runScore3(run);
7211
8384
  if (s === void 0) continue;
7212
8385
  const key = taskKey(run);
7213
8386
  const cur = acc.get(key) ?? { sum: 0, n: 0 };
@@ -7359,10 +8532,10 @@ async function discoverPersonas(dir, opts = {}) {
7359
8532
  function matchGoldens(goldens, candidates, options = {}) {
7360
8533
  const extract = options.text ?? defaultExtract2;
7361
8534
  const haystacks = candidates.map((c) => extract(c).toLowerCase());
7362
- const matches = goldens.map((golden) => goldenMatched(golden, haystacks));
8535
+ const matches2 = goldens.map((golden) => goldenMatched(golden, haystacks));
7363
8536
  return {
7364
- matches,
7365
- hits: matches.filter(Boolean).length,
8537
+ matches: matches2,
8538
+ hits: matches2.filter(Boolean).length,
7366
8539
  total: goldens.length
7367
8540
  };
7368
8541
  }
@@ -7615,69 +8788,6 @@ function fmt(x) {
7615
8788
  return x.toFixed(4);
7616
8789
  }
7617
8790
 
7618
- // src/judge-retry.ts
7619
- var DEFAULT_MAX_ATTEMPTS = 3;
7620
- var DEFAULT_TIMEOUT_MS = 3e5;
7621
- function sleep(ms) {
7622
- return new Promise((resolve) => setTimeout(resolve, ms));
7623
- }
7624
- async function withJudgeRetry(judgeFn, policy = {}) {
7625
- const maxAttempts = policy.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
7626
- const timeoutMs = policy.timeoutMs ?? DEFAULT_TIMEOUT_MS;
7627
- const backoff = policy.backoffMs ?? backoffMs;
7628
- const isRetryable = policy.isRetryable ?? isTransientLlmError;
7629
- const models = policy.models && policy.models.length > 0 ? policy.models : [void 0];
7630
- let totalAttempts = 0;
7631
- const attemptErrors = [];
7632
- let lastError;
7633
- for (const model of models) {
7634
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
7635
- totalAttempts += 1;
7636
- const controller = new AbortController();
7637
- const timer = setTimeout(() => controller.abort(new Error("TimeoutError")), timeoutMs);
7638
- try {
7639
- const value = await judgeFn(model, controller.signal);
7640
- clearTimeout(timer);
7641
- return {
7642
- value,
7643
- succeeded: true,
7644
- attempts: totalAttempts,
7645
- modelUsed: model,
7646
- attemptErrors
7647
- };
7648
- } catch (err) {
7649
- clearTimeout(timer);
7650
- const errObj = err instanceof Error ? err : new Error(String(err));
7651
- lastError = errObj;
7652
- attemptErrors.push({
7653
- attempt: totalAttempts,
7654
- model: model ?? "(default)",
7655
- error: errObj.message
7656
- });
7657
- if (!isRetryable(errObj)) {
7658
- return {
7659
- value: null,
7660
- succeeded: false,
7661
- attempts: totalAttempts,
7662
- error: errObj,
7663
- attemptErrors
7664
- };
7665
- }
7666
- if (attempt < maxAttempts - 1) {
7667
- await sleep(backoff(attempt));
7668
- }
7669
- }
7670
- }
7671
- }
7672
- return {
7673
- value: null,
7674
- succeeded: false,
7675
- attempts: totalAttempts,
7676
- error: lastError,
7677
- attemptErrors
7678
- };
7679
- }
7680
-
7681
8791
  // src/orthogonality.ts
7682
8792
  function passOrthogonality(input) {
7683
8793
  const passes = input.passes;
@@ -8056,6 +9166,7 @@ function traceJudgeEnsemble(judges, judgeNames, opts) {
8056
9166
  });
8057
9167
  try {
8058
9168
  const allScores = [];
9169
+ let failedJudges = 0;
8059
9170
  for (let i = 0; i < judges.length; i++) {
8060
9171
  const judge = judges[i];
8061
9172
  const name = judgeNames[i] ?? `judge_${i}`;
@@ -8063,8 +9174,13 @@ function traceJudgeEnsemble(judges, judgeNames, opts) {
8063
9174
  emitter: opts.emitter,
8064
9175
  parentSpanId: ensembleSpan.span.spanId
8065
9176
  });
8066
- const scores2 = await tracedFn(tc, input);
8067
- allScores.push(...scores2);
9177
+ try {
9178
+ const scores2 = await tracedFn(tc, input);
9179
+ allScores.push(...scores2);
9180
+ } catch (err) {
9181
+ if (!(err instanceof JudgeParseError)) throw err;
9182
+ failedJudges++;
9183
+ }
8068
9184
  }
8069
9185
  const composite = allScores.length > 0 ? allScores.reduce((sum3, s) => sum3 + s.score, 0) / allScores.length : 0;
8070
9186
  await ensembleSpan.end({
@@ -8072,6 +9188,7 @@ function traceJudgeEnsemble(judges, judgeNames, opts) {
8072
9188
  "judge.ensemble_size": judges.length,
8073
9189
  "judge.composite_score": composite,
8074
9190
  "judge.total_dimensions": allScores.length,
9191
+ "judge.failed_judges": failedJudges,
8075
9192
  "eval.phase": "judge"
8076
9193
  }
8077
9194
  });
@@ -8501,8 +9618,256 @@ function applyDomainPatch(p, sectionId, newBody) {
8501
9618
  function sectionHash(section) {
8502
9619
  return surfaceContentHash(JSON.stringify({ title: section.title, body: section.body }));
8503
9620
  }
9621
+
9622
+ // src/cost-report.ts
9623
+ function costReport(ledger) {
9624
+ const summary = ledger.summary();
9625
+ const perModel = /* @__PURE__ */ new Map();
9626
+ for (const entry of ledger.list()) {
9627
+ const roll = perModel.get(entry.model) ?? {
9628
+ model: entry.model,
9629
+ usd: 0,
9630
+ entries: 0,
9631
+ unpriced: false
9632
+ };
9633
+ roll.usd += entry.costUsd;
9634
+ roll.entries += 1;
9635
+ if (entry.costUnknown) roll.unpriced = true;
9636
+ perModel.set(entry.model, roll);
9637
+ }
9638
+ return {
9639
+ perChannel: summary.byChannel,
9640
+ total: {
9641
+ usd: summary.totalCostUsd,
9642
+ unknownEntries: summary.byChannel.reduce((sum3, c) => sum3 + c.unpricedCalls, 0)
9643
+ },
9644
+ perModel: [...perModel.values()].sort((a, b) => a.model.localeCompare(b.model))
9645
+ };
9646
+ }
9647
+ function attachCostToReport(report, ledger) {
9648
+ if ("cost" in report) {
9649
+ throw new ValidationError(
9650
+ "attachCostToReport: report already has a 'cost' key \u2014 refusing to overwrite an existing stamp"
9651
+ );
9652
+ }
9653
+ return { ...report, cost: costReport(ledger) };
9654
+ }
9655
+
9656
+ // src/model-seats.ts
9657
+ var seatPresets = {
9658
+ economy: {
9659
+ worker: "kimi-k2.6",
9660
+ judges: ["kimi-k2.6", "deepseek-v4-pro", "gpt-4.1-mini"],
9661
+ analyst: "gpt-4.1-mini",
9662
+ reflection: "gpt-4.1-mini",
9663
+ verifier: "deepseek-v4-pro"
9664
+ },
9665
+ frontier: {}
9666
+ };
9667
+ var SeatUnsetError = class extends ConfigError {
9668
+ constructor(seat) {
9669
+ super(
9670
+ `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)`
9671
+ );
9672
+ this.seat = seat;
9673
+ }
9674
+ seat;
9675
+ };
9676
+ function resolveSeat(seats, seat, fallback) {
9677
+ const value = seats[seat];
9678
+ if (seat === "judges") {
9679
+ if (value !== void 0 && !Array.isArray(value)) {
9680
+ throw new ValidationError(`ModelSeats: seat 'judges' must be a string[], got ${typeof value}`);
9681
+ }
9682
+ const models = Array.isArray(value) ? value : [];
9683
+ if (models.length > 0) {
9684
+ const blank = models.findIndex((m) => typeof m !== "string" || m.trim() === "");
9685
+ if (blank >= 0) {
9686
+ throw new ValidationError(
9687
+ `ModelSeats: judges[${blank}] is blank \u2014 every panel model must be a non-empty id`
9688
+ );
9689
+ }
9690
+ return [...models];
9691
+ }
9692
+ } else {
9693
+ if (value !== void 0 && typeof value !== "string") {
9694
+ throw new ValidationError(`ModelSeats: seat '${seat}' must be a string, got ${typeof value}`);
9695
+ }
9696
+ if (typeof value === "string" && value.trim() !== "") return value;
9697
+ }
9698
+ if (fallback !== void 0) {
9699
+ if (fallback.trim() === "") {
9700
+ throw new ValidationError(`ModelSeats: fallback for seat '${seat}' is blank`);
9701
+ }
9702
+ return seat === "judges" ? [fallback] : fallback;
9703
+ }
9704
+ throw new SeatUnsetError(seat);
9705
+ }
9706
+
9707
+ // src/verdict-cache.ts
9708
+ import { createHash } from "crypto";
9709
+ import { appendFileSync as appendFileSync3, existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
9710
+ function canonicalizeAt(value, path) {
9711
+ if (value === null) return "null";
9712
+ switch (typeof value) {
9713
+ case "boolean":
9714
+ return value ? "true" : "false";
9715
+ case "number":
9716
+ if (!Number.isFinite(value)) {
9717
+ throw new Error(
9718
+ `canonicalJson: non-finite number (${value}) at ${path} \u2014 ambiguity is an error, not a coercion`
9719
+ );
9720
+ }
9721
+ return JSON.stringify(value);
9722
+ case "string":
9723
+ return JSON.stringify(value);
9724
+ case "undefined":
9725
+ case "function":
9726
+ case "symbol":
9727
+ throw new Error(
9728
+ `canonicalJson: ${typeof value} at ${path} \u2014 ambiguity is an error, not a coercion`
9729
+ );
9730
+ case "bigint":
9731
+ throw new Error(`canonicalJson: bigint at ${path} \u2014 not representable in JSON`);
9732
+ case "object":
9733
+ break;
9734
+ }
9735
+ const obj = value;
9736
+ if (typeof obj["toJSON"] === "function") {
9737
+ return canonicalizeAt(obj.toJSON(), path);
9738
+ }
9739
+ if (Array.isArray(obj)) {
9740
+ return `[${obj.map((item, i) => canonicalizeAt(item, `${path}[${i}]`)).join(",")}]`;
9741
+ }
9742
+ if (obj instanceof Map || obj instanceof Set) {
9743
+ throw new Error(
9744
+ `canonicalJson: ${obj instanceof Map ? "Map" : "Set"} at ${path} \u2014 would serialize as '{}'; convert to a plain object/array first`
9745
+ );
9746
+ }
9747
+ const keys = Object.keys(obj).sort();
9748
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalizeAt(obj[k], `${path}.${k}`)}`);
9749
+ return `{${parts.join(",")}}`;
9750
+ }
9751
+ function canonicalJson(value) {
9752
+ return canonicalizeAt(value, "$");
9753
+ }
9754
+ function contentHash(value) {
9755
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
9756
+ }
9757
+ function inMemoryVerdictCache() {
9758
+ const entries = /* @__PURE__ */ new Map();
9759
+ return {
9760
+ get: (key) => entries.get(key),
9761
+ set: (key, score) => {
9762
+ entries.set(key, score);
9763
+ }
9764
+ };
9765
+ }
9766
+ function parseCacheLine(line, path, lineNo) {
9767
+ let parsed;
9768
+ try {
9769
+ parsed = JSON.parse(line);
9770
+ } catch (err) {
9771
+ throw new Error(
9772
+ `fileVerdictCache: corrupt JSONL at ${path}:${lineNo} \u2014 ${err instanceof Error ? err.message : String(err)}`
9773
+ );
9774
+ }
9775
+ const rec = parsed;
9776
+ 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") {
9777
+ throw new Error(
9778
+ `fileVerdictCache: invalid record shape at ${path}:${lineNo} \u2014 expected {key, score:{dimensions, composite, notes}}`
9779
+ );
9780
+ }
9781
+ return rec;
9782
+ }
9783
+ function fileVerdictCache(path) {
9784
+ const entries = /* @__PURE__ */ new Map();
9785
+ if (existsSync5(path)) {
9786
+ const lines = readFileSync6(path, "utf8").split("\n");
9787
+ for (let i = 0; i < lines.length; i++) {
9788
+ const line = lines[i];
9789
+ if (line === void 0 || line.trim() === "") continue;
9790
+ const rec = parseCacheLine(line, path, i + 1);
9791
+ entries.set(rec.key, rec.score);
9792
+ }
9793
+ }
9794
+ return {
9795
+ get: (key) => entries.get(key),
9796
+ set: (key, score) => {
9797
+ appendFileSync3(path, `${JSON.stringify({ key, score })}
9798
+ `, "utf8");
9799
+ entries.set(key, score);
9800
+ }
9801
+ };
9802
+ }
9803
+ function cachedJudge(judge, store, options) {
9804
+ if (typeof options.judgeVersion !== "string" || options.judgeVersion.trim() === "") {
9805
+ throw new Error("cachedJudge: judgeVersion is required and must be a non-empty string");
9806
+ }
9807
+ const stats = { hits: 0, misses: 0 };
9808
+ const wrapped = {
9809
+ name: judge.name,
9810
+ dimensions: judge.dimensions,
9811
+ async score(input) {
9812
+ const key = contentHash({
9813
+ artifact: canonicalJson(input.artifact),
9814
+ scenarioId: input.scenario.id,
9815
+ judgeName: judge.name,
9816
+ dimensions: judge.dimensions,
9817
+ judgeVersion: options.judgeVersion
9818
+ });
9819
+ const cached = await store.get(key);
9820
+ if (cached !== void 0) {
9821
+ stats.hits += 1;
9822
+ return cached;
9823
+ }
9824
+ const score = await judge.score(input);
9825
+ await store.set(key, score);
9826
+ stats.misses += 1;
9827
+ return score;
9828
+ },
9829
+ stats: () => ({ ...stats })
9830
+ };
9831
+ if (judge.appliesTo) wrapped.appliesTo = judge.appliesTo;
9832
+ return wrapped;
9833
+ }
9834
+
9835
+ // src/attestation.ts
9836
+ var ATTESTATION_ALGORITHM = "sha256/canonical-json";
9837
+ function attest(report, provenance) {
9838
+ return {
9839
+ reportHash: contentHash(report),
9840
+ provenance,
9841
+ algorithm: ATTESTATION_ALGORITHM
9842
+ };
9843
+ }
9844
+ function verifyAttestation(report, attested) {
9845
+ if (attested.algorithm !== ATTESTATION_ALGORITHM) {
9846
+ return {
9847
+ valid: false,
9848
+ reason: `unknown algorithm '${attested.algorithm}' \u2014 this verifier only checks '${ATTESTATION_ALGORITHM}'`
9849
+ };
9850
+ }
9851
+ let recomputed;
9852
+ try {
9853
+ recomputed = contentHash(report);
9854
+ } catch (err) {
9855
+ return {
9856
+ valid: false,
9857
+ reason: `report is not canonicalizable: ${err instanceof Error ? err.message : String(err)}`
9858
+ };
9859
+ }
9860
+ if (recomputed !== attested.reportHash) {
9861
+ return {
9862
+ valid: false,
9863
+ reason: `report hash mismatch: attested ${attested.reportHash}, recomputed ${recomputed}`
9864
+ };
9865
+ }
9866
+ return { valid: true };
9867
+ }
8504
9868
  export {
8505
9869
  AGENT_PROFILE_KINDS,
9870
+ ATTESTATION_ALGORITHM,
8506
9871
  AgentDriver,
8507
9872
  AgentEvalError,
8508
9873
  AgentProfileCellValidationError,
@@ -8517,6 +9882,7 @@ export {
8517
9882
  CaptureIntegrityError,
8518
9883
  ConfigError,
8519
9884
  ConvergenceTracker,
9885
+ CostLedger,
8520
9886
  CostTracker,
8521
9887
  CrossFamilyError,
8522
9888
  DEFAULT_AGENT_SLOS,
@@ -8538,6 +9904,8 @@ export {
8538
9904
  DockerSandboxDriver,
8539
9905
  DualAgentBench,
8540
9906
  ERROR_COUNT_PATTERNS,
9907
+ EvalTraceStore,
9908
+ ExperimentTracker,
8541
9909
  FAILURE_CLASSES,
8542
9910
  FAILURE_MODE_KIND_SPEC,
8543
9911
  FileSystemFeedbackTrajectoryStore,
@@ -8554,6 +9922,7 @@ export {
8554
9922
  InMemoryTraceStore,
8555
9923
  InMemoryWorkspaceInspector,
8556
9924
  JudgeError,
9925
+ JudgeParseError,
8557
9926
  JudgeRunner,
8558
9927
  KNOWLEDGE_GAP_KIND_SPEC,
8559
9928
  KNOWLEDGE_POISONING_KIND_SPEC,
@@ -8586,6 +9955,7 @@ export {
8586
9955
  SKILL_USAGE_ANALYST,
8587
9956
  SandboxHarness,
8588
9957
  ScenarioRegistry,
9958
+ SeatUnsetError,
8589
9959
  SingleBackendError,
8590
9960
  SkillUsageAnalyst,
8591
9961
  SpanNotFoundError,
@@ -8596,6 +9966,7 @@ export {
8596
9966
  TRACE_ANALYST_TRUNCATION_MARKER_PREFIX,
8597
9967
  TRACE_SCHEMA_VERSION,
8598
9968
  TokenCounter,
9969
+ TraceContractBuilder,
8599
9970
  TraceEmitter,
8600
9971
  TraceFileMissingError,
8601
9972
  TraceNotFoundError,
@@ -8630,6 +10001,9 @@ export {
8630
10001
  assertRunCaptured,
8631
10002
  assertSingleBackend,
8632
10003
  assignFeedbackSplit,
10004
+ assignHeldOutTag,
10005
+ attachCostToReport,
10006
+ attest,
8633
10007
  attributeCounterfactuals,
8634
10008
  backoffMs,
8635
10009
  deterministicSplit as benchmarkDeterministicSplit,
@@ -8651,17 +10025,20 @@ export {
8651
10025
  buildTraceInsightPrompt,
8652
10026
  buildTrajectory,
8653
10027
  byteLengthRange,
10028
+ cachedJudge,
8654
10029
  calibrateJudge,
8655
10030
  calibrateJudgeContinuous,
8656
10031
  callLlm,
8657
10032
  callLlmJson,
8658
10033
  canaryLeakView,
10034
+ canonicalJson,
8659
10035
  canonicalize,
8660
10036
  captureFetchToRawSink,
8661
10037
  causalAttribution,
8662
10038
  checkBehavioralCanary,
8663
10039
  checkCanaries,
8664
10040
  checkSlos,
10041
+ checkTraceContracts,
8665
10042
  clamp01,
8666
10043
  classifyEuAiRisk,
8667
10044
  classifyFailure,
@@ -8675,20 +10052,26 @@ export {
8675
10052
  compareReferenceReplay,
8676
10053
  compareToBaseline,
8677
10054
  compilerJudge,
10055
+ completionVerdict,
8678
10056
  composeParsers,
8679
10057
  composeValidators,
10058
+ computeExperimentStats,
8680
10059
  computeFindingId,
8681
10060
  computeToolUseMetrics,
8682
10061
  computeTraceMetrics,
8683
10062
  confidenceInterval,
8684
10063
  containsAll,
10064
+ contentHash,
8685
10065
  continuousAgreement,
10066
+ contractJudge,
8686
10067
  controlFailureClassFromVerification,
8687
10068
  controlRunToFeedbackTrajectory,
8688
10069
  controlRunToRunRecord,
8689
10070
  convertTraceStoresToOtlp,
8690
10071
  corpusInterRaterAgreement,
8691
10072
  corpusInterRaterAgreementFromJudgeScores,
10073
+ costForUsage,
10074
+ costReport,
8692
10075
  createAnalystAi,
8693
10076
  createAntiSlopJudge,
8694
10077
  createChatClient,
@@ -8727,6 +10110,8 @@ export {
8727
10110
  distillPlaybook,
8728
10111
  domainEvidencePattern,
8729
10112
  dominates,
10113
+ eProcess,
10114
+ ensembleJudge,
8730
10115
  estimateCost,
8731
10116
  estimateTokens,
8732
10117
  euAiActReport,
@@ -8736,6 +10121,7 @@ export {
8736
10121
  evaluateInterimReleaseConfidence,
8737
10122
  evaluateOracles,
8738
10123
  evaluateReleaseConfidence,
10124
+ evaluateTraceContract,
8739
10125
  executeScenario,
8740
10126
  expectAgent,
8741
10127
  exportRewardModel,
@@ -8744,6 +10130,9 @@ export {
8744
10130
  extractErrorCount,
8745
10131
  extractOtlpAttributes,
8746
10132
  extractProducedState,
10133
+ extractUsage,
10134
+ extractUsageFromResponse,
10135
+ extractUsageFromSse,
8747
10136
  feedbackTrajectoriesToDatasetScenarios,
8748
10137
  feedbackTrajectoriesToOptimizerRows,
8749
10138
  feedbackTrajectoryToDatasetScenario,
@@ -8751,6 +10140,8 @@ export {
8751
10140
  fieldAgreement,
8752
10141
  fileContains,
8753
10142
  fileExists,
10143
+ fileExperimentStore,
10144
+ fileVerdictCache,
8754
10145
  findAutoMatchNoExpectation,
8755
10146
  findConstructorCwdDropped,
8756
10147
  findFallbackToPass,
@@ -8760,12 +10151,14 @@ export {
8760
10151
  firstStringAttr,
8761
10152
  flattenOtlpExportToNdjson,
8762
10153
  flowLayer,
10154
+ fnv1a32,
8763
10155
  formatBenchmarkReport,
8764
10156
  formatDriverReport,
8765
10157
  formatFindings,
8766
10158
  formatScorecardDiff,
8767
10159
  gainHistogram,
8768
10160
  ghCliClient,
10161
+ gitProvenanceReader,
8769
10162
  precision as goldenPrecision,
8770
10163
  gradeSemanticStatus,
8771
10164
  groupBy,
@@ -8773,10 +10166,15 @@ export {
8773
10166
  hashContent,
8774
10167
  hashJson,
8775
10168
  hashScenarios,
10169
+ hashToUnit,
8776
10170
  htmlContainsElement,
8777
10171
  httpGithubClient,
10172
+ improvementVerdict,
10173
+ inMemoryExperimentStore,
8778
10174
  inMemoryReferenceReplayStore,
8779
10175
  inMemoryReviewStore,
10176
+ inMemoryRunRecordBackend,
10177
+ inMemoryVerdictCache,
8780
10178
  inferDomainKeywords,
8781
10179
  inferOtlpKind,
8782
10180
  interRaterReliability,
@@ -8797,6 +10195,7 @@ export {
8797
10195
  jsonShape,
8798
10196
  jsonlReferenceReplayStore,
8799
10197
  jsonlReviewStore,
10198
+ jsonlRunRecordBackend,
8800
10199
  judgeFamily,
8801
10200
  judgeReplayGate,
8802
10201
  judgeSpans,
@@ -8810,12 +10209,16 @@ export {
8810
10209
  loadScorerFromGrader,
8811
10210
  localCommandRunner,
8812
10211
  lowercaseMutator,
10212
+ makeEvalTools,
8813
10213
  makeFinding,
8814
10214
  mannWhitneyU,
8815
10215
  matchGoldens,
10216
+ matchSpan,
8816
10217
  mergeLayerResults,
8817
10218
  mergeSteeringBundle,
8818
10219
  modelDescriptionBits,
10220
+ modelPriceKey,
10221
+ mulberry32,
8819
10222
  multiToolchainLayer,
8820
10223
  nistAiRmfReport,
8821
10224
  normalizeScores,
@@ -8840,6 +10243,7 @@ export {
8840
10243
  parseRunRecordSafe,
8841
10244
  parseRuntimeTrajectoryHookEvent,
8842
10245
  partialCredit,
10246
+ partitionHeldOut,
8843
10247
  passOrthogonality,
8844
10248
  pixelDeltaRatio,
8845
10249
  planTraceInsightQuestions,
@@ -8883,6 +10287,7 @@ export {
8883
10287
  researchReport,
8884
10288
  resetLockedAppendersForTesting,
8885
10289
  resolveModelPricing,
10290
+ resolveSeat,
8886
10291
  roundTripRunRecord,
8887
10292
  rowCount,
8888
10293
  rowWhere,
@@ -8905,6 +10310,7 @@ export {
8905
10310
  runProposeReview,
8906
10311
  runProposeReviewAsControlLoop,
8907
10312
  runReferenceReplay,
10313
+ runScore,
8908
10314
  runSelfPlay,
8909
10315
  runSemanticConceptJudge,
8910
10316
  runTestGradedScenario,
@@ -8919,6 +10325,7 @@ export {
8919
10325
  scoreRedTeamOutput,
8920
10326
  scoreReferenceReplay,
8921
10327
  scoreTraceInsightReadiness,
10328
+ seatPresets,
8922
10329
  securityJudge,
8923
10330
  selectHarnessVariant,
8924
10331
  selfPreference,
@@ -8944,12 +10351,14 @@ export {
8944
10351
  throwIfRunIncomplete,
8945
10352
  toAgentProfileJson,
8946
10353
  toLangfuseEnvelope,
10354
+ toOpenAiTool,
8947
10355
  toPrometheusText,
8948
10356
  tokenizeDomainWords,
8949
10357
  toolNamesForRun,
8950
10358
  toolSpans,
8951
10359
  traceAnalystFunctionGroup,
8952
10360
  traceAnalystOnRunComplete,
10361
+ traceContract,
8953
10362
  traceJudge,
8954
10363
  traceJudgeEnsemble,
8955
10364
  tracedAnalyzeTraces,
@@ -8960,6 +10369,7 @@ export {
8960
10369
  validateRunRecord,
8961
10370
  verbosityBias,
8962
10371
  verifyAgentProfileCell,
10372
+ verifyAttestation,
8963
10373
  verifyCompletion,
8964
10374
  verifyManifest,
8965
10375
  visualDiff,