@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.
- package/CHANGELOG.md +14 -0
- package/dist/adapters/http.d.ts +3 -3
- package/dist/adapters/langchain.d.ts +3 -3
- package/dist/adapters/otel.d.ts +6 -6
- package/dist/adversarial-DIVcDoI_.d.ts +88 -0
- package/dist/analyst/index.d.ts +11 -10
- package/dist/analyst/index.js +13 -8
- package/dist/analyst/index.js.map +1 -1
- package/dist/analyze-runs-DwCEkpO_.d.ts +81 -0
- package/dist/belief-state/index.d.ts +4 -4
- package/dist/belief-state/index.js +1 -1
- package/dist/benchmarks/index.d.ts +3 -3
- package/dist/campaign/index.d.ts +165 -18
- package/dist/campaign/index.js +289 -14
- package/dist/campaign/index.js.map +1 -1
- package/dist/chunk-45EEMHTC.js +35 -0
- package/dist/chunk-45EEMHTC.js.map +1 -0
- package/dist/{chunk-FZWAFVAA.js → chunk-4FBZZIYD.js} +2 -2
- package/dist/{chunk-YV7J7X5N.js → chunk-5HRORJQY.js} +22 -12
- package/dist/chunk-5HRORJQY.js.map +1 -0
- package/dist/{chunk-OTYQPHPL.js → chunk-6SOJM3VR.js} +5 -5
- package/dist/chunk-BOD4O7OF.js +40 -0
- package/dist/chunk-BOD4O7OF.js.map +1 -0
- package/dist/{chunk-Z7VFTS2J.js → chunk-CY6U5S3X.js} +2 -2
- package/dist/{chunk-VIDQF3F5.js → chunk-D3V5B42D.js} +5 -34
- package/dist/chunk-D3V5B42D.js.map +1 -0
- package/dist/{chunk-YGYXHNAQ.js → chunk-FIUKOSWI.js} +21 -8
- package/dist/chunk-FIUKOSWI.js.map +1 -0
- package/dist/{chunk-WJL2NJXN.js → chunk-GSH6QNNS.js} +2 -2
- package/dist/{chunk-RBNA5AZT.js → chunk-L3JOU6XM.js} +2 -2
- package/dist/{chunk-IDVBLYCY.js → chunk-LMZQ2Z4U.js} +56 -2
- package/dist/{chunk-IDVBLYCY.js.map → chunk-LMZQ2Z4U.js.map} +1 -1
- package/dist/{chunk-VUINJM5M.js → chunk-QAY5UIJO.js} +2 -193
- package/dist/chunk-QAY5UIJO.js.map +1 -0
- package/dist/{chunk-XQL22JDG.js → chunk-QG2OVF2D.js} +101 -7
- package/dist/chunk-QG2OVF2D.js.map +1 -0
- package/dist/chunk-REVYNR6C.js +100 -0
- package/dist/chunk-REVYNR6C.js.map +1 -0
- package/dist/{chunk-ZZ2HOPME.js → chunk-TWS7AZEY.js} +2 -2
- package/dist/chunk-UHMJT4T7.js +200 -0
- package/dist/chunk-UHMJT4T7.js.map +1 -0
- package/dist/chunk-UMMZHCPB.js +190 -0
- package/dist/chunk-UMMZHCPB.js.map +1 -0
- package/dist/chunk-VZSRQ272.js +149 -0
- package/dist/chunk-VZSRQ272.js.map +1 -0
- package/dist/{chunk-L5G7OUKD.js → chunk-XY4DDNEG.js} +8 -190
- package/dist/chunk-XY4DDNEG.js.map +1 -0
- package/dist/chunk-Y47J2LJ3.js +859 -0
- package/dist/chunk-Y47J2LJ3.js.map +1 -0
- package/dist/{chunk-BABOZOSN.js → chunk-ZFIBGEOL.js} +3 -3
- package/dist/chunk-ZFIBGEOL.js.map +1 -0
- package/dist/{code-agent-session-BRXmavYv.d.ts → code-agent-session-BO8nCnv3.d.ts} +1 -1
- package/dist/contract/index.d.ts +24 -95
- package/dist/contract/index.js +16 -755
- package/dist/contract/index.js.map +1 -1
- package/dist/{control-GeE8OhpN.d.ts → control-_Qb7skHX.d.ts} +2 -2
- package/dist/control.d.ts +5 -5
- package/dist/corpus-BoR-041R.d.ts +560 -0
- package/dist/cost-ledger-DuSqlw5B.d.ts +113 -0
- package/dist/counterfactual-Dwibr5IW.d.ts +85 -0
- package/dist/{dataset-B2kL-fSM.d.ts → dataset-BbGkaN2I.d.ts} +1 -1
- package/dist/{registry-DrEQ3Luj.d.ts → default-registry-zoGHUQEH.d.ts} +29 -2
- package/dist/diagnose.d.ts +251 -0
- package/dist/diagnose.js +381 -0
- package/dist/diagnose.js.map +1 -0
- package/dist/{errors-Dwqw-T_m.d.ts → errors-CzMUYo7b.d.ts} +1 -1
- package/dist/{feedback-trajectory-B3rErRsh.d.ts → feedback-trajectory-D9OVLrg9.d.ts} +1 -1
- package/dist/fuzz.d.ts +484 -0
- package/dist/fuzz.js +613 -0
- package/dist/fuzz.js.map +1 -0
- package/dist/governance/index.d.ts +4 -4
- package/dist/hosted/index.d.ts +6 -6
- package/dist/{index-DE3RXAXD.d.ts → index-Bx3gZ8xl.d.ts} +1 -1
- package/dist/index.d.ts +1073 -344
- package/dist/index.js +2099 -689
- package/dist/index.js.map +1 -1
- package/dist/{insight-report-3ADTfClO.d.ts → insight-report-BBwvOh6x.d.ts} +2 -2
- package/dist/{integrity-CJzrpUua.d.ts → integrity-VJ9A7aST.d.ts} +1 -1
- package/dist/{judge-calibration-DilmB3Ml.d.ts → judge-calibration-0p2QcWNE.d.ts} +1 -1
- package/dist/{kind-factory-CVecZZG_.d.ts → kind-factory-5b7xXXOr.d.ts} +2 -2
- package/dist/{llm-client-CuUg2Mn3.d.ts → llm-client-BeEcAokY.d.ts} +1 -1
- package/dist/matrix/index.d.ts +2 -2
- package/dist/meta-eval/index.d.ts +177 -3
- package/dist/meta-eval/index.js +260 -1
- package/dist/meta-eval/index.js.map +1 -1
- package/dist/{multi-layer-verifier-DlWCXuxL.d.ts → multi-layer-verifier-DUZXrPDA.d.ts} +7 -1
- package/dist/multishot/index.d.ts +25 -11
- package/dist/multishot/index.js +36 -7
- package/dist/multishot/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.js +2 -2
- package/dist/{agent-profile-D0PBIWlV.d.ts → pre-registration-DELOEJ8v.d.ts} +144 -4
- package/dist/{provenance-DPpNIOJD.d.ts → provenance-LnqRT0sS.d.ts} +5 -5
- package/dist/{red-team-DW9Ca_tj.d.ts → red-team-BXHil6c8.d.ts} +1 -1
- package/dist/{release-report-hlNtD12q.d.ts → release-report-euXIV_Sk.d.ts} +3 -3
- package/dist/reporting.d.ts +8 -8
- package/dist/reporting.js +3 -3
- package/dist/{researcher-BLPHBbNV.d.ts → researcher-DE6Gpnb4.d.ts} +4 -4
- package/dist/rl.d.ts +194 -656
- package/dist/rl.js +236 -154
- package/dist/rl.js.map +1 -1
- package/dist/{rubric-predictive-validity-CnEl9Jc8.d.ts → rubric-predictive-validity-Cy_W-hWZ.d.ts} +1 -1
- package/dist/{run-campaign-4Y5V5CN3.js → run-campaign-RDGAM5KJ.js} +3 -3
- package/dist/{run-improvement-loop-CNqQckTj.d.ts → run-improvement-loop-5z_l5zDz.d.ts} +2 -2
- package/dist/{run-record-De9VarXR.d.ts → run-record-e7vj1uZQ.d.ts} +1 -1
- package/dist/{runtime-trajectory-BLRiaifm.d.ts → runtime-trajectory-BDgfGZSr.d.ts} +1 -1
- package/dist/{semantic-concept-judge-DIEgr_6v.d.ts → semantic-concept-judge-Dn8Z6KEG.d.ts} +5 -31
- package/dist/series-convergence-D5OWMBg6.d.ts +33 -0
- package/dist/{statistics-CnC1FMbx.d.ts → statistics-C7PozGrZ.d.ts} +71 -2
- package/dist/{summary-report-Db0dDSWP.d.ts → summary-report-DGmUucwQ.d.ts} +1 -1
- package/dist/traces.d.ts +58 -4
- package/dist/traces.js +14 -6
- package/dist/{types-Cu3u_x59.d.ts → types-2VVIL04s.d.ts} +2 -2
- package/dist/{types-D7lLRYe9.d.ts → types-BU-7W85F.d.ts} +21 -1
- package/dist/{types-CqPax19X.d.ts → types-mn5Aqk7x.d.ts} +1 -1
- package/dist/{verdict-CeEgtjyI.d.ts → verdict-C9MlYujm.d.ts} +3 -0
- package/dist/wire/index.d.ts +3 -3
- package/dist/workflow/index.d.ts +12 -11
- package/dist/workflow/index.js +1 -1
- package/package.json +36 -13
- package/dist/chunk-BABOZOSN.js.map +0 -1
- package/dist/chunk-L5G7OUKD.js.map +0 -1
- package/dist/chunk-SHTXZ4O2.js +0 -113
- package/dist/chunk-SHTXZ4O2.js.map +0 -1
- package/dist/chunk-VIDQF3F5.js.map +0 -1
- package/dist/chunk-VUINJM5M.js.map +0 -1
- package/dist/chunk-XQL22JDG.js.map +0 -1
- package/dist/chunk-YGYXHNAQ.js.map +0 -1
- package/dist/chunk-YV7J7X5N.js.map +0 -1
- /package/dist/{chunk-FZWAFVAA.js.map → chunk-4FBZZIYD.js.map} +0 -0
- /package/dist/{chunk-OTYQPHPL.js.map → chunk-6SOJM3VR.js.map} +0 -0
- /package/dist/{chunk-Z7VFTS2J.js.map → chunk-CY6U5S3X.js.map} +0 -0
- /package/dist/{chunk-WJL2NJXN.js.map → chunk-GSH6QNNS.js.map} +0 -0
- /package/dist/{chunk-RBNA5AZT.js.map → chunk-L3JOU6XM.js.map} +0 -0
- /package/dist/{chunk-ZZ2HOPME.js.map → chunk-TWS7AZEY.js.map} +0 -0
- /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-
|
|
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-
|
|
56
|
+
} from "./chunk-ZFIBGEOL.js";
|
|
44
57
|
import {
|
|
45
58
|
BackendIntegrityError,
|
|
46
59
|
assertRealBackend,
|
|
47
60
|
summarizeBackendIntegrity
|
|
48
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
164
|
+
} from "./chunk-4FBZZIYD.js";
|
|
163
165
|
import {
|
|
164
166
|
runEvalCampaign
|
|
165
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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/
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
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
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
2415
|
+
matches2.push({ referenceId: reference.id, commentId: best.comment.id, score: best.score });
|
|
2402
2416
|
}
|
|
2403
|
-
return
|
|
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,
|
|
2444
|
+
function isSeverityAligned(comment, references, matches2) {
|
|
2431
2445
|
if (!comment.severity) return false;
|
|
2432
|
-
const match =
|
|
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
|
|
2742
|
+
const matches2 = output.match(
|
|
2729
2743
|
new RegExp(re, re.flags.includes("g") ? re.flags : `${re.flags}g`)
|
|
2730
2744
|
);
|
|
2731
|
-
if (
|
|
2732
|
-
counts.hedging +=
|
|
2745
|
+
if (matches2) {
|
|
2746
|
+
counts.hedging += matches2.length;
|
|
2733
2747
|
issues.push({
|
|
2734
2748
|
category: "hedging",
|
|
2735
|
-
detail: `${
|
|
2736
|
-
example:
|
|
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
|
|
2755
|
+
const matches2 = output.match(
|
|
2742
2756
|
new RegExp(re, re.flags.includes("g") ? re.flags : `${re.flags}g`)
|
|
2743
2757
|
);
|
|
2744
|
-
if (
|
|
2745
|
-
counts.apology +=
|
|
2758
|
+
if (matches2) {
|
|
2759
|
+
counts.apology += matches2.length;
|
|
2746
2760
|
issues.push({
|
|
2747
2761
|
category: "apology",
|
|
2748
|
-
detail: `${
|
|
2749
|
-
example:
|
|
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
|
|
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:
|
|
3026
|
-
aggregate: aggregateRunScore(
|
|
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(
|
|
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[
|
|
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-
|
|
3175
|
-
var
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
3678
|
-
|
|
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
|
|
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
|
|
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))
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
4400
|
-
|
|
4401
|
-
score:
|
|
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
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
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
|
|
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
|
-
|
|
4420
|
-
|
|
4421
|
-
score
|
|
4422
|
-
const
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
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
|
|
5877
|
-
if (
|
|
5878
|
-
const count = p.transform ?
|
|
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:
|
|
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
|
|
6403
|
-
blendedScore
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
6921
|
-
const matched =
|
|
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 =
|
|
6924
|
-
const totalWeight =
|
|
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
|
|
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 =
|
|
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
|
|
8535
|
+
const matches2 = goldens.map((golden) => goldenMatched(golden, haystacks));
|
|
7363
8536
|
return {
|
|
7364
|
-
matches,
|
|
7365
|
-
hits:
|
|
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
|
-
|
|
8067
|
-
|
|
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,
|