@chllming/wave-orchestration 0.6.2 → 0.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 (116) hide show
  1. package/CHANGELOG.md +64 -1
  2. package/README.md +44 -8
  3. package/docs/agents/wave-orchestrator-role.md +50 -0
  4. package/docs/agents/wave-planner-role.md +39 -0
  5. package/docs/context7/bundles.json +9 -0
  6. package/docs/context7/planner-agent/README.md +25 -0
  7. package/docs/context7/planner-agent/manifest.json +83 -0
  8. package/docs/context7/planner-agent/papers/cooperbench-why-coding-agents-cannot-be-your-teammates-yet.md +3283 -0
  9. package/docs/context7/planner-agent/papers/dova-deliberation-first-multi-agent-orchestration-for-autonomous-research-automation.md +1699 -0
  10. package/docs/context7/planner-agent/papers/dpbench-large-language-models-struggle-with-simultaneous-coordination.md +2251 -0
  11. package/docs/context7/planner-agent/papers/incremental-planning-to-control-a-blackboard-based-problem-solver.md +1729 -0
  12. package/docs/context7/planner-agent/papers/silo-bench-a-scalable-environment-for-evaluating-distributed-coordination-in-multi-agent-llm-systems.md +3747 -0
  13. package/docs/context7/planner-agent/papers/todoevolve-learning-to-architect-agent-planning-systems.md +1675 -0
  14. package/docs/context7/planner-agent/papers/verified-multi-agent-orchestration-a-plan-execute-verify-replan-framework-for-complex-query-resolution.md +1173 -0
  15. package/docs/context7/planner-agent/papers/why-do-multi-agent-llm-systems-fail.md +5211 -0
  16. package/docs/context7/planner-agent/topics/planning-and-orchestration.md +24 -0
  17. package/docs/evals/README.md +96 -1
  18. package/docs/evals/arm-templates/README.md +13 -0
  19. package/docs/evals/arm-templates/full-wave.json +15 -0
  20. package/docs/evals/arm-templates/single-agent.json +15 -0
  21. package/docs/evals/benchmark-catalog.json +7 -0
  22. package/docs/evals/cases/README.md +47 -0
  23. package/docs/evals/cases/wave-blackboard-inbox-targeting.json +73 -0
  24. package/docs/evals/cases/wave-contradiction-conflict.json +104 -0
  25. package/docs/evals/cases/wave-expert-routing-preservation.json +69 -0
  26. package/docs/evals/cases/wave-hidden-profile-private-evidence.json +81 -0
  27. package/docs/evals/cases/wave-premature-closure-guard.json +71 -0
  28. package/docs/evals/cases/wave-silo-cross-agent-state.json +77 -0
  29. package/docs/evals/cases/wave-simultaneous-lockstep.json +92 -0
  30. package/docs/evals/cooperbench/real-world-mitigation.md +341 -0
  31. package/docs/evals/external-benchmarks.json +85 -0
  32. package/docs/evals/external-command-config.sample.json +9 -0
  33. package/docs/evals/external-command-config.swe-bench-pro.json +8 -0
  34. package/docs/evals/pilots/README.md +47 -0
  35. package/docs/evals/pilots/swe-bench-pro-public-full-wave-review-10.json +64 -0
  36. package/docs/evals/pilots/swe-bench-pro-public-pilot.json +111 -0
  37. package/docs/evals/wave-benchmark-program.md +302 -0
  38. package/docs/guides/planner.md +48 -11
  39. package/docs/plans/context7-wave-orchestrator.md +20 -0
  40. package/docs/plans/current-state.md +9 -1
  41. package/docs/plans/examples/wave-benchmark-improvement.md +108 -0
  42. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  43. package/docs/plans/examples/wave-example-rollout-fidelity.md +340 -0
  44. package/docs/plans/wave-orchestrator.md +73 -11
  45. package/docs/plans/waves/reviews/wave-1-benchmark-operator.md +118 -0
  46. package/docs/reference/coordination-and-closure.md +436 -0
  47. package/docs/reference/live-proof-waves.md +25 -3
  48. package/docs/reference/npmjs-trusted-publishing.md +3 -3
  49. package/docs/reference/proof-metrics.md +90 -0
  50. package/docs/reference/runtime-config/README.md +61 -0
  51. package/docs/reference/sample-waves.md +29 -18
  52. package/docs/reference/wave-control.md +164 -0
  53. package/docs/reference/wave-planning-lessons.md +131 -0
  54. package/package.json +5 -4
  55. package/releases/manifest.json +33 -0
  56. package/scripts/research/agent-context-archive.mjs +18 -0
  57. package/scripts/research/manifests/agent-context-expanded-2026-03-22.mjs +17 -0
  58. package/scripts/research/sync-planner-context7-bundle.mjs +133 -0
  59. package/scripts/wave-autonomous.mjs +2 -4
  60. package/scripts/wave-orchestrator/adhoc.mjs +32 -11
  61. package/scripts/wave-orchestrator/artifact-schemas.mjs +232 -0
  62. package/scripts/wave-orchestrator/autonomous.mjs +27 -6
  63. package/scripts/wave-orchestrator/benchmark-cases.mjs +374 -0
  64. package/scripts/wave-orchestrator/benchmark-external.mjs +1384 -0
  65. package/scripts/wave-orchestrator/benchmark.mjs +972 -0
  66. package/scripts/wave-orchestrator/clarification-triage.mjs +78 -12
  67. package/scripts/wave-orchestrator/config.mjs +175 -0
  68. package/scripts/wave-orchestrator/control-cli.mjs +1123 -0
  69. package/scripts/wave-orchestrator/control-plane.mjs +697 -0
  70. package/scripts/wave-orchestrator/coord-cli.mjs +360 -2
  71. package/scripts/wave-orchestrator/coordination-store.mjs +211 -9
  72. package/scripts/wave-orchestrator/coordination.mjs +84 -0
  73. package/scripts/wave-orchestrator/dashboard-renderer.mjs +38 -3
  74. package/scripts/wave-orchestrator/dashboard-state.mjs +22 -0
  75. package/scripts/wave-orchestrator/evals.mjs +23 -0
  76. package/scripts/wave-orchestrator/executors.mjs +3 -2
  77. package/scripts/wave-orchestrator/feedback.mjs +55 -0
  78. package/scripts/wave-orchestrator/install.mjs +253 -26
  79. package/scripts/wave-orchestrator/launcher-closure.mjs +4 -1
  80. package/scripts/wave-orchestrator/launcher-runtime.mjs +24 -21
  81. package/scripts/wave-orchestrator/launcher.mjs +800 -35
  82. package/scripts/wave-orchestrator/package-update-notice.mjs +230 -0
  83. package/scripts/wave-orchestrator/package-version.mjs +32 -0
  84. package/scripts/wave-orchestrator/planner-context.mjs +75 -0
  85. package/scripts/wave-orchestrator/planner.mjs +2270 -136
  86. package/scripts/wave-orchestrator/proof-cli.mjs +195 -0
  87. package/scripts/wave-orchestrator/proof-registry.mjs +317 -0
  88. package/scripts/wave-orchestrator/replay.mjs +10 -4
  89. package/scripts/wave-orchestrator/retry-cli.mjs +184 -0
  90. package/scripts/wave-orchestrator/retry-control.mjs +225 -0
  91. package/scripts/wave-orchestrator/shared.mjs +26 -0
  92. package/scripts/wave-orchestrator/swe-bench-pro-task.mjs +1004 -0
  93. package/scripts/wave-orchestrator/traces.mjs +157 -2
  94. package/scripts/wave-orchestrator/wave-control-client.mjs +532 -0
  95. package/scripts/wave-orchestrator/wave-control-schema.mjs +309 -0
  96. package/scripts/wave-orchestrator/wave-files.mjs +17 -5
  97. package/scripts/wave.mjs +39 -2
  98. package/skills/repo-coding-rules/SKILL.md +1 -0
  99. package/skills/role-cont-eval/SKILL.md +1 -0
  100. package/skills/role-cont-qa/SKILL.md +13 -6
  101. package/skills/role-deploy/SKILL.md +1 -0
  102. package/skills/role-documentation/SKILL.md +4 -0
  103. package/skills/role-implementation/SKILL.md +4 -0
  104. package/skills/role-infra/SKILL.md +2 -1
  105. package/skills/role-integration/SKILL.md +15 -8
  106. package/skills/role-planner/SKILL.md +39 -0
  107. package/skills/role-planner/skill.json +21 -0
  108. package/skills/role-research/SKILL.md +1 -0
  109. package/skills/role-security/SKILL.md +2 -2
  110. package/skills/runtime-claude/SKILL.md +2 -1
  111. package/skills/runtime-codex/SKILL.md +1 -0
  112. package/skills/runtime-local/SKILL.md +2 -0
  113. package/skills/runtime-opencode/SKILL.md +1 -0
  114. package/skills/wave-core/SKILL.md +25 -6
  115. package/skills/wave-core/references/marker-syntax.md +16 -8
  116. package/wave.config.json +45 -0
@@ -0,0 +1,374 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ materializeCoordinationState,
5
+ normalizeCoordinationRecord,
6
+ } from "./coordination-store.mjs";
7
+ import { loadBenchmarkCatalog } from "./evals.mjs";
8
+ import { REPO_ROOT, readJsonOrNull } from "./shared.mjs";
9
+
10
+ const DEFAULT_BENCHMARK_CASES_DIR = "docs/evals/cases";
11
+ const DEFAULT_EXTERNAL_BENCHMARKS_PATH = "docs/evals/external-benchmarks.json";
12
+ const SUPPORTED_CASE_KINDS = new Set(["projection"]);
13
+ const SUPPORTED_CASE_ARMS = new Set([
14
+ "single-agent",
15
+ "multi-agent-minimal",
16
+ "full-wave",
17
+ "full-wave-plus-improvement",
18
+ ]);
19
+
20
+ function cleanText(value) {
21
+ return String(value ?? "").trim();
22
+ }
23
+
24
+ function normalizeRepoRelativePath(value, label) {
25
+ const normalized = cleanText(value)
26
+ .replaceAll("\\", "/")
27
+ .replace(/^\.\/+/, "")
28
+ .replace(/\/+/g, "/")
29
+ .replace(/\/$/, "");
30
+ if (!normalized) {
31
+ throw new Error(`${label} is required`);
32
+ }
33
+ if (normalized.startsWith("/") || normalized.startsWith("../") || normalized.includes("/../")) {
34
+ throw new Error(`${label} must stay within the repository`);
35
+ }
36
+ return normalized;
37
+ }
38
+
39
+ function normalizeId(value, label) {
40
+ const normalized = cleanText(value).toLowerCase();
41
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
42
+ throw new Error(`${label} must match /^[a-z0-9][a-z0-9._-]*$/`);
43
+ }
44
+ return normalized;
45
+ }
46
+
47
+ function normalizeStringArray(value, label) {
48
+ if (value == null) {
49
+ return [];
50
+ }
51
+ if (!Array.isArray(value)) {
52
+ throw new Error(`${label} must be an array`);
53
+ }
54
+ return value.map((entry, index) => cleanText(entry)).map((entry, index) => {
55
+ if (!entry) {
56
+ throw new Error(`${label}[${index}] must be a non-empty string`);
57
+ }
58
+ return entry;
59
+ });
60
+ }
61
+
62
+ function normalizeIdArray(value, label, { allowEmpty = true } = {}) {
63
+ const ids = normalizeStringArray(value, label).map((entry, index) =>
64
+ normalizeId(entry, `${label}[${index}]`),
65
+ );
66
+ if (!allowEmpty && ids.length === 0) {
67
+ throw new Error(`${label} must not be empty`);
68
+ }
69
+ return Array.from(new Set(ids));
70
+ }
71
+
72
+ function normalizeAgent(rawAgent, index) {
73
+ if (!rawAgent || typeof rawAgent !== "object" || Array.isArray(rawAgent)) {
74
+ throw new Error(`fixture.agents[${index}] must be an object`);
75
+ }
76
+ const agentId = normalizeId(rawAgent.agentId, `fixture.agents[${index}].agentId`);
77
+ return {
78
+ agentId,
79
+ title: cleanText(rawAgent.title) || agentId,
80
+ ownedPaths: normalizeStringArray(rawAgent.ownedPaths, `fixture.agents[${index}].ownedPaths`),
81
+ capabilities: normalizeIdArray(
82
+ rawAgent.capabilities,
83
+ `fixture.agents[${index}].capabilities`,
84
+ ),
85
+ };
86
+ }
87
+
88
+ function normalizeAssignments(value, label) {
89
+ if (value == null) {
90
+ return [];
91
+ }
92
+ if (!Array.isArray(value)) {
93
+ throw new Error(`${label} must be an array`);
94
+ }
95
+ return value.map((entry, index) => {
96
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
97
+ throw new Error(`${label}[${index}] must be an object`);
98
+ }
99
+ return {
100
+ requestId: normalizeId(entry.requestId, `${label}[${index}].requestId`),
101
+ assignedAgentId: normalizeId(entry.assignedAgentId, `${label}[${index}].assignedAgentId`),
102
+ };
103
+ });
104
+ }
105
+
106
+ function normalizeTargetedInboxes(value, label) {
107
+ if (value == null) {
108
+ return {};
109
+ }
110
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
111
+ throw new Error(`${label} must be an object`);
112
+ }
113
+ return Object.fromEntries(
114
+ Object.entries(value).map(([agentId, facts]) => [
115
+ normalizeId(agentId, `${label}.${agentId}`),
116
+ normalizeStringArray(facts, `${label}.${agentId}`),
117
+ ]),
118
+ );
119
+ }
120
+
121
+ function normalizeExpectations(rawExpectations = {}) {
122
+ if (!rawExpectations || typeof rawExpectations !== "object" || Array.isArray(rawExpectations)) {
123
+ throw new Error("expectations must be an object");
124
+ }
125
+ return {
126
+ globalFacts: normalizeStringArray(rawExpectations.globalFacts, "expectations.globalFacts"),
127
+ summaryFacts: normalizeStringArray(rawExpectations.summaryFacts, "expectations.summaryFacts"),
128
+ targetedInboxes: normalizeTargetedInboxes(
129
+ rawExpectations.targetedInboxes,
130
+ "expectations.targetedInboxes",
131
+ ),
132
+ requiredAssignments: normalizeAssignments(
133
+ rawExpectations.requiredAssignments,
134
+ "expectations.requiredAssignments",
135
+ ),
136
+ clarificationRequestIds: normalizeIdArray(
137
+ rawExpectations.clarificationRequestIds,
138
+ "expectations.clarificationRequestIds",
139
+ ),
140
+ minimumDistinctAssignedAgents:
141
+ rawExpectations.minimumDistinctAssignedAgents == null
142
+ ? null
143
+ : Number.parseInt(String(rawExpectations.minimumDistinctAssignedAgents), 10),
144
+ requireBlockingGuard:
145
+ rawExpectations.requireBlockingGuard === undefined
146
+ ? false
147
+ : Boolean(rawExpectations.requireBlockingGuard),
148
+ };
149
+ }
150
+
151
+ function normalizeThresholds(rawThresholds = {}) {
152
+ if (!rawThresholds || typeof rawThresholds !== "object" || Array.isArray(rawThresholds)) {
153
+ throw new Error("scoring.thresholds must be an object");
154
+ }
155
+ const normalized = {};
156
+ for (const [key, value] of Object.entries(rawThresholds)) {
157
+ const parsed = Number(value);
158
+ if (!Number.isFinite(parsed)) {
159
+ throw new Error(`scoring.thresholds.${key} must be numeric`);
160
+ }
161
+ normalized[key] = parsed;
162
+ }
163
+ return normalized;
164
+ }
165
+
166
+ function normalizeScoring(rawScoring = {}) {
167
+ if (!rawScoring || typeof rawScoring !== "object" || Array.isArray(rawScoring)) {
168
+ throw new Error("scoring must be an object");
169
+ }
170
+ const kind = normalizeId(rawScoring.kind, "scoring.kind");
171
+ return {
172
+ kind,
173
+ primaryMetric: normalizeId(rawScoring.primaryMetric, "scoring.primaryMetric"),
174
+ thresholds: normalizeThresholds(rawScoring.thresholds || {}),
175
+ practicalWinThreshold:
176
+ rawScoring.practicalWinThreshold == null
177
+ ? 5
178
+ : Number.parseFloat(String(rawScoring.practicalWinThreshold)),
179
+ };
180
+ }
181
+
182
+ function normalizeCapabilityRouting(value) {
183
+ if (value == null) {
184
+ return { preferredAgents: {} };
185
+ }
186
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
187
+ throw new Error("fixture.capabilityRouting must be an object");
188
+ }
189
+ const preferredAgents =
190
+ value.preferredAgents && typeof value.preferredAgents === "object" && !Array.isArray(value.preferredAgents)
191
+ ? value.preferredAgents
192
+ : {};
193
+ return {
194
+ preferredAgents: Object.fromEntries(
195
+ Object.entries(preferredAgents).map(([capability, agents]) => [
196
+ normalizeId(capability, `fixture.capabilityRouting.preferredAgents.${capability}`),
197
+ normalizeIdArray(
198
+ agents,
199
+ `fixture.capabilityRouting.preferredAgents.${capability}`,
200
+ ),
201
+ ]),
202
+ ),
203
+ };
204
+ }
205
+
206
+ function normalizeBenchmarkCase(rawCase, filePath, catalog) {
207
+ if (!rawCase || typeof rawCase !== "object" || Array.isArray(rawCase)) {
208
+ throw new Error(`Benchmark case must be an object: ${filePath}`);
209
+ }
210
+ const id = normalizeId(rawCase.id, `${filePath}: id`);
211
+ const familyId = normalizeId(rawCase.familyId, `${filePath}: familyId`);
212
+ const benchmarkId = normalizeId(rawCase.benchmarkId, `${filePath}: benchmarkId`);
213
+ const family = catalog.families[familyId];
214
+ if (!family) {
215
+ throw new Error(`${filePath}: unknown benchmark family "${familyId}"`);
216
+ }
217
+ const benchmark = family.benchmarks[benchmarkId];
218
+ if (!benchmark) {
219
+ throw new Error(`${filePath}: unknown benchmark "${benchmarkId}" for family "${familyId}"`);
220
+ }
221
+ const catalogLocalCases = Array.from(
222
+ new Set([...(family.localCases || []), ...(benchmark.localCases || [])]),
223
+ );
224
+ if (catalogLocalCases.length > 0 && !catalogLocalCases.includes(id)) {
225
+ throw new Error(
226
+ `${filePath}: case "${id}" is not registered in ${catalog.path} for ${familyId}/${benchmarkId}`,
227
+ );
228
+ }
229
+ const kind = normalizeId(rawCase.kind || "projection", `${filePath}: kind`);
230
+ if (!SUPPORTED_CASE_KINDS.has(kind)) {
231
+ throw new Error(`${filePath}: unsupported case kind "${kind}"`);
232
+ }
233
+ const fixture = rawCase.fixture;
234
+ if (!fixture || typeof fixture !== "object" || Array.isArray(fixture)) {
235
+ throw new Error(`${filePath}: fixture must be an object`);
236
+ }
237
+ const agents = Array.isArray(fixture.agents)
238
+ ? fixture.agents.map((agent, index) => normalizeAgent(agent, index))
239
+ : [];
240
+ if (agents.length === 0) {
241
+ throw new Error(`${filePath}: fixture.agents must define at least one agent`);
242
+ }
243
+ const primaryAgentId = normalizeId(
244
+ fixture.primaryAgentId || agents[0].agentId,
245
+ `${filePath}: fixture.primaryAgentId`,
246
+ );
247
+ if (!agents.some((agent) => agent.agentId === primaryAgentId)) {
248
+ throw new Error(`${filePath}: fixture.primaryAgentId must match one of fixture.agents`);
249
+ }
250
+ const supportedArms = normalizeIdArray(
251
+ rawCase.supportedArms || ["single-agent", "multi-agent-minimal", "full-wave"],
252
+ `${filePath}: supportedArms`,
253
+ { allowEmpty: false },
254
+ );
255
+ for (const arm of supportedArms) {
256
+ if (!SUPPORTED_CASE_ARMS.has(arm)) {
257
+ throw new Error(`${filePath}: unsupported benchmark arm "${arm}"`);
258
+ }
259
+ }
260
+ const records = Array.isArray(fixture.records)
261
+ ? fixture.records.map((record, index) =>
262
+ normalizeCoordinationRecord(record, {
263
+ lane: cleanText(fixture.lane) || "main",
264
+ wave:
265
+ fixture.waveNumber == null ? 0 : Number.parseInt(String(fixture.waveNumber), 10),
266
+ source: "benchmark-case",
267
+ }),
268
+ )
269
+ : [];
270
+ if (records.length === 0) {
271
+ throw new Error(`${filePath}: fixture.records must define at least one coordination record`);
272
+ }
273
+ return {
274
+ id,
275
+ version: Number.parseInt(String(rawCase.version ?? "1"), 10) || 1,
276
+ path: path.relative(REPO_ROOT, filePath).replaceAll(path.sep, "/"),
277
+ title: cleanText(rawCase.title) || id,
278
+ summary: cleanText(rawCase.summary) || null,
279
+ tags: normalizeIdArray(rawCase.tags, `${filePath}: tags`),
280
+ kind,
281
+ familyId,
282
+ benchmarkId,
283
+ familyTitle: family.title,
284
+ benchmarkTitle: benchmark.title,
285
+ supportedArms,
286
+ scoring: normalizeScoring(rawCase.scoring),
287
+ expectations: normalizeExpectations(rawCase.expectations),
288
+ fixture: {
289
+ lane: cleanText(fixture.lane) || "main",
290
+ waveNumber:
291
+ fixture.waveNumber == null ? 0 : Number.parseInt(String(fixture.waveNumber), 10),
292
+ primaryAgentId,
293
+ capabilityRouting: normalizeCapabilityRouting(fixture.capabilityRouting),
294
+ agents,
295
+ records,
296
+ state: materializeCoordinationState(records),
297
+ },
298
+ };
299
+ }
300
+
301
+ function readJsonFile(filePath) {
302
+ const payload = readJsonOrNull(filePath);
303
+ if (!payload) {
304
+ throw new Error(`Invalid JSON file: ${path.relative(REPO_ROOT, filePath)}`);
305
+ }
306
+ return payload;
307
+ }
308
+
309
+ export function loadBenchmarkCases(options = {}) {
310
+ const casesDir = path.resolve(
311
+ REPO_ROOT,
312
+ normalizeRepoRelativePath(options.casesDir || DEFAULT_BENCHMARK_CASES_DIR, "casesDir"),
313
+ );
314
+ if (!fs.existsSync(casesDir)) {
315
+ throw new Error(`Benchmark cases directory does not exist: ${path.relative(REPO_ROOT, casesDir)}`);
316
+ }
317
+ const catalog = loadBenchmarkCatalog(options);
318
+ const files = fs
319
+ .readdirSync(casesDir, { withFileTypes: true })
320
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
321
+ .map((entry) => path.join(casesDir, entry.name))
322
+ .toSorted();
323
+ const seen = new Set();
324
+ const cases = files.map((filePath) => {
325
+ const benchmarkCase = normalizeBenchmarkCase(readJsonFile(filePath), filePath, catalog);
326
+ if (seen.has(benchmarkCase.id)) {
327
+ throw new Error(`Duplicate benchmark case id "${benchmarkCase.id}"`);
328
+ }
329
+ seen.add(benchmarkCase.id);
330
+ return benchmarkCase;
331
+ });
332
+ return {
333
+ casesDir: path.relative(REPO_ROOT, casesDir).replaceAll(path.sep, "/"),
334
+ absoluteCasesDir: casesDir,
335
+ catalog,
336
+ cases,
337
+ byId: new Map(cases.map((benchmarkCase) => [benchmarkCase.id, benchmarkCase])),
338
+ };
339
+ }
340
+
341
+ export function loadExternalBenchmarkAdapters(options = {}) {
342
+ const registryPath = path.resolve(
343
+ REPO_ROOT,
344
+ normalizeRepoRelativePath(
345
+ options.externalBenchmarksPath || DEFAULT_EXTERNAL_BENCHMARKS_PATH,
346
+ "externalBenchmarksPath",
347
+ ),
348
+ );
349
+ const payload = readJsonFile(registryPath);
350
+ const adapters = Array.isArray(payload.adapters) ? payload.adapters : [];
351
+ return {
352
+ path: path.relative(REPO_ROOT, registryPath).replaceAll(path.sep, "/"),
353
+ version: Number.parseInt(String(payload.version ?? "1"), 10) || 1,
354
+ adapters: adapters.map((adapter, index) => {
355
+ if (!adapter || typeof adapter !== "object" || Array.isArray(adapter)) {
356
+ throw new Error(`adapters[${index}] in ${registryPath} must be an object`);
357
+ }
358
+ return {
359
+ id: normalizeId(adapter.id, `adapters[${index}].id`),
360
+ title: cleanText(adapter.title) || normalizeId(adapter.id, `adapters[${index}].id`),
361
+ mode: cleanText(adapter.mode) || "adapted",
362
+ sourceBenchmark: cleanText(adapter.sourceBenchmark) || null,
363
+ split: cleanText(adapter.split) || null,
364
+ pilotManifestPath: cleanText(adapter.pilotManifestPath) || null,
365
+ officialDocsUrl: cleanText(adapter.officialDocsUrl) || null,
366
+ officialCodeUrl: cleanText(adapter.officialCodeUrl) || null,
367
+ summary: cleanText(adapter.summary) || null,
368
+ commandTemplate: cleanText(adapter.commandTemplate) || null,
369
+ metrics: normalizeStringArray(adapter.metrics, `adapters[${index}].metrics`),
370
+ notes: normalizeStringArray(adapter.notes, `adapters[${index}].notes`),
371
+ };
372
+ }),
373
+ };
374
+ }