@cassiomc1/forgeloop 1.6.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/AGENT_COMPATIBILITY.md +11 -0
  2. package/DOCS_INDEX.md +1 -0
  3. package/GUIDE_ROUTER.md +26 -1
  4. package/LOOP_ENGINEERING.md +48 -0
  5. package/ORCHESTRATOR_INTEGRATION.md +38 -0
  6. package/PROTOCOL_INTEGRATION.md +62 -0
  7. package/README.md +25 -0
  8. package/benchmarks/execution-profiles/README.md +44 -0
  9. package/benchmarks/execution-profiles/api-feature.json +18 -0
  10. package/benchmarks/execution-profiles/authentication-change.json +18 -0
  11. package/benchmarks/execution-profiles/documentation-correction.json +18 -0
  12. package/benchmarks/execution-profiles/infrastructure-release.json +18 -0
  13. package/benchmarks/execution-profiles/novatask-saas-landing-page.json +36 -0
  14. package/benchmarks/execution-profiles/small-bug-fix.json +18 -0
  15. package/benchmarks/execution-profiles/static-landing-page.json +18 -0
  16. package/completions/_forgeloop +6 -4
  17. package/completions/forgeloop.bash +8 -4
  18. package/completions/forgeloop.fish +24 -1
  19. package/docs/AGENT_PROTOCOL_SUMMARY.md +45 -1
  20. package/docs/ARTIFACT_REFERENCE.md +53 -7
  21. package/docs/CLI_REFERENCE.md +83 -1
  22. package/docs/EXECUTION_PROFILE_BENCHMARKS.md +208 -0
  23. package/docs/GETTING_STARTED.md +28 -0
  24. package/docs/MCP.md +6 -0
  25. package/docs/RELEASE_CHECKLIST.md +4 -0
  26. package/docs/TROUBLESHOOTING.md +6 -0
  27. package/docs/UNIVERSAL_INTEGRATION.md +58 -0
  28. package/package.json +14 -2
  29. package/schemas/config.schema.json +1 -0
  30. package/schemas/execution-profile-benchmark-aggregate.schema.json +43 -0
  31. package/schemas/execution-profile-benchmark-run.schema.json +106 -0
  32. package/schemas/execution-profile-benchmark-scenario.schema.json +66 -0
  33. package/schemas/routing-result.schema.json +12 -0
  34. package/schemas/usage.schema.json +30 -0
  35. package/scripts/check-efficiency-regression.mjs +99 -0
  36. package/scripts/generate-agent-protocol-summary.mjs +35 -0
  37. package/scripts/lib/execution-profile-benchmark-io.mjs +67 -0
  38. package/scripts/run-execution-profile-benchmarks.mjs +265 -0
  39. package/scripts/summarize-execution-profile-benchmarks.mjs +84 -0
  40. package/scripts/validate-execution-profile-benchmarks.mjs +120 -0
  41. package/src/cli.js +16 -4
  42. package/src/commands/efficiency.js +12 -0
  43. package/src/commands/eval.js +8 -2
  44. package/src/commands/metrics.js +2 -2
  45. package/src/commands/next.js +26 -2
  46. package/src/commands/route.js +20 -2
  47. package/src/commands/task-show.js +31 -2
  48. package/src/commands/usage-record.js +61 -0
  49. package/src/core/artifact-registry.js +12 -0
  50. package/src/core/cli-command-definitions.js +27 -0
  51. package/src/core/command-executors.js +29 -5
  52. package/src/core/command-input.js +34 -0
  53. package/src/core/config.js +9 -0
  54. package/src/core/efficiency.js +197 -0
  55. package/src/core/error-codes.js +18 -0
  56. package/src/core/execution-profile-benchmarks.js +674 -0
  57. package/src/core/execution-profile-context.js +177 -0
  58. package/src/core/execution-profile.js +248 -0
  59. package/src/core/integration-invocation-policy.js +43 -0
  60. package/src/core/integration-resources.js +22 -1
  61. package/src/core/protocol-info.js +42 -0
  62. package/src/core/resumability.js +27 -1
  63. package/src/core/router.js +23 -1
  64. package/src/core/runtime-context.js +11 -0
  65. package/src/core/schema-validation.js +4 -0
  66. package/src/core/task-paths.js +2 -0
  67. package/src/core/templates.js +4 -0
  68. package/src/core/trace.js +1 -0
  69. package/src/core/trajectory-evaluation.js +2 -2
  70. package/src/core/trajectory-metrics.js +18 -2
  71. package/src/core/usage.js +137 -0
  72. package/src/integration.d.ts +84 -0
  73. package/src/integration.js +14 -0
@@ -0,0 +1,674 @@
1
+ import { canonicalFingerprint } from "./artifacts.js";
2
+ import { evaluateRoute } from "./router.js";
3
+ import { normalizeUsage } from "./usage.js";
4
+
5
+ export const BENCHMARK_VERSION = "1";
6
+ export const BENCHMARK_MODES = Object.freeze([
7
+ "direct",
8
+ "forgeloopBalanced",
9
+ "forgeloopAdaptive",
10
+ ]);
11
+ export const BENCHMARK_VERIFICATION_RESULTS = Object.freeze([
12
+ "PASS",
13
+ "FAIL",
14
+ "NOT_AVAILABLE",
15
+ ]);
16
+ export const BENCHMARK_USAGE_SOURCES = Object.freeze([
17
+ "PROVIDER_REPORTED",
18
+ "HOST_REPORTED",
19
+ "UNKNOWN",
20
+ ]);
21
+ export const BENCHMARK_CONTEXT_USAGE_SOURCES = Object.freeze([
22
+ "HOST_REPORTED",
23
+ "UNKNOWN",
24
+ ]);
25
+ export const BENCHMARK_CONTEXT_USAGE_ITEMS = Object.freeze([
26
+ "taskContext",
27
+ "guides",
28
+ "history",
29
+ "protocolInstructions",
30
+ "repositoryContext",
31
+ "other",
32
+ ]);
33
+ export const BENCHMARK_QUALITY_SOURCES = Object.freeze([
34
+ "EXTERNAL_REPORTED",
35
+ "HOST_REPORTED",
36
+ "UNKNOWN",
37
+ ]);
38
+ export const BENCHMARK_QUALITY_FIELDS = Object.freeze([
39
+ "visualQuality",
40
+ "responsiveQuality",
41
+ "accessibility",
42
+ "interactionPolish",
43
+ "requirementsCompleteness",
44
+ ]);
45
+ export const REQUIRED_BENCHMARK_SCENARIO_IDS = Object.freeze([
46
+ "documentation-correction",
47
+ "static-landing-page",
48
+ "small-bug-fix",
49
+ "api-feature",
50
+ "authentication-change",
51
+ "infrastructure-release",
52
+ "novatask-saas-landing-page",
53
+ ]);
54
+ export const LIGHT_EFFICIENCY_OBJECTIVES = Object.freeze({
55
+ p50TokenOverheadPercent: 35,
56
+ p95TokenOverheadPercent: 60,
57
+ });
58
+
59
+ const PROFILE_BY_MODE = Object.freeze({
60
+ direct: { requestedProfile: null, resolvedProfile: null },
61
+ forgeloopBalanced: { requestedProfile: "balanced", resolvedProfile: null },
62
+ forgeloopAdaptive: { requestedProfile: "auto", resolvedProfile: null },
63
+ });
64
+
65
+ function benchmarkError(message) {
66
+ const error = new Error(message);
67
+ error.code = "E_BENCHMARK_INVALID";
68
+ return error;
69
+ }
70
+
71
+ function assertObject(value, label) {
72
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
73
+ throw benchmarkError(`${label} must be an object`);
74
+ }
75
+ return value;
76
+ }
77
+
78
+ function assertString(value, label, { nullable = false } = {}) {
79
+ if (nullable && value === null) return value;
80
+ if (typeof value !== "string" || value.length === 0) {
81
+ throw benchmarkError(`${label} must be a non-empty string${nullable ? " or null" : ""}`);
82
+ }
83
+ return value;
84
+ }
85
+
86
+ function assertNullableFiniteNumber(value, label) {
87
+ if (value === null) return value;
88
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
89
+ throw benchmarkError(`${label} must be a non-negative finite number or null`);
90
+ }
91
+ return value;
92
+ }
93
+
94
+ function assertNullableNonNegativeInteger(value, label) {
95
+ if (value === null) return value;
96
+ if (!Number.isInteger(value) || value < 0) {
97
+ throw benchmarkError(`${label} must be a non-negative integer or null`);
98
+ }
99
+ return value;
100
+ }
101
+
102
+ function assertNullableProfile(value, label) {
103
+ if (value === null) return value;
104
+ if (typeof value !== "string" || !["light", "balanced", "full"].includes(value)) {
105
+ throw benchmarkError(`${label} must be light, balanced, full, or null`);
106
+ }
107
+ return value;
108
+ }
109
+
110
+ function assertRunSetId(value) {
111
+ assertString(value, "runSetId");
112
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u.test(value)) {
113
+ throw benchmarkError("runSetId contains unsupported characters");
114
+ }
115
+ return value;
116
+ }
117
+
118
+ function assertRunId(value) {
119
+ assertString(value, "runId");
120
+ if (!/^run-[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u.test(value)) {
121
+ throw benchmarkError("runId must start with run- and contain safe identifier characters");
122
+ }
123
+ return value;
124
+ }
125
+
126
+ function assertScenarioId(value) {
127
+ assertString(value, "scenarioId");
128
+ if (!/^[a-z0-9][a-z0-9-]{0,127}$/u.test(value)) {
129
+ throw benchmarkError("scenarioId must be a lowercase portable identifier");
130
+ }
131
+ return value;
132
+ }
133
+
134
+ function assertTimestamp(value, label) {
135
+ assertString(value, label);
136
+ if (Number.isNaN(Date.parse(value))) throw benchmarkError(`${label} must be a valid timestamp`);
137
+ return value;
138
+ }
139
+
140
+ function normalizedModeProfile(mode, resolvedProfile) {
141
+ const expected = PROFILE_BY_MODE[mode];
142
+ if (!expected) throw benchmarkError(`Unsupported benchmark mode: ${mode}`);
143
+ if (mode === "direct" && resolvedProfile !== null) {
144
+ throw benchmarkError(`${mode} must not report a ForgeLoop resolved profile`);
145
+ }
146
+ if (mode !== "direct" && (typeof resolvedProfile !== "string" || !resolvedProfile)) {
147
+ throw benchmarkError(`${mode} must report its resolved ForgeLoop profile`);
148
+ }
149
+ if (mode !== "direct" && !["light", "balanced", "full"].includes(resolvedProfile)) {
150
+ throw benchmarkError(`${mode} resolvedProfile must be light, balanced, or full`);
151
+ }
152
+ return expected;
153
+ }
154
+
155
+ export function assertBenchmarkScenario(value) {
156
+ const scenario = assertObject(value, "benchmark scenario");
157
+ if (scenario.schemaVersion !== 1) throw benchmarkError("benchmark scenario schemaVersion must be 1");
158
+ if (scenario.benchmarkVersion !== BENCHMARK_VERSION) {
159
+ throw benchmarkError(`benchmark scenario benchmarkVersion must be ${BENCHMARK_VERSION}`);
160
+ }
161
+ assertScenarioId(scenario.scenarioId);
162
+ assertString(scenario.description, "description");
163
+ assertObject(scenario.input, "input");
164
+ assertString(scenario.input.workType, "input.workType");
165
+ for (const key of ["surfaces", "risks", "platforms"]) {
166
+ if (!Array.isArray(scenario.input[key]) || scenario.input[key].some((item) => typeof item !== "string" || !item)) {
167
+ throw benchmarkError(`input.${key} must be an array of non-empty strings`);
168
+ }
169
+ }
170
+ if (!["light", "balanced", "full"].includes(scenario.expectedProfile)) {
171
+ throw benchmarkError("expectedProfile must be light, balanced, or full");
172
+ }
173
+ let resolvedProfile;
174
+ try {
175
+ resolvedProfile = evaluateRoute(scenario.input).executionProfile.resolved;
176
+ } catch (error) {
177
+ throw benchmarkError(`input must be a valid ForgeLoop route: ${error.message}`);
178
+ }
179
+ if (resolvedProfile !== scenario.expectedProfile) {
180
+ throw benchmarkError(
181
+ `expectedProfile does not match the default route resolution: ${scenario.expectedProfile} !== ${resolvedProfile}`,
182
+ );
183
+ }
184
+ if (scenario.referenceTask !== undefined) {
185
+ const referenceTask = assertObject(scenario.referenceTask, "referenceTask");
186
+ assertString(referenceTask.name, "referenceTask.name");
187
+ for (const field of ["requirements", "exclusions"]) {
188
+ if (!Array.isArray(referenceTask[field]) || referenceTask[field].some((item) => typeof item !== "string" || item.trim() === "")) {
189
+ throw benchmarkError(`referenceTask.${field} must be an array of non-empty strings`);
190
+ }
191
+ }
192
+ assertString(referenceTask.artifactMode, "referenceTask.artifactMode");
193
+ }
194
+ assertObject(scenario.measurements, "measurements");
195
+ for (const mode of BENCHMARK_MODES) {
196
+ const measurement = assertObject(scenario.measurements[mode], `measurements.${mode}`);
197
+ for (const field of [
198
+ "inputTokens",
199
+ "outputTokens",
200
+ "cacheReadTokens",
201
+ "cacheWriteTokens",
202
+ "totalTokens",
203
+ ]) {
204
+ assertNullableNonNegativeInteger(measurement[field], `measurements.${mode}.${field}`);
205
+ }
206
+ assertNullableFiniteNumber(measurement.wallClockMs, `measurements.${mode}.wallClockMs`);
207
+ if (!BENCHMARK_VERIFICATION_RESULTS.includes(measurement.verification)) {
208
+ throw benchmarkError(`measurements.${mode}.verification is not a supported result`);
209
+ }
210
+ }
211
+ return scenario;
212
+ }
213
+
214
+ export function assertRequiredBenchmarkScenarios(scenarios) {
215
+ if (!Array.isArray(scenarios)) throw benchmarkError("benchmark scenarios must be an array");
216
+ const ids = new Set(scenarios.map((scenario) => scenario.scenarioId));
217
+ const missing = REQUIRED_BENCHMARK_SCENARIO_IDS.filter((scenarioId) => !ids.has(scenarioId));
218
+ if (missing.length > 0) {
219
+ throw benchmarkError(`missing required benchmark scenario(s): ${missing.join(", ")}`);
220
+ }
221
+ return scenarios;
222
+ }
223
+
224
+ function normalizeBenchmarkUsage(value) {
225
+ let usage;
226
+ try {
227
+ usage = normalizeUsage(value, {
228
+ defaultSource: "UNKNOWN",
229
+ allowedSources: BENCHMARK_USAGE_SOURCES,
230
+ });
231
+ } catch (error) {
232
+ throw benchmarkError(error.message);
233
+ }
234
+ if (usage.source === "UNKNOWN" && [
235
+ usage.inputTokens,
236
+ usage.outputTokens,
237
+ usage.cacheReadTokens,
238
+ usage.cacheWriteTokens,
239
+ usage.totalTokens,
240
+ usage.costUsd,
241
+ usage.model,
242
+ usage.provider,
243
+ ].some((field) => field !== null)) {
244
+ throw benchmarkError("UNKNOWN usage must keep all measured values and identity fields null");
245
+ }
246
+ return usage;
247
+ }
248
+
249
+ export function normalizeBenchmarkContextUsage(value, expectedProfile = null) {
250
+ assertNullableProfile(expectedProfile, "contextUsage expected profile");
251
+ if (value === undefined || value === null) {
252
+ return {
253
+ source: "UNKNOWN",
254
+ profile: expectedProfile,
255
+ items: Object.fromEntries(BENCHMARK_CONTEXT_USAGE_ITEMS.map((item) => [item, null])),
256
+ };
257
+ }
258
+ const contextUsage = assertObject(value, "contextUsage");
259
+ const source = contextUsage.source ?? "UNKNOWN";
260
+ if (!BENCHMARK_CONTEXT_USAGE_SOURCES.includes(source)) {
261
+ throw benchmarkError(`contextUsage.source must be one of ${BENCHMARK_CONTEXT_USAGE_SOURCES.join(", ")}`);
262
+ }
263
+ const profile = contextUsage.profile ?? expectedProfile;
264
+ assertNullableProfile(profile, "contextUsage.profile");
265
+ if (profile !== expectedProfile) {
266
+ throw benchmarkError("contextUsage.profile must match the resolved benchmark profile");
267
+ }
268
+ const items = assertObject(contextUsage.items ?? {}, "contextUsage.items");
269
+ const unknownItem = Object.keys(items).find((item) => !BENCHMARK_CONTEXT_USAGE_ITEMS.includes(item));
270
+ if (unknownItem) throw benchmarkError(`contextUsage.items contains unsupported item: ${unknownItem}`);
271
+ const normalizedItems = Object.fromEntries(BENCHMARK_CONTEXT_USAGE_ITEMS.map((item) => {
272
+ const valueForItem = items[item] ?? null;
273
+ return [item, assertNullableNonNegativeInteger(valueForItem, `contextUsage.items.${item}`)];
274
+ }));
275
+ if (source === "UNKNOWN" && Object.values(normalizedItems).some((item) => item !== null)) {
276
+ throw benchmarkError("UNKNOWN context usage must keep every context item null");
277
+ }
278
+ return { source, profile, items: normalizedItems };
279
+ }
280
+
281
+ export function normalizeBenchmarkQuality(value) {
282
+ if (value === undefined || value === null) {
283
+ return {
284
+ source: "UNKNOWN",
285
+ scores: Object.fromEntries(BENCHMARK_QUALITY_FIELDS.map((field) => [field, null])),
286
+ };
287
+ }
288
+ const quality = assertObject(value, "quality");
289
+ const source = quality.source ?? "UNKNOWN";
290
+ if (!BENCHMARK_QUALITY_SOURCES.includes(source)) {
291
+ throw benchmarkError(`quality.source must be one of ${BENCHMARK_QUALITY_SOURCES.join(", ")}`);
292
+ }
293
+ const scores = assertObject(quality.scores ?? {}, "quality.scores");
294
+ const unknownScore = Object.keys(scores).find((field) => !BENCHMARK_QUALITY_FIELDS.includes(field));
295
+ if (unknownScore) throw benchmarkError(`quality.scores contains unsupported field: ${unknownScore}`);
296
+ const normalizedScores = Object.fromEntries(BENCHMARK_QUALITY_FIELDS.map((field) => {
297
+ const score = scores[field] ?? null;
298
+ if (score !== null && (typeof score !== "number" || !Number.isFinite(score) || score < 0 || score > 5)) {
299
+ throw benchmarkError(`quality.scores.${field} must be between 0 and 5 or null`);
300
+ }
301
+ return [field, score];
302
+ }));
303
+ if (source === "UNKNOWN" && Object.values(normalizedScores).some((score) => score !== null)) {
304
+ throw benchmarkError("UNKNOWN quality must keep every score null");
305
+ }
306
+ return { source, scores: normalizedScores };
307
+ }
308
+
309
+ function contextUsageForRun(run) {
310
+ return normalizeBenchmarkContextUsage(run.contextUsage, run.metadata?.resolvedProfile ?? null);
311
+ }
312
+
313
+ function measuredContextTokens(contextUsage) {
314
+ if (contextUsage.source !== "HOST_REPORTED") return null;
315
+ const values = BENCHMARK_CONTEXT_USAGE_ITEMS.map((item) => contextUsage.items[item]);
316
+ if (values.some((item) => item === null)) return null;
317
+ return values.reduce((sum, item) => sum + item, 0);
318
+ }
319
+
320
+ function assertBenchmarkMetadata(metadata, run) {
321
+ assertObject(metadata, "metadata");
322
+ assertScenarioId(metadata.scenarioId);
323
+ if (!BENCHMARK_MODES.includes(metadata.mode)) throw benchmarkError("metadata.mode is unsupported");
324
+ if (metadata.scenarioId !== run.scenarioId || metadata.mode !== run.mode) {
325
+ throw benchmarkError("benchmark metadata does not match the run identity");
326
+ }
327
+ if (metadata.benchmarkVersion !== run.benchmarkVersion) {
328
+ throw benchmarkError("benchmark metadata benchmarkVersion does not match the run");
329
+ }
330
+ assertString(metadata.environmentClass, "metadata.environmentClass");
331
+ for (const field of ["model", "provider", "projectRevision"]) {
332
+ assertString(metadata[field], `metadata.${field}`, { nullable: true });
333
+ }
334
+ assertString(metadata.promptSpecFingerprint, "metadata.promptSpecFingerprint");
335
+ assertString(metadata.requestedProfile, "metadata.requestedProfile", { nullable: true });
336
+ assertString(metadata.resolvedProfile, "metadata.resolvedProfile", { nullable: true });
337
+ normalizedModeProfile(run.mode, metadata.resolvedProfile);
338
+ if (metadata.requestedProfile !== PROFILE_BY_MODE[run.mode].requestedProfile) {
339
+ throw benchmarkError("metadata.requestedProfile does not match the benchmark mode");
340
+ }
341
+ assertNullableNonNegativeInteger(metadata.verificationCycles, "metadata.verificationCycles");
342
+ assertNullableNonNegativeInteger(metadata.comparableSteps, "metadata.comparableSteps");
343
+ if (metadata.verificationCycles !== run.verificationCycles || metadata.comparableSteps !== run.comparableSteps) {
344
+ throw benchmarkError("benchmark metadata does not match measured run values");
345
+ }
346
+ for (const field of ["nodeVersion", "os", "arch"]) {
347
+ if (metadata[field] !== undefined) assertString(metadata[field], `metadata.${field}`);
348
+ }
349
+ return metadata;
350
+ }
351
+
352
+ export function assertBenchmarkRun(value) {
353
+ const run = assertObject(value, "benchmark run");
354
+ if (run.schemaVersion !== 1) throw benchmarkError("benchmark run schemaVersion must be 1");
355
+ if (run.benchmarkVersion !== BENCHMARK_VERSION) {
356
+ throw benchmarkError(`benchmark run benchmarkVersion must be ${BENCHMARK_VERSION}`);
357
+ }
358
+ assertRunSetId(run.runSetId);
359
+ assertRunId(run.runId);
360
+ assertScenarioId(run.scenarioId);
361
+ if (!BENCHMARK_MODES.includes(run.mode)) throw benchmarkError("benchmark run mode is unsupported");
362
+ if (!Number.isInteger(run.runIndex) || run.runIndex < 1) throw benchmarkError("runIndex must be a positive integer");
363
+ assertTimestamp(run.recordedAt, "recordedAt");
364
+ const usage = normalizeBenchmarkUsage(run.usage);
365
+ assertNullableFiniteNumber(run.wallClockMs, "wallClockMs");
366
+ if (!BENCHMARK_VERIFICATION_RESULTS.includes(run.verification)) {
367
+ throw benchmarkError("verification is not a supported result");
368
+ }
369
+ assertNullableNonNegativeInteger(run.verificationCycles, "verificationCycles");
370
+ assertNullableNonNegativeInteger(run.comparableSteps, "comparableSteps");
371
+ assertBenchmarkMetadata(run.metadata, run);
372
+ normalizeBenchmarkContextUsage(run.contextUsage, run.metadata.resolvedProfile);
373
+ normalizeBenchmarkQuality(run.quality);
374
+ if (usage.model !== null && run.metadata.model !== usage.model) {
375
+ throw benchmarkError("metadata.model must match usage.model when reported");
376
+ }
377
+ if (usage.provider !== null && run.metadata.provider !== usage.provider) {
378
+ throw benchmarkError("metadata.provider must match usage.provider when reported");
379
+ }
380
+ return run;
381
+ }
382
+
383
+ export function createBenchmarkRun({
384
+ runSetId,
385
+ runId,
386
+ runIndex,
387
+ scenario,
388
+ mode,
389
+ recordedAt = new Date().toISOString(),
390
+ usage = {},
391
+ wallClockMs = null,
392
+ verification = "NOT_AVAILABLE",
393
+ verificationCycles = null,
394
+ comparableSteps = null,
395
+ contextUsage = undefined,
396
+ quality = undefined,
397
+ metadata = {},
398
+ } = {}) {
399
+ assertBenchmarkScenario(scenario);
400
+ const normalizedUsage = normalizeBenchmarkUsage(usage);
401
+ const profile = PROFILE_BY_MODE[mode];
402
+ if (!profile) throw benchmarkError(`Unsupported benchmark mode: ${mode}`);
403
+ const resolvedProfile = metadata.resolvedProfile ?? profile.resolvedProfile;
404
+ normalizedModeProfile(mode, resolvedProfile);
405
+ const run = {
406
+ schemaVersion: 1,
407
+ benchmarkVersion: BENCHMARK_VERSION,
408
+ runSetId,
409
+ runId,
410
+ scenarioId: scenario.scenarioId,
411
+ mode,
412
+ runIndex,
413
+ recordedAt,
414
+ usage: normalizedUsage,
415
+ wallClockMs,
416
+ verification,
417
+ verificationCycles,
418
+ comparableSteps,
419
+ contextUsage: normalizeBenchmarkContextUsage(contextUsage, resolvedProfile),
420
+ quality: normalizeBenchmarkQuality(quality),
421
+ metadata: {
422
+ scenarioId: scenario.scenarioId,
423
+ mode,
424
+ model: metadata.model ?? normalizedUsage.model,
425
+ provider: metadata.provider ?? normalizedUsage.provider,
426
+ promptSpecFingerprint: metadata.promptSpecFingerprint,
427
+ projectRevision: metadata.projectRevision ?? null,
428
+ benchmarkVersion: BENCHMARK_VERSION,
429
+ environmentClass: metadata.environmentClass,
430
+ requestedProfile: profile.requestedProfile,
431
+ resolvedProfile,
432
+ verificationCycles,
433
+ comparableSteps,
434
+ ...(metadata.nodeVersion ? { nodeVersion: metadata.nodeVersion } : {}),
435
+ ...(metadata.os ? { os: metadata.os } : {}),
436
+ ...(metadata.arch ? { arch: metadata.arch } : {}),
437
+ },
438
+ };
439
+ return assertBenchmarkRun(run);
440
+ }
441
+
442
+ function statistic(values) {
443
+ const usable = values.filter((value) => typeof value === "number" && Number.isFinite(value)).sort((a, b) => a - b);
444
+ if (usable.length === 0) return { count: 0, average: null, p50: null, p95: null, minimum: null, maximum: null };
445
+ const percentile = (fraction) => {
446
+ const position = (usable.length - 1) * fraction;
447
+ const lower = Math.floor(position);
448
+ const upper = Math.ceil(position);
449
+ if (lower === upper) return usable[lower];
450
+ return usable[lower] + ((usable[upper] - usable[lower]) * (position - lower));
451
+ };
452
+ return {
453
+ count: usable.length,
454
+ average: Number((usable.reduce((sum, value) => sum + value, 0) / usable.length).toFixed(4)),
455
+ p50: Number(percentile(0.5).toFixed(4)),
456
+ p95: Number(percentile(0.95).toFixed(4)),
457
+ minimum: usable[0],
458
+ maximum: usable.at(-1),
459
+ };
460
+ }
461
+
462
+ function trustedMeasurement(run) {
463
+ return ["PROVIDER_REPORTED", "HOST_REPORTED"].includes(run.usage.source)
464
+ && run.verification === "PASS"
465
+ && run.metadata.model !== null
466
+ && run.metadata.provider !== null
467
+ && run.metadata.promptSpecFingerprint !== null
468
+ && run.metadata.projectRevision !== null
469
+ && run.metadata.comparableSteps !== null
470
+ && run.metadata.comparableSteps > 0;
471
+ }
472
+
473
+ function comparablePair(left, right) {
474
+ if (!trustedMeasurement(left) || !trustedMeasurement(right)) return false;
475
+ const fields = [
476
+ "scenarioId",
477
+ "model",
478
+ "provider",
479
+ "promptSpecFingerprint",
480
+ "projectRevision",
481
+ "benchmarkVersion",
482
+ "environmentClass",
483
+ "nodeVersion",
484
+ "os",
485
+ "arch",
486
+ ];
487
+ return fields.every((field) => (left.metadata[field] ?? null) === (right.metadata[field] ?? null));
488
+ }
489
+
490
+ function comparisonForMode(directRuns, modeRuns) {
491
+ const directByIndex = new Map(directRuns.map((run) => [run.runIndex, run]));
492
+ const pairs = modeRuns
493
+ .map((run) => ({ variant: run, direct: directByIndex.get(run.runIndex) }))
494
+ .filter((pair) => pair.direct && comparablePair(pair.direct, pair.variant));
495
+ const tokenOverheads = pairs
496
+ .filter(({ direct, variant }) => Number.isFinite(direct.usage.totalTokens) && Number.isFinite(variant.usage.totalTokens) && direct.usage.totalTokens > 0)
497
+ .map(({ direct, variant }) => ((variant.usage.totalTokens - direct.usage.totalTokens) / direct.usage.totalTokens) * 100);
498
+ const timeOverheads = pairs
499
+ .filter(({ direct, variant }) => Number.isFinite(direct.wallClockMs) && Number.isFinite(variant.wallClockMs) && direct.wallClockMs > 0)
500
+ .map(({ direct, variant }) => ((variant.wallClockMs - direct.wallClockMs) / direct.wallClockMs) * 100);
501
+ const tokenStats = statistic(tokenOverheads);
502
+ const timeStats = statistic(timeOverheads);
503
+ const claimAllowed = tokenStats.count > 0 && timeStats.count > 0;
504
+ return {
505
+ comparablePairs: pairs.length,
506
+ tokenComparablePairs: tokenStats.count,
507
+ timeComparablePairs: timeStats.count,
508
+ tokenOverheadPercent: { p50: tokenStats.p50, p95: tokenStats.p95 },
509
+ timeOverheadPercent: { p50: timeStats.p50, p95: timeStats.p95 },
510
+ claimStatus: claimAllowed ? "OBSERVATIONAL" : "NOT_COMPARABLE",
511
+ claimAllowed,
512
+ reason: claimAllowed
513
+ ? "Trusted usage, actual timing, verification, and matching comparability metadata are present."
514
+ : "Efficiency claims require trusted usage, actual timing, PASS verification, positive comparable steps, and matching metadata.",
515
+ };
516
+ }
517
+
518
+ function contextUsageAggregate(runs) {
519
+ const contextUsages = runs.map(contextUsageForRun);
520
+ const totals = contextUsages.map(measuredContextTokens);
521
+ return {
522
+ sources: [...new Set(contextUsages.map((contextUsage) => contextUsage.source))].sort(),
523
+ profiles: [...new Set(contextUsages.map((contextUsage) => contextUsage.profile).filter(Boolean))].sort(),
524
+ measuredRuns: totals.filter((value) => value !== null).length,
525
+ totalTokens: statistic(totals),
526
+ items: Object.fromEntries(BENCHMARK_CONTEXT_USAGE_ITEMS.map((item) => [
527
+ item,
528
+ statistic(contextUsages.map((contextUsage) => contextUsage.items[item])),
529
+ ])),
530
+ };
531
+ }
532
+
533
+ function qualityAggregate(runs) {
534
+ const quality = runs.map((run) => normalizeBenchmarkQuality(run.quality));
535
+ return {
536
+ sources: [...new Set(quality.map((item) => item.source))].sort(),
537
+ scores: Object.fromEntries(BENCHMARK_QUALITY_FIELDS.map((field) => [
538
+ field,
539
+ statistic(quality.map((item) => item.scores[field])),
540
+ ])),
541
+ };
542
+ }
543
+
544
+ function contextInflationForScenario(balancedRuns, adaptiveRuns) {
545
+ const lightRuns = adaptiveRuns.filter((run) => run.metadata.resolvedProfile === "light");
546
+ const balancedByIndex = new Map(balancedRuns.map((run) => [run.runIndex, run]));
547
+ const pairs = lightRuns
548
+ .map((light) => ({
549
+ light,
550
+ balanced: balancedByIndex.get(light.runIndex),
551
+ }))
552
+ .map((pair) => ({
553
+ ...pair,
554
+ lightTotal: pair.light ? measuredContextTokens(contextUsageForRun(pair.light)) : null,
555
+ balancedTotal: pair.balanced ? measuredContextTokens(contextUsageForRun(pair.balanced)) : null,
556
+ }))
557
+ .filter((pair) => pair.light && pair.balanced && pair.lightTotal !== null && pair.balancedTotal !== null);
558
+ const inflatedPairs = pairs.filter((pair) => pair.lightTotal > pair.balancedTotal);
559
+ const status = lightRuns.length === 0
560
+ ? "NOT_COMPARABLE"
561
+ : pairs.length === 0
562
+ ? "NOT_COMPARABLE"
563
+ : inflatedPairs.length > 0 ? "CONTEXT_INFLATION" : "NOT_DETECTED";
564
+ return {
565
+ status,
566
+ comparablePairs: pairs.length,
567
+ inflatedPairs: inflatedPairs.length,
568
+ lightContextTokens: statistic(pairs.map((pair) => pair.lightTotal)),
569
+ balancedContextTokens: statistic(pairs.map((pair) => pair.balancedTotal)),
570
+ blocking: false,
571
+ reason: status === "CONTEXT_INFLATION"
572
+ ? "Host-reported LIGHT context exceeded the comparable BALANCED context in at least one repeated run."
573
+ : status === "NOT_DETECTED"
574
+ ? "Comparable host-reported LIGHT and BALANCED context values did not show inflation."
575
+ : lightRuns.length === 0
576
+ ? "The adaptive mode did not resolve to LIGHT for this scenario."
577
+ : "Context inflation requires complete HOST_REPORTED context items for matching LIGHT and BALANCED runs.",
578
+ };
579
+ }
580
+
581
+ function modeAggregate(runs, { includeContextUsage = false, includeQuality = false } = {}) {
582
+ const verificationCount = runs.filter((run) => run.verification === "PASS").length;
583
+ const comparableSteps = runs.map((run) => run.comparableSteps);
584
+ const tokenPerStep = runs.map((run) => (
585
+ Number.isFinite(run.usage.totalTokens) && Number.isInteger(run.comparableSteps) && run.comparableSteps > 0
586
+ ? run.usage.totalTokens / run.comparableSteps
587
+ : null
588
+ ));
589
+ return {
590
+ runCount: runs.length,
591
+ verificationSuccessRate: runs.length > 0 ? Number((verificationCount / runs.length).toFixed(4)) : null,
592
+ usageSources: [...new Set(runs.map((run) => run.usage.source))].sort(),
593
+ totalTokens: statistic(runs.map((run) => run.usage.totalTokens)),
594
+ wallClockMs: statistic(runs.map((run) => run.wallClockMs)),
595
+ verificationCycles: statistic(runs.map((run) => run.verificationCycles)),
596
+ comparableSteps: statistic(comparableSteps),
597
+ tokensPerComparableStep: statistic(tokenPerStep),
598
+ ...(includeContextUsage ? { contextUsage: contextUsageAggregate(runs) } : {}),
599
+ ...(includeQuality ? { quality: qualityAggregate(runs) } : {}),
600
+ };
601
+ }
602
+
603
+ export function aggregateBenchmarkRuns({ scenario, runs } = {}) {
604
+ assertBenchmarkScenario(scenario);
605
+ if (!Array.isArray(runs) || runs.length === 0) throw benchmarkError("runs must contain at least one benchmark run");
606
+ for (const run of runs) assertBenchmarkRun(run);
607
+ const runSetIds = new Set(runs.map((run) => run.runSetId));
608
+ if (runSetIds.size !== 1) throw benchmarkError("all runs in an aggregate must belong to one run set");
609
+ if (runs.some((run) => run.scenarioId !== scenario.scenarioId)) {
610
+ throw benchmarkError("aggregate runs must belong to the supplied scenario");
611
+ }
612
+ const byMode = Object.fromEntries(BENCHMARK_MODES.map((mode) => [
613
+ mode,
614
+ runs.filter((run) => run.mode === mode).sort((left, right) => left.runIndex - right.runIndex),
615
+ ]));
616
+ const duplicateKeys = new Set();
617
+ for (const run of runs) {
618
+ const key = `${run.mode}:${run.runIndex}`;
619
+ if (duplicateKeys.has(key)) throw benchmarkError(`duplicate benchmark run: ${key}`);
620
+ duplicateKeys.add(key);
621
+ }
622
+ const directRuns = byMode.direct;
623
+ const comparisons = Object.fromEntries(BENCHMARK_MODES.map((mode) => [
624
+ mode,
625
+ mode === "direct" ? null : comparisonForMode(directRuns, byMode[mode]),
626
+ ]));
627
+ const lightObjectives = scenario.expectedProfile === "light"
628
+ ? {
629
+ p50TokenOverheadPercent: LIGHT_EFFICIENCY_OBJECTIVES.p50TokenOverheadPercent,
630
+ p95TokenOverheadPercent: LIGHT_EFFICIENCY_OBJECTIVES.p95TokenOverheadPercent,
631
+ status: comparisons.forgeloopAdaptive?.claimAllowed ? "OBSERVATIONAL" : "NOT_VERIFIED",
632
+ p50Pass: comparisons.forgeloopAdaptive?.tokenOverheadPercent.p50 === null
633
+ ? null
634
+ : comparisons.forgeloopAdaptive.tokenOverheadPercent.p50 <= LIGHT_EFFICIENCY_OBJECTIVES.p50TokenOverheadPercent,
635
+ p95Pass: comparisons.forgeloopAdaptive?.tokenOverheadPercent.p95 === null
636
+ ? null
637
+ : comparisons.forgeloopAdaptive.tokenOverheadPercent.p95 <= LIGHT_EFFICIENCY_OBJECTIVES.p95TokenOverheadPercent,
638
+ }
639
+ : null;
640
+ const includeContextUsage = runs.some((run) => Object.prototype.hasOwnProperty.call(run, "contextUsage"));
641
+ const includeQuality = runs.some((run) => Object.prototype.hasOwnProperty.call(run, "quality"));
642
+ return {
643
+ schemaVersion: 1,
644
+ benchmarkVersion: BENCHMARK_VERSION,
645
+ runSetId: runs[0].runSetId,
646
+ scenarioId: scenario.scenarioId,
647
+ expectedProfile: scenario.expectedProfile,
648
+ modeAggregates: Object.fromEntries(BENCHMARK_MODES.map((mode) => [mode, modeAggregate(byMode[mode], { includeContextUsage, includeQuality })])),
649
+ comparisons,
650
+ lightObjectives,
651
+ sourcePolicy: "PROVIDER_REPORTED_OR_HOST_REPORTED_ONLY",
652
+ claimsAllowed: Object.values(comparisons).some((comparison) => comparison?.claimAllowed === true),
653
+ generatedFromRunCount: runs.length,
654
+ ...(includeContextUsage
655
+ ? { contextInflation: contextInflationForScenario(byMode.forgeloopBalanced, byMode.forgeloopAdaptive) }
656
+ : {}),
657
+ };
658
+ }
659
+
660
+ export function benchmarkScenarioFingerprint(scenario) {
661
+ assertBenchmarkScenario(scenario);
662
+ return canonicalFingerprint({
663
+ benchmarkVersion: scenario.benchmarkVersion,
664
+ scenarioId: scenario.scenarioId,
665
+ description: scenario.description,
666
+ input: scenario.input,
667
+ expectedProfile: scenario.expectedProfile,
668
+ });
669
+ }
670
+
671
+ export function benchmarkProfileForMode(mode) {
672
+ if (!PROFILE_BY_MODE[mode]) throw benchmarkError(`Unsupported benchmark mode: ${mode}`);
673
+ return { ...PROFILE_BY_MODE[mode] };
674
+ }