@ikon85/agent-workflow-kit 0.32.1 → 0.34.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/.agents/skills/ask-matt/SKILL.md +4 -3
- package/.agents/skills/audit-skills/SKILL.md +6 -0
- package/.agents/skills/board-to-waves/SKILL.md +6 -2
- package/.agents/skills/code-review/SKILL.md +6 -0
- package/.agents/skills/codebase-design/DESIGN-IT-TWICE.md +11 -1
- package/.agents/skills/codex-adapter-sync/SKILL.md +23 -19
- package/.agents/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +6 -0
- package/.agents/skills/improve-codebase-architecture/SKILL.md +10 -1
- package/.agents/skills/kit-update/SKILL.md +13 -0
- package/.agents/skills/orchestrate-wave/SKILL.md +54 -54
- package/.agents/skills/orchestrate-wave/references/builder-contract.md +16 -3
- package/.agents/skills/orchestrate-wave/references/dispatch-subagents.md +91 -0
- package/.agents/skills/orchestrate-wave/references/dispatch-workflow.md +66 -0
- package/.agents/skills/orchestrate-wave/references/report-contracts.md +42 -0
- package/.agents/skills/research/SKILL.md +6 -0
- package/.agents/skills/scale-check/SKILL.md +9 -7
- package/.agents/skills/setup-workflow/SKILL.md +47 -1
- package/.agents/skills/setup-workflow/board-sync.md +7 -2
- package/.agents/skills/setup-workflow/workflow-overview.md +1 -2
- package/.agents/skills/to-issues/SKILL.md +66 -8
- package/.agents/skills/to-waves/SKILL.md +24 -12
- package/.claude/skills/ask-matt/SKILL.md +4 -3
- package/.claude/skills/audit-skills/SKILL.md +6 -0
- package/.claude/skills/board-to-waves/SKILL.md +6 -2
- package/.claude/skills/code-review/SKILL.md +6 -0
- package/.claude/skills/codebase-design/DESIGN-IT-TWICE.md +8 -0
- package/.claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +6 -0
- package/.claude/skills/improve-codebase-architecture/SKILL.md +6 -0
- package/.claude/skills/kit-update/SKILL.md +13 -0
- package/.claude/skills/orchestrate-wave/SKILL.md +54 -54
- package/.claude/skills/orchestrate-wave/references/builder-contract.md +16 -3
- package/.claude/skills/orchestrate-wave/references/dispatch-subagents.md +91 -0
- package/.claude/skills/orchestrate-wave/references/dispatch-workflow.md +66 -0
- package/.claude/skills/orchestrate-wave/references/report-contracts.md +42 -0
- package/.claude/skills/research/SKILL.md +6 -0
- package/.claude/skills/scale-check/SKILL.md +9 -7
- package/.claude/skills/setup-workflow/SKILL.md +47 -1
- package/.claude/skills/setup-workflow/board-sync.md +7 -2
- package/.claude/skills/setup-workflow/workflow-overview.md +1 -2
- package/.claude/skills/to-issues/SKILL.md +66 -8
- package/.claude/skills/to-waves/SKILL.md +24 -12
- package/README.md +126 -11
- package/agent-workflow-kit.package.json +291 -39
- package/docs/adr/0002-capability-gated-orchestration.md +70 -0
- package/docs/adr/0005-to-issues-is-the-planning-facade.md +42 -0
- package/docs/adr/0006-routing-knowledge-access-and-policy-are-separate.md +91 -0
- package/docs/agents/skills/local-ci.md +89 -0
- package/docs/agents/wave-anchor-template.md +2 -2
- package/docs/methodology.html +1 -1
- package/docs/methodology.svg +1 -1
- package/docs/workflow.html +2 -2
- package/docs/workflow.png +0 -0
- package/package.json +1 -1
- package/scripts/codex-exec.sh +47 -8
- package/scripts/codex-exec.test.mjs +56 -0
- package/scripts/test_codex_adapter_sync_contract.py +30 -15
- package/scripts/test_orchestrate_wave_contract.py +209 -0
- package/scripts/test_program_planning_contract.py +70 -0
- package/scripts/test_skill_portability_lint.py +54 -0
- package/scripts/test_worktree_setup_base_guard.py +140 -0
- package/scripts/worktree-lifecycle/setup.py +40 -0
- package/src/cli.mjs +109 -2
- package/src/commands/init.mjs +17 -1
- package/src/commands/routing-policy-update.mjs +204 -0
- package/src/commands/update.mjs +22 -0
- package/src/lib/agentSurfaceRegistry.mjs +60 -0
- package/src/lib/bundle.mjs +34 -0
- package/src/lib/capabilityMatrix.mjs +179 -0
- package/src/lib/dispatchReceipt.mjs +162 -0
- package/src/lib/frontendWorkloads.mjs +170 -0
- package/src/lib/reconcileReconReports.mjs +111 -0
- package/src/lib/reportValidator.mjs +186 -0
- package/src/lib/routeDispatcher.mjs +158 -0
- package/src/lib/routingAccessGraph.mjs +105 -0
- package/src/lib/routingAdapters/claude.mjs +62 -0
- package/src/lib/routingAdapters/codex.mjs +136 -0
- package/src/lib/routingCatalog.mjs +123 -0
- package/src/lib/routingEvidenceCache.mjs +222 -0
- package/src/lib/routingIntent.mjs +93 -0
- package/src/lib/routingPolicy.mjs +67 -0
- package/src/lib/routingProfile.mjs +334 -0
- package/src/lib/routingResolver.mjs +176 -0
- package/src/lib/routingSources/artificialAnalysis.mjs +129 -0
- package/src/lib/routingSources/benchlm.mjs +151 -0
- package/src/lib/routingSources/codeArena.mjs +162 -0
- package/src/lib/routingSources/deepswe.mjs +106 -0
- package/src/lib/routingSources/openhands.mjs +102 -0
- package/src/lib/routingSources/openhandsFrontend.mjs +135 -0
- package/src/lib/waveClaim.mjs +134 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const SOURCE_ID = 'benchlm';
|
|
2
|
+
const ARTIFACT_URL = 'https://benchlm.ai/data';
|
|
3
|
+
const OWNER_URLS = Object.freeze({
|
|
4
|
+
'code-arena-webdev': 'https://arena.ai/leaderboard/code/webdev',
|
|
5
|
+
design2code: 'https://github.com/NoviScl/Design2Code',
|
|
6
|
+
'swe-bench-multimodal': 'https://www.swebench.com/multimodal',
|
|
7
|
+
vision2web: 'https://vision2web-bench.github.io/',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
function object(value, field) {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
12
|
+
throw new TypeError(`${field} must be an object`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function string(value, field) {
|
|
18
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
19
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function timestamp(value, field) {
|
|
25
|
+
string(value, field);
|
|
26
|
+
if (!Number.isFinite(Date.parse(value))) throw new TypeError(`${field} must be an ISO timestamp`);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function deepFreeze(value) {
|
|
31
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
32
|
+
Object.freeze(value);
|
|
33
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateUnique(items, identity, label) {
|
|
38
|
+
const identities = new Set();
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
const id = identity(item);
|
|
41
|
+
if (identities.has(id)) throw new TypeError(`duplicate ${label} identity: ${id}`);
|
|
42
|
+
identities.add(id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
|
|
47
|
+
object(payload, 'BenchLM payload');
|
|
48
|
+
string(snapshotHash, 'snapshotHash');
|
|
49
|
+
timestamp(observedAt, 'observedAt');
|
|
50
|
+
timestamp(expiresAt, 'expiresAt');
|
|
51
|
+
if (Date.parse(expiresAt) <= Date.parse(observedAt)) {
|
|
52
|
+
throw new TypeError('expiresAt must follow observedAt');
|
|
53
|
+
}
|
|
54
|
+
if (payload.schemaVersion !== 1) throw new TypeError('BenchLM schemaVersion must be 1');
|
|
55
|
+
const generatedAt = timestamp(payload.generatedAt, 'BenchLM generatedAt');
|
|
56
|
+
const sourceLastUpdated = timestamp(
|
|
57
|
+
payload.sourceLastUpdated,
|
|
58
|
+
'BenchLM sourceLastUpdated',
|
|
59
|
+
);
|
|
60
|
+
if (Date.parse(sourceLastUpdated) > Date.parse(generatedAt)) {
|
|
61
|
+
throw new TypeError('BenchLM sourceLastUpdated cannot follow generatedAt');
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(payload.models)) throw new TypeError('BenchLM models must be an array');
|
|
64
|
+
if (!Array.isArray(payload.benchmarks)) {
|
|
65
|
+
throw new TypeError('BenchLM benchmarks must be an array');
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(payload.updates)) throw new TypeError('BenchLM updates must be an array');
|
|
68
|
+
|
|
69
|
+
validateUnique(
|
|
70
|
+
payload.models,
|
|
71
|
+
(model) => `${string(model.providerId, 'BenchLM model providerId')}:`
|
|
72
|
+
+ string(model.modelId, 'BenchLM model modelId'),
|
|
73
|
+
'model',
|
|
74
|
+
);
|
|
75
|
+
validateUnique(
|
|
76
|
+
payload.benchmarks,
|
|
77
|
+
(benchmark) => string(benchmark.id, 'BenchLM benchmark id'),
|
|
78
|
+
'benchmark',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
for (const benchmark of payload.benchmarks) {
|
|
82
|
+
object(benchmark, 'BenchLM benchmark');
|
|
83
|
+
const id = string(benchmark.id, 'BenchLM benchmark id');
|
|
84
|
+
const expectedUrl = OWNER_URLS[id];
|
|
85
|
+
if (expectedUrl && benchmark.ownerUrl !== expectedUrl) {
|
|
86
|
+
throw new TypeError(`BenchLM owner URL for ${id} must be ${expectedUrl}`);
|
|
87
|
+
}
|
|
88
|
+
string(benchmark.ownerUrl, `BenchLM benchmark ${id} ownerUrl`);
|
|
89
|
+
string(benchmark.status, `BenchLM benchmark ${id} status`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const models = payload.models.map((model) => ({
|
|
93
|
+
providerId: string(model.providerId, 'BenchLM model providerId'),
|
|
94
|
+
modelId: string(model.modelId, 'BenchLM model modelId'),
|
|
95
|
+
}));
|
|
96
|
+
const signals = [];
|
|
97
|
+
for (const model of payload.models) {
|
|
98
|
+
object(model, 'BenchLM model');
|
|
99
|
+
string(model.family, 'BenchLM model family');
|
|
100
|
+
string(model.releasedAt, 'BenchLM model releasedAt');
|
|
101
|
+
object(model.pricing, 'BenchLM model pricing');
|
|
102
|
+
object(model.aggregates, 'BenchLM model aggregates');
|
|
103
|
+
signals.push({
|
|
104
|
+
kind: 'pricing-changed',
|
|
105
|
+
identity: `${model.providerId}:${model.modelId}`,
|
|
106
|
+
pricing: structuredClone(model.pricing),
|
|
107
|
+
observedAt: sourceLastUpdated,
|
|
108
|
+
});
|
|
109
|
+
if (Object.keys(model.aggregates).length > 0) {
|
|
110
|
+
signals.push({
|
|
111
|
+
kind: 'evidence-gap',
|
|
112
|
+
identity: `${model.providerId}:${model.modelId}`,
|
|
113
|
+
reason: 'aggregate-requires-owner-observation',
|
|
114
|
+
ownerCandidates: payload.benchmarks.map(({ id, ownerUrl }) => ({ id, ownerUrl })),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const update of payload.updates) {
|
|
119
|
+
object(update, 'BenchLM update');
|
|
120
|
+
signals.push({
|
|
121
|
+
kind: string(update.kind, 'BenchLM update kind'),
|
|
122
|
+
identity: string(update.identity, 'BenchLM update identity'),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (payload.models.length > 0 || payload.benchmarks.length > 0 || payload.updates.length > 0) {
|
|
126
|
+
signals.push({
|
|
127
|
+
kind: 'freshness',
|
|
128
|
+
generatedAt,
|
|
129
|
+
sourceLastUpdated,
|
|
130
|
+
snapshotHash,
|
|
131
|
+
});
|
|
132
|
+
signals.push({
|
|
133
|
+
kind: 'corroboration-candidate',
|
|
134
|
+
benchmarkIds: payload.benchmarks.map(({ id }) => id),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return deepFreeze({
|
|
139
|
+
sourceId: SOURCE_ID,
|
|
140
|
+
models,
|
|
141
|
+
observations: [],
|
|
142
|
+
signals,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const benchLmSource = Object.freeze({
|
|
147
|
+
sourceId: SOURCE_ID,
|
|
148
|
+
owner: 'BenchLM',
|
|
149
|
+
artifactUrl: ARTIFACT_URL,
|
|
150
|
+
ingest,
|
|
151
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { frontendEvidenceWorkload } from '../frontendWorkloads.mjs';
|
|
2
|
+
|
|
3
|
+
const OWNER_URL = 'https://arena.ai/leaderboard/code/webdev';
|
|
4
|
+
const SOURCE_ID = 'code-arena-webdev';
|
|
5
|
+
|
|
6
|
+
function object(value, field) {
|
|
7
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
8
|
+
throw new TypeError(`${field} must be an object`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function string(value, field) {
|
|
14
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
15
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function finite(value, field) {
|
|
21
|
+
if (!Number.isFinite(value)) throw new TypeError(`${field} must be a finite number`);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function timestamp(value, field) {
|
|
26
|
+
string(value, field);
|
|
27
|
+
if (!Number.isFinite(Date.parse(value))) throw new TypeError(`${field} must be an ISO timestamp`);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function deepFreeze(value) {
|
|
32
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
33
|
+
Object.freeze(value);
|
|
34
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validateContext({ snapshotHash, observedAt, expiresAt }) {
|
|
39
|
+
string(snapshotHash, 'snapshotHash');
|
|
40
|
+
timestamp(observedAt, 'observedAt');
|
|
41
|
+
timestamp(expiresAt, 'expiresAt');
|
|
42
|
+
if (Date.parse(expiresAt) <= Date.parse(observedAt)) {
|
|
43
|
+
throw new TypeError('expiresAt must follow observedAt');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasOverlappingIntervals(observations) {
|
|
48
|
+
return observations.some((left, index) =>
|
|
49
|
+
observations.slice(index + 1).some((right) =>
|
|
50
|
+
left.score - left.uncertainty.value <= right.score + right.uncertainty.value
|
|
51
|
+
&& right.score - right.uncertainty.value <= left.score + left.uncertainty.value));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
|
|
55
|
+
object(payload, 'Code Arena payload');
|
|
56
|
+
validateContext({ snapshotHash, observedAt, expiresAt });
|
|
57
|
+
if (payload.schemaVersion !== 1) throw new TypeError('Code Arena schemaVersion must be 1');
|
|
58
|
+
string(payload.exportId, 'Code Arena exportId');
|
|
59
|
+
object(payload.benchmark, 'Code Arena benchmark');
|
|
60
|
+
if (payload.benchmark.id !== SOURCE_ID) {
|
|
61
|
+
throw new TypeError(`Code Arena benchmark identity must be ${SOURCE_ID}`);
|
|
62
|
+
}
|
|
63
|
+
if (payload.benchmark.url !== OWNER_URL) {
|
|
64
|
+
throw new TypeError(`Code Arena owner URL must be ${OWNER_URL}`);
|
|
65
|
+
}
|
|
66
|
+
string(payload.benchmark.owner, 'Code Arena benchmark owner');
|
|
67
|
+
string(payload.benchmark.version, 'Code Arena benchmark version');
|
|
68
|
+
object(payload.harness, 'Code Arena harness');
|
|
69
|
+
string(payload.harness.id, 'Code Arena harness id');
|
|
70
|
+
string(payload.harness.version, 'Code Arena harness version');
|
|
71
|
+
if (!Array.isArray(payload.rows) || payload.rows.length === 0) {
|
|
72
|
+
throw new TypeError('Code Arena rows must be a non-empty array');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const models = [];
|
|
76
|
+
const observations = [];
|
|
77
|
+
const modelIds = new Set();
|
|
78
|
+
const observationIds = new Set();
|
|
79
|
+
for (const [index, row] of payload.rows.entries()) {
|
|
80
|
+
object(row, `Code Arena rows[${index}]`);
|
|
81
|
+
const providerId = string(row.providerId, `Code Arena rows[${index}].providerId`);
|
|
82
|
+
const modelId = string(row.modelId, `Code Arena rows[${index}].modelId`);
|
|
83
|
+
const effort = string(row.effort, `Code Arena rows[${index}].effort`);
|
|
84
|
+
const domain = string(row.domain, `Code Arena rows[${index}].domain`);
|
|
85
|
+
const score = finite(row.score, `Code Arena rows[${index}].score`);
|
|
86
|
+
const interval = finite(
|
|
87
|
+
row.confidenceInterval,
|
|
88
|
+
`Code Arena rows[${index}].confidenceInterval`,
|
|
89
|
+
);
|
|
90
|
+
if (interval < 0) throw new TypeError('Code Arena confidenceInterval must be non-negative');
|
|
91
|
+
if (!['preliminary', 'established'].includes(row.status)) {
|
|
92
|
+
throw new TypeError(`Code Arena rows[${index}].status is unsupported`);
|
|
93
|
+
}
|
|
94
|
+
const sampleSize = finite(row.sampleSize, `Code Arena rows[${index}].sampleSize`);
|
|
95
|
+
const modelIdentity = `${providerId}:${modelId}`;
|
|
96
|
+
if (!modelIds.has(modelIdentity)) {
|
|
97
|
+
models.push({ providerId, modelId });
|
|
98
|
+
modelIds.add(modelIdentity);
|
|
99
|
+
}
|
|
100
|
+
const id = `${SOURCE_ID}:${payload.benchmark.version}:${domain}:${modelIdentity}:${effort}`;
|
|
101
|
+
if (observationIds.has(id)) {
|
|
102
|
+
throw new TypeError(`duplicate observation identity: ${id}`);
|
|
103
|
+
}
|
|
104
|
+
observationIds.add(id);
|
|
105
|
+
object(row.cost, `Code Arena rows[${index}].cost`);
|
|
106
|
+
observations.push({
|
|
107
|
+
id,
|
|
108
|
+
providerId,
|
|
109
|
+
modelId,
|
|
110
|
+
effort,
|
|
111
|
+
workload: frontendEvidenceWorkload({
|
|
112
|
+
workload: 'frontend-greenfield',
|
|
113
|
+
frontendDomain: domain,
|
|
114
|
+
axis: 'visual-preference',
|
|
115
|
+
}),
|
|
116
|
+
harness: {
|
|
117
|
+
id: payload.harness.id,
|
|
118
|
+
version: payload.harness.version,
|
|
119
|
+
},
|
|
120
|
+
score,
|
|
121
|
+
source: {
|
|
122
|
+
id: SOURCE_ID,
|
|
123
|
+
owner: payload.benchmark.owner,
|
|
124
|
+
url: payload.benchmark.url,
|
|
125
|
+
benchmark: SOURCE_ID,
|
|
126
|
+
version: payload.benchmark.version,
|
|
127
|
+
snapshotHash,
|
|
128
|
+
},
|
|
129
|
+
uncertainty: {
|
|
130
|
+
kind: 'confidence-interval',
|
|
131
|
+
value: interval,
|
|
132
|
+
status: row.status,
|
|
133
|
+
sampleSize,
|
|
134
|
+
},
|
|
135
|
+
freshness: { observedAt, expiresAt },
|
|
136
|
+
cost: {
|
|
137
|
+
amount: finite(row.cost.amount, `Code Arena rows[${index}].cost.amount`),
|
|
138
|
+
currency: string(row.cost.currency, `Code Arena rows[${index}].cost.currency`),
|
|
139
|
+
unit: string(row.cost.unit, `Code Arena rows[${index}].cost.unit`),
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return deepFreeze({
|
|
145
|
+
sourceId: SOURCE_ID,
|
|
146
|
+
models,
|
|
147
|
+
observations,
|
|
148
|
+
diagnostics: {
|
|
149
|
+
overlappingUncertainty: hasOverlappingIntervals(observations),
|
|
150
|
+
preliminaryObservationIds: observations
|
|
151
|
+
.filter(({ uncertainty }) => uncertainty.status === 'preliminary')
|
|
152
|
+
.map(({ id }) => id),
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const codeArenaSource = Object.freeze({
|
|
158
|
+
sourceId: SOURCE_ID,
|
|
159
|
+
owner: 'Arena',
|
|
160
|
+
artifactUrl: OWNER_URL,
|
|
161
|
+
ingest,
|
|
162
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const SOURCE_ID = 'deepswe';
|
|
2
|
+
const OWNER = 'DataCurve';
|
|
3
|
+
const ARTIFACT_URL =
|
|
4
|
+
'https://deepswe.datacurve.ai/artifacts/v1.1/leaderboard-live.json';
|
|
5
|
+
|
|
6
|
+
function object(value, field) {
|
|
7
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
8
|
+
throw new TypeError(`${field} must be an object`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function string(value, field) {
|
|
14
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
15
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function number(value, field) {
|
|
21
|
+
if (!Number.isFinite(value)) throw new TypeError(`${field} must be a finite number`);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function context({ snapshotHash, observedAt, expiresAt }) {
|
|
26
|
+
string(snapshotHash, 'snapshotHash');
|
|
27
|
+
if (!Number.isFinite(Date.parse(observedAt))) {
|
|
28
|
+
throw new TypeError('observedAt must be an ISO timestamp');
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isFinite(Date.parse(expiresAt)) || Date.parse(expiresAt) <= Date.parse(observedAt)) {
|
|
31
|
+
throw new TypeError('expiresAt must be an ISO timestamp after observedAt');
|
|
32
|
+
}
|
|
33
|
+
return { snapshotHash, observedAt, expiresAt };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ingest({ payload, ...inputContext }) {
|
|
37
|
+
const { snapshotHash, observedAt, expiresAt } = context(inputContext);
|
|
38
|
+
object(payload, 'DeepSWE payload');
|
|
39
|
+
const artifact = object(payload.artifact, 'DeepSWE artifact');
|
|
40
|
+
if (artifact.owner !== OWNER || artifact.url !== ARTIFACT_URL) {
|
|
41
|
+
throw new TypeError('DeepSWE source identity does not match the owner artifact');
|
|
42
|
+
}
|
|
43
|
+
const benchmark = string(artifact.benchmark, 'DeepSWE artifact benchmark');
|
|
44
|
+
const benchmarkVersion = string(artifact.version, 'DeepSWE artifact version');
|
|
45
|
+
const harness = object(artifact.harness, 'DeepSWE artifact harness');
|
|
46
|
+
const harnessId = string(harness.id, 'DeepSWE artifact harness id');
|
|
47
|
+
const harnessVersion = string(harness.version, 'DeepSWE artifact harness version');
|
|
48
|
+
if (!Array.isArray(payload.rows)) throw new TypeError('DeepSWE rows must be an array');
|
|
49
|
+
|
|
50
|
+
const models = new Map();
|
|
51
|
+
const observations = [];
|
|
52
|
+
const observationIds = new Set();
|
|
53
|
+
payload.rows.forEach((row, index) => {
|
|
54
|
+
object(row, `DeepSWE rows[${index}]`);
|
|
55
|
+
const providerId = string(row.provider, `DeepSWE rows[${index}].provider`);
|
|
56
|
+
const modelId = string(row.model, `DeepSWE rows[${index}].model`);
|
|
57
|
+
const effort = string(row.effort, `DeepSWE rows[${index}].effort`);
|
|
58
|
+
const id = `${SOURCE_ID}:${benchmarkVersion}:${providerId}:${modelId}:${effort}`;
|
|
59
|
+
if (observationIds.has(id)) throw new TypeError(`duplicate DeepSWE observation: ${id}`);
|
|
60
|
+
observationIds.add(id);
|
|
61
|
+
models.set(`${providerId}:${modelId}`, { providerId, modelId });
|
|
62
|
+
observations.push({
|
|
63
|
+
id,
|
|
64
|
+
providerId,
|
|
65
|
+
modelId,
|
|
66
|
+
effort,
|
|
67
|
+
workload: 'development',
|
|
68
|
+
harness: { id: harnessId, version: harnessVersion },
|
|
69
|
+
score: number(row.score, `DeepSWE rows[${index}].score`),
|
|
70
|
+
source: {
|
|
71
|
+
id: SOURCE_ID,
|
|
72
|
+
owner: OWNER,
|
|
73
|
+
url: ARTIFACT_URL,
|
|
74
|
+
benchmark,
|
|
75
|
+
version: benchmarkVersion,
|
|
76
|
+
snapshotHash,
|
|
77
|
+
},
|
|
78
|
+
uncertainty: {
|
|
79
|
+
kind: 'confidence-interval-95-half-width',
|
|
80
|
+
value: number(
|
|
81
|
+
row.confidence95HalfWidth,
|
|
82
|
+
`DeepSWE rows[${index}].confidence95HalfWidth`,
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
freshness: { observedAt, expiresAt },
|
|
86
|
+
cost: {
|
|
87
|
+
amount: number(row.averageCostUsd, `DeepSWE rows[${index}].averageCostUsd`),
|
|
88
|
+
currency: 'USD',
|
|
89
|
+
unit: 'task',
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
sourceId: SOURCE_ID,
|
|
96
|
+
models: [...models.values()],
|
|
97
|
+
observations,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const deepSweSource = Object.freeze({
|
|
102
|
+
sourceId: SOURCE_ID,
|
|
103
|
+
owner: OWNER,
|
|
104
|
+
artifactUrl: ARTIFACT_URL,
|
|
105
|
+
ingest,
|
|
106
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const SOURCE_ID = 'openhands-evaluation';
|
|
2
|
+
const OWNER = 'All Hands AI';
|
|
3
|
+
const ARTIFACT_URL = 'https://github.com/All-Hands-AI/OpenHands/tree/main/evaluation';
|
|
4
|
+
|
|
5
|
+
function object(value, field) {
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
7
|
+
throw new TypeError(`${field} must be an object`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function string(value, field) {
|
|
13
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
14
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function number(value, field) {
|
|
20
|
+
if (!Number.isFinite(value)) throw new TypeError(`${field} must be a finite number`);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
|
|
25
|
+
string(snapshotHash, 'snapshotHash');
|
|
26
|
+
if (!Number.isFinite(Date.parse(observedAt))) {
|
|
27
|
+
throw new TypeError('observedAt must be an ISO timestamp');
|
|
28
|
+
}
|
|
29
|
+
if (!Number.isFinite(Date.parse(expiresAt)) || Date.parse(expiresAt) <= Date.parse(observedAt)) {
|
|
30
|
+
throw new TypeError('expiresAt must be an ISO timestamp after observedAt');
|
|
31
|
+
}
|
|
32
|
+
object(payload, 'OpenHands payload');
|
|
33
|
+
const provenance = object(payload.provenance, 'OpenHands provenance');
|
|
34
|
+
if (provenance.organization !== OWNER || provenance.artifactUrl !== ARTIFACT_URL) {
|
|
35
|
+
throw new TypeError('OpenHands source identity does not match the owner artifact');
|
|
36
|
+
}
|
|
37
|
+
const benchmark = string(provenance.benchmarkName, 'OpenHands benchmark name');
|
|
38
|
+
const benchmarkVersion = string(
|
|
39
|
+
provenance.benchmarkVersion,
|
|
40
|
+
'OpenHands benchmark version',
|
|
41
|
+
);
|
|
42
|
+
const harnessId = string(provenance.harnessId, 'OpenHands harness id');
|
|
43
|
+
const harnessVersion = string(provenance.harnessVersion, 'OpenHands harness version');
|
|
44
|
+
if (!Array.isArray(payload.results)) throw new TypeError('OpenHands results must be an array');
|
|
45
|
+
|
|
46
|
+
const models = new Map();
|
|
47
|
+
const observations = [];
|
|
48
|
+
const observationIds = new Set();
|
|
49
|
+
payload.results.forEach((row, index) => {
|
|
50
|
+
object(row, `OpenHands results[${index}]`);
|
|
51
|
+
const providerId = string(row.provider, `OpenHands results[${index}].provider`);
|
|
52
|
+
const modelId = string(row.model, `OpenHands results[${index}].model`);
|
|
53
|
+
const effort = string(row.effort, `OpenHands results[${index}].effort`);
|
|
54
|
+
const id = `${SOURCE_ID}:${benchmarkVersion}:${providerId}:${modelId}:${effort}`;
|
|
55
|
+
if (observationIds.has(id)) throw new TypeError(`duplicate OpenHands observation: ${id}`);
|
|
56
|
+
observationIds.add(id);
|
|
57
|
+
models.set(`${providerId}:${modelId}`, { providerId, modelId });
|
|
58
|
+
const meanCost = object(row.meanCost, `OpenHands results[${index}].meanCost`);
|
|
59
|
+
observations.push({
|
|
60
|
+
id,
|
|
61
|
+
providerId,
|
|
62
|
+
modelId,
|
|
63
|
+
effort,
|
|
64
|
+
workload: 'development',
|
|
65
|
+
harness: { id: harnessId, version: harnessVersion },
|
|
66
|
+
score: number(row.resolvedRate, `OpenHands results[${index}].resolvedRate`),
|
|
67
|
+
source: {
|
|
68
|
+
id: SOURCE_ID,
|
|
69
|
+
owner: OWNER,
|
|
70
|
+
url: ARTIFACT_URL,
|
|
71
|
+
benchmark,
|
|
72
|
+
version: benchmarkVersion,
|
|
73
|
+
snapshotHash,
|
|
74
|
+
},
|
|
75
|
+
uncertainty: {
|
|
76
|
+
kind: 'standard-error',
|
|
77
|
+
value: number(row.standardError, `OpenHands results[${index}].standardError`),
|
|
78
|
+
},
|
|
79
|
+
freshness: { observedAt, expiresAt },
|
|
80
|
+
cost: {
|
|
81
|
+
amount: number(meanCost.amount, `OpenHands results[${index}].meanCost.amount`),
|
|
82
|
+
currency: string(
|
|
83
|
+
meanCost.currency,
|
|
84
|
+
`OpenHands results[${index}].meanCost.currency`,
|
|
85
|
+
),
|
|
86
|
+
unit: string(meanCost.unit, `OpenHands results[${index}].meanCost.unit`),
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
sourceId: SOURCE_ID,
|
|
92
|
+
models: [...models.values()],
|
|
93
|
+
observations,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const openHandsSource = Object.freeze({
|
|
98
|
+
sourceId: SOURCE_ID,
|
|
99
|
+
owner: OWNER,
|
|
100
|
+
artifactUrl: ARTIFACT_URL,
|
|
101
|
+
ingest,
|
|
102
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { frontendEvidenceWorkload } from '../frontendWorkloads.mjs';
|
|
2
|
+
|
|
3
|
+
const OWNER_URL = 'https://www.openhands.dev/blog/openhands-index';
|
|
4
|
+
const SOURCE_ID = 'openhands-frontend';
|
|
5
|
+
const BENCHMARK_ID = 'swe-bench-multimodal';
|
|
6
|
+
|
|
7
|
+
function object(value, field) {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
9
|
+
throw new TypeError(`${field} must be an object`);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function string(value, field) {
|
|
15
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
16
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function finite(value, field) {
|
|
22
|
+
if (!Number.isFinite(value)) throw new TypeError(`${field} must be a finite number`);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function timestamp(value, field) {
|
|
27
|
+
string(value, field);
|
|
28
|
+
if (!Number.isFinite(Date.parse(value))) throw new TypeError(`${field} must be an ISO timestamp`);
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function deepFreeze(value) {
|
|
33
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
34
|
+
Object.freeze(value);
|
|
35
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
|
|
40
|
+
object(payload, 'OpenHands frontend payload');
|
|
41
|
+
string(snapshotHash, 'snapshotHash');
|
|
42
|
+
timestamp(observedAt, 'observedAt');
|
|
43
|
+
timestamp(expiresAt, 'expiresAt');
|
|
44
|
+
if (Date.parse(expiresAt) <= Date.parse(observedAt)) {
|
|
45
|
+
throw new TypeError('expiresAt must follow observedAt');
|
|
46
|
+
}
|
|
47
|
+
if (payload.schemaVersion !== 1) throw new TypeError('OpenHands schemaVersion must be 1');
|
|
48
|
+
string(payload.exportId, 'OpenHands exportId');
|
|
49
|
+
object(payload.benchmark, 'OpenHands benchmark');
|
|
50
|
+
if (payload.benchmark.id !== BENCHMARK_ID) {
|
|
51
|
+
throw new TypeError(`OpenHands benchmark identity must be ${BENCHMARK_ID}`);
|
|
52
|
+
}
|
|
53
|
+
if (payload.benchmark.url !== OWNER_URL) {
|
|
54
|
+
throw new TypeError(`OpenHands owner URL must be ${OWNER_URL}`);
|
|
55
|
+
}
|
|
56
|
+
string(payload.benchmark.owner, 'OpenHands benchmark owner');
|
|
57
|
+
string(payload.benchmark.version, 'OpenHands benchmark version');
|
|
58
|
+
object(payload.harness, 'OpenHands harness');
|
|
59
|
+
string(payload.harness.id, 'OpenHands harness id');
|
|
60
|
+
string(payload.harness.version, 'OpenHands harness version');
|
|
61
|
+
if (!Array.isArray(payload.rows) || payload.rows.length === 0) {
|
|
62
|
+
throw new TypeError('OpenHands rows must be a non-empty array');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const models = [];
|
|
66
|
+
const observations = [];
|
|
67
|
+
const modelIds = new Set();
|
|
68
|
+
const observationIds = new Set();
|
|
69
|
+
for (const [index, row] of payload.rows.entries()) {
|
|
70
|
+
object(row, `OpenHands rows[${index}]`);
|
|
71
|
+
const providerId = string(row.providerId, `OpenHands rows[${index}].providerId`);
|
|
72
|
+
const modelId = string(row.modelId, `OpenHands rows[${index}].modelId`);
|
|
73
|
+
const effort = string(row.effort, `OpenHands rows[${index}].effort`);
|
|
74
|
+
const modelIdentity = `${providerId}:${modelId}`;
|
|
75
|
+
if (!modelIds.has(modelIdentity)) {
|
|
76
|
+
models.push({ providerId, modelId });
|
|
77
|
+
modelIds.add(modelIdentity);
|
|
78
|
+
}
|
|
79
|
+
const id = `${SOURCE_ID}:${payload.benchmark.version}:${modelIdentity}:${effort}`;
|
|
80
|
+
if (observationIds.has(id)) {
|
|
81
|
+
throw new TypeError(`duplicate observation identity: ${id}`);
|
|
82
|
+
}
|
|
83
|
+
observationIds.add(id);
|
|
84
|
+
if (!['preliminary', 'established'].includes(row.status)) {
|
|
85
|
+
throw new TypeError(`OpenHands rows[${index}].status is unsupported`);
|
|
86
|
+
}
|
|
87
|
+
object(row.cost, `OpenHands rows[${index}].cost`);
|
|
88
|
+
observations.push({
|
|
89
|
+
id,
|
|
90
|
+
providerId,
|
|
91
|
+
modelId,
|
|
92
|
+
effort,
|
|
93
|
+
workload: frontendEvidenceWorkload({
|
|
94
|
+
workload: 'frontend-repository-repair',
|
|
95
|
+
axis: 'functional',
|
|
96
|
+
}),
|
|
97
|
+
harness: {
|
|
98
|
+
id: payload.harness.id,
|
|
99
|
+
version: payload.harness.version,
|
|
100
|
+
},
|
|
101
|
+
score: finite(row.score, `OpenHands rows[${index}].score`),
|
|
102
|
+
source: {
|
|
103
|
+
id: SOURCE_ID,
|
|
104
|
+
owner: payload.benchmark.owner,
|
|
105
|
+
url: payload.benchmark.url,
|
|
106
|
+
benchmark: BENCHMARK_ID,
|
|
107
|
+
version: payload.benchmark.version,
|
|
108
|
+
snapshotHash,
|
|
109
|
+
},
|
|
110
|
+
uncertainty: {
|
|
111
|
+
kind: 'confidence-interval',
|
|
112
|
+
value: finite(
|
|
113
|
+
row.confidenceInterval,
|
|
114
|
+
`OpenHands rows[${index}].confidenceInterval`,
|
|
115
|
+
),
|
|
116
|
+
status: row.status,
|
|
117
|
+
sampleSize: finite(row.sampleSize, `OpenHands rows[${index}].sampleSize`),
|
|
118
|
+
},
|
|
119
|
+
freshness: { observedAt, expiresAt },
|
|
120
|
+
cost: {
|
|
121
|
+
amount: finite(row.cost.amount, `OpenHands rows[${index}].cost.amount`),
|
|
122
|
+
currency: string(row.cost.currency, `OpenHands rows[${index}].cost.currency`),
|
|
123
|
+
unit: string(row.cost.unit, `OpenHands rows[${index}].cost.unit`),
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return deepFreeze({ sourceId: SOURCE_ID, models, observations });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const openHandsFrontendSource = Object.freeze({
|
|
131
|
+
sourceId: SOURCE_ID,
|
|
132
|
+
owner: 'OpenHands',
|
|
133
|
+
artifactUrl: OWNER_URL,
|
|
134
|
+
ingest,
|
|
135
|
+
});
|