@chllming/wave-orchestration 0.5.1

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 (68) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +549 -0
  3. package/docs/agents/wave-deploy-verifier-role.md +34 -0
  4. package/docs/agents/wave-documentation-role.md +30 -0
  5. package/docs/agents/wave-evaluator-role.md +43 -0
  6. package/docs/agents/wave-infra-role.md +34 -0
  7. package/docs/agents/wave-integration-role.md +32 -0
  8. package/docs/agents/wave-launcher-role.md +37 -0
  9. package/docs/context7/bundles.json +91 -0
  10. package/docs/plans/component-cutover-matrix.json +112 -0
  11. package/docs/plans/component-cutover-matrix.md +49 -0
  12. package/docs/plans/context7-wave-orchestrator.md +130 -0
  13. package/docs/plans/current-state.md +44 -0
  14. package/docs/plans/master-plan.md +16 -0
  15. package/docs/plans/migration.md +23 -0
  16. package/docs/plans/wave-orchestrator.md +254 -0
  17. package/docs/plans/waves/wave-0.md +165 -0
  18. package/docs/reference/github-packages-setup.md +52 -0
  19. package/docs/reference/migration-0.2-to-0.5.md +622 -0
  20. package/docs/reference/npmjs-trusted-publishing.md +55 -0
  21. package/docs/reference/repository-guidance.md +18 -0
  22. package/docs/reference/runtime-config/README.md +85 -0
  23. package/docs/reference/runtime-config/claude.md +105 -0
  24. package/docs/reference/runtime-config/codex.md +81 -0
  25. package/docs/reference/runtime-config/opencode.md +93 -0
  26. package/docs/research/agent-context-sources.md +57 -0
  27. package/docs/roadmap.md +626 -0
  28. package/package.json +53 -0
  29. package/releases/manifest.json +101 -0
  30. package/scripts/context7-api-check.sh +21 -0
  31. package/scripts/context7-export-env.sh +52 -0
  32. package/scripts/research/agent-context-archive.mjs +472 -0
  33. package/scripts/research/generate-agent-context-indexes.mjs +85 -0
  34. package/scripts/research/import-agent-context-archive.mjs +793 -0
  35. package/scripts/research/manifests/harness-and-blackboard-2026-03-21.mjs +201 -0
  36. package/scripts/wave-autonomous.mjs +13 -0
  37. package/scripts/wave-cli-bootstrap.mjs +27 -0
  38. package/scripts/wave-dashboard.mjs +11 -0
  39. package/scripts/wave-human-feedback.mjs +11 -0
  40. package/scripts/wave-launcher.mjs +11 -0
  41. package/scripts/wave-local-executor.mjs +13 -0
  42. package/scripts/wave-orchestrator/agent-state.mjs +416 -0
  43. package/scripts/wave-orchestrator/autonomous.mjs +367 -0
  44. package/scripts/wave-orchestrator/clarification-triage.mjs +605 -0
  45. package/scripts/wave-orchestrator/config.mjs +848 -0
  46. package/scripts/wave-orchestrator/context7.mjs +464 -0
  47. package/scripts/wave-orchestrator/coord-cli.mjs +286 -0
  48. package/scripts/wave-orchestrator/coordination-store.mjs +987 -0
  49. package/scripts/wave-orchestrator/coordination.mjs +768 -0
  50. package/scripts/wave-orchestrator/dashboard-renderer.mjs +254 -0
  51. package/scripts/wave-orchestrator/dashboard-state.mjs +473 -0
  52. package/scripts/wave-orchestrator/dep-cli.mjs +219 -0
  53. package/scripts/wave-orchestrator/docs-queue.mjs +75 -0
  54. package/scripts/wave-orchestrator/executors.mjs +385 -0
  55. package/scripts/wave-orchestrator/feedback.mjs +372 -0
  56. package/scripts/wave-orchestrator/install.mjs +540 -0
  57. package/scripts/wave-orchestrator/launcher.mjs +3879 -0
  58. package/scripts/wave-orchestrator/ledger.mjs +332 -0
  59. package/scripts/wave-orchestrator/local-executor.mjs +263 -0
  60. package/scripts/wave-orchestrator/replay.mjs +246 -0
  61. package/scripts/wave-orchestrator/roots.mjs +10 -0
  62. package/scripts/wave-orchestrator/routing-state.mjs +542 -0
  63. package/scripts/wave-orchestrator/shared.mjs +405 -0
  64. package/scripts/wave-orchestrator/terminals.mjs +209 -0
  65. package/scripts/wave-orchestrator/traces.mjs +1094 -0
  66. package/scripts/wave-orchestrator/wave-files.mjs +1923 -0
  67. package/scripts/wave.mjs +103 -0
  68. package/wave.config.json +115 -0
@@ -0,0 +1,1094 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { buildAgentExecutionSummary, validateImplementationSummary } from "./agent-state.mjs";
4
+ import { openClarificationLinkedRequests, readCoordinationLog, serializeCoordinationState } from "./coordination-store.mjs";
5
+ import {
6
+ REPO_ROOT,
7
+ ensureDirectory,
8
+ hashText,
9
+ readJsonOrNull,
10
+ readStatusRecordIfPresent,
11
+ toIsoTimestamp,
12
+ writeJsonAtomic,
13
+ writeTextAtomic,
14
+ } from "./shared.mjs";
15
+
16
+ export const TRACE_VERSION = 2;
17
+ const LEGACY_TRACE_VERSION = 1;
18
+
19
+ export function traceWaveDir(tracesDir, waveNumber) {
20
+ return path.join(tracesDir, `wave-${waveNumber}`);
21
+ }
22
+
23
+ export function traceAttemptDir(tracesDir, waveNumber, attemptNumber) {
24
+ return path.join(traceWaveDir(tracesDir, waveNumber), `attempt-${attemptNumber}`);
25
+ }
26
+
27
+ function toTracePath(value) {
28
+ return String(value || "").replaceAll(path.sep, "/");
29
+ }
30
+
31
+ function relativePathOrNull(filePath, rootDir) {
32
+ if (!filePath) {
33
+ return null;
34
+ }
35
+ return toTracePath(path.relative(rootDir, filePath));
36
+ }
37
+
38
+ function fileHashOrNull(filePath) {
39
+ if (!filePath || !fs.existsSync(filePath)) {
40
+ return null;
41
+ }
42
+ return hashText(fs.readFileSync(filePath, "utf8"));
43
+ }
44
+
45
+ function copyFileIfExists(sourcePath, destPath) {
46
+ if (!sourcePath || !fs.existsSync(sourcePath)) {
47
+ return false;
48
+ }
49
+ ensureDirectory(path.dirname(destPath));
50
+ fs.copyFileSync(sourcePath, destPath);
51
+ return true;
52
+ }
53
+
54
+ function writeCoordinationLogSnapshot(sourcePath, destPath, coordinationState) {
55
+ ensureDirectory(path.dirname(destPath));
56
+ if (sourcePath && fs.existsSync(sourcePath)) {
57
+ fs.copyFileSync(sourcePath, destPath);
58
+ return true;
59
+ }
60
+ const records = Array.isArray(coordinationState?.records) ? coordinationState.records : [];
61
+ const text =
62
+ records.length > 0
63
+ ? `${records.map((record) => JSON.stringify(record)).join("\n")}\n`
64
+ : "";
65
+ fs.writeFileSync(destPath, text, "utf8");
66
+ return records.length > 0;
67
+ }
68
+
69
+ function readAttemptMetadataIfPresent(dirPath) {
70
+ const payload = readJsonOrNull(path.join(dirPath, "run-metadata.json"));
71
+ return payload && typeof payload === "object" ? payload : null;
72
+ }
73
+
74
+ function readPriorAttemptMetadata(tracesDir, waveNumber, currentAttempt) {
75
+ const waveDir = traceWaveDir(tracesDir, waveNumber);
76
+ if (!fs.existsSync(waveDir)) {
77
+ return [];
78
+ }
79
+ return fs
80
+ .readdirSync(waveDir, { withFileTypes: true })
81
+ .filter((entry) => entry.isDirectory() && /^attempt-\d+$/.test(entry.name))
82
+ .map((entry) => ({
83
+ dirPath: path.join(waveDir, entry.name),
84
+ attempt: Number.parseInt(entry.name.replace("attempt-", ""), 10),
85
+ }))
86
+ .filter((entry) => Number.isFinite(entry.attempt) && entry.attempt < currentAttempt)
87
+ .sort((a, b) => a.attempt - b.attempt)
88
+ .map((entry) => readAttemptMetadataIfPresent(entry.dirPath))
89
+ .filter(Boolean);
90
+ }
91
+
92
+ function dedupeByKey(items, keyFn) {
93
+ const byKey = new Map();
94
+ for (const item of items || []) {
95
+ byKey.set(keyFn(item), item);
96
+ }
97
+ return Array.from(byKey.values());
98
+ }
99
+
100
+ function collectLaunchEventsFromMetadata(metadata) {
101
+ return (metadata?.agents || [])
102
+ .filter((agent) => agent?.launchedInAttempt === true)
103
+ .map((agent) => ({
104
+ attempt: metadata.attempt,
105
+ agentId: agent.agentId,
106
+ role: agent.executor?.role || null,
107
+ executorId: agent.executor?.executorId || agent.executor?.id || null,
108
+ }));
109
+ }
110
+
111
+ function collectEvaluatorStatusesFromMetadata(metadata) {
112
+ const statusCode = metadata?.gateSnapshot?.evaluatorGate?.statusCode || null;
113
+ if (!statusCode) {
114
+ return [];
115
+ }
116
+ return [
117
+ {
118
+ attempt: Number(metadata.attempt),
119
+ statusCode,
120
+ },
121
+ ];
122
+ }
123
+
124
+ function collectLaunchEventsFromCurrent(agentRuns, attempt) {
125
+ return (agentRuns || [])
126
+ .filter((run) => Number(run?.lastLaunchAttempt) === attempt)
127
+ .map((run) => ({
128
+ attempt,
129
+ agentId: run.agent.agentId,
130
+ role: run.agent.executorResolved?.role || null,
131
+ executorId: run.agent.executorResolved?.id || null,
132
+ }));
133
+ }
134
+
135
+ function collectEvaluatorStatusesFromCurrent(gateSnapshot, attempt) {
136
+ const statusCode = gateSnapshot?.evaluatorGate?.statusCode || null;
137
+ if (!statusCode) {
138
+ return [];
139
+ }
140
+ return [{ attempt, statusCode }];
141
+ }
142
+
143
+ function emptyHistorySnapshot() {
144
+ return {
145
+ launchEvents: [],
146
+ evaluatorStatuses: [],
147
+ };
148
+ }
149
+
150
+ function normalizeHistorySnapshot(snapshot) {
151
+ if (!snapshot || typeof snapshot !== "object") {
152
+ return emptyHistorySnapshot();
153
+ }
154
+ return {
155
+ launchEvents: dedupeByKey(
156
+ Array.isArray(snapshot.launchEvents)
157
+ ? snapshot.launchEvents
158
+ .filter(Boolean)
159
+ .map((event) => ({
160
+ attempt: Number.parseInt(String(event.attempt), 10),
161
+ agentId: String(event.agentId || "").trim(),
162
+ role: String(event.role || "").trim() || null,
163
+ executorId: String(event.executorId || "").trim() || null,
164
+ }))
165
+ .filter((event) => Number.isFinite(event.attempt) && event.agentId)
166
+ : [],
167
+ (event) => `${event.attempt}:${event.agentId}:${event.executorId || ""}`,
168
+ ).sort((a, b) => a.attempt - b.attempt || a.agentId.localeCompare(b.agentId)),
169
+ evaluatorStatuses: dedupeByKey(
170
+ Array.isArray(snapshot.evaluatorStatuses)
171
+ ? snapshot.evaluatorStatuses
172
+ .filter(Boolean)
173
+ .map((entry) => ({
174
+ attempt: Number.parseInt(String(entry.attempt), 10),
175
+ statusCode: String(entry.statusCode || "").trim() || null,
176
+ }))
177
+ .filter((entry) => Number.isFinite(entry.attempt) && entry.statusCode)
178
+ : [],
179
+ (entry) => `${entry.attempt}`,
180
+ ).sort((a, b) => a.attempt - b.attempt),
181
+ };
182
+ }
183
+
184
+ function latestHistoricalSnapshot(priorMetadata) {
185
+ const latest = Array.isArray(priorMetadata) && priorMetadata.length > 0 ? priorMetadata.at(-1) : null;
186
+ if (latest?.traceVersion >= TRACE_VERSION && latest?.historySnapshot) {
187
+ return normalizeHistorySnapshot(latest.historySnapshot);
188
+ }
189
+ return null;
190
+ }
191
+
192
+ function buildHistorySnapshotFromPriorMetadata(priorMetadata) {
193
+ const latest = latestHistoricalSnapshot(priorMetadata);
194
+ if (latest) {
195
+ return latest;
196
+ }
197
+ return normalizeHistorySnapshot({
198
+ launchEvents: (priorMetadata || []).flatMap((metadata) => collectLaunchEventsFromMetadata(metadata)),
199
+ evaluatorStatuses: (priorMetadata || []).flatMap((metadata) =>
200
+ collectEvaluatorStatusesFromMetadata(metadata),
201
+ ),
202
+ });
203
+ }
204
+
205
+ function mergeHistorySnapshot(baseSnapshot, currentSnapshot) {
206
+ const base = normalizeHistorySnapshot(baseSnapshot);
207
+ const current = normalizeHistorySnapshot(currentSnapshot);
208
+ return normalizeHistorySnapshot({
209
+ launchEvents: [...base.launchEvents, ...current.launchEvents],
210
+ evaluatorStatuses: [...base.evaluatorStatuses, ...current.evaluatorStatuses],
211
+ });
212
+ }
213
+
214
+ function buildHistorySnapshot({
215
+ tracesDir,
216
+ waveNumber,
217
+ attempt,
218
+ agentRuns,
219
+ gateSnapshot,
220
+ }) {
221
+ const priorMetadata = tracesDir
222
+ ? readPriorAttemptMetadata(tracesDir, waveNumber, attempt)
223
+ : [];
224
+ const priorSnapshot = buildHistorySnapshotFromPriorMetadata(priorMetadata);
225
+ const currentSnapshot = {
226
+ launchEvents: collectLaunchEventsFromCurrent(agentRuns, attempt),
227
+ evaluatorStatuses: collectEvaluatorStatusesFromCurrent(gateSnapshot, attempt),
228
+ };
229
+ return mergeHistorySnapshot(priorSnapshot, currentSnapshot);
230
+ }
231
+
232
+ function buildRelaunchCounts(events) {
233
+ const launchCountsByAgent = new Map();
234
+ const byRole = {};
235
+ const byExecutor = {};
236
+ let totalLaunches = 0;
237
+ for (const event of events.sort((a, b) => a.attempt - b.attempt || a.agentId.localeCompare(b.agentId))) {
238
+ totalLaunches += 1;
239
+ const priorLaunches = launchCountsByAgent.get(event.agentId) || 0;
240
+ if (priorLaunches > 0) {
241
+ const role = event.role || "unknown";
242
+ const executorId = event.executorId || "unknown";
243
+ byRole[role] = (byRole[role] || 0) + 1;
244
+ byExecutor[executorId] = (byExecutor[executorId] || 0) + 1;
245
+ }
246
+ launchCountsByAgent.set(event.agentId, priorLaunches + 1);
247
+ }
248
+ return { byRole, byExecutor, totalLaunches };
249
+ }
250
+
251
+ function averageOrNull(values) {
252
+ if (!Array.isArray(values) || values.length === 0) {
253
+ return null;
254
+ }
255
+ return Math.round(values.reduce((sum, value) => sum + value, 0) / values.length);
256
+ }
257
+
258
+ function groupCoordinationHistory(records) {
259
+ const grouped = new Map();
260
+ for (const record of records || []) {
261
+ const list = grouped.get(record.id) || [];
262
+ list.push(record);
263
+ grouped.set(record.id, list);
264
+ }
265
+ for (const list of grouped.values()) {
266
+ list.sort((a, b) => Date.parse(a.updatedAt) - Date.parse(b.updatedAt));
267
+ }
268
+ return grouped;
269
+ }
270
+
271
+ function computeAckAndBlockerTimings(coordinationRecords) {
272
+ const grouped = groupCoordinationHistory(coordinationRecords);
273
+ const ackedStatuses = new Set(["acknowledged", "in_progress", "resolved", "closed"]);
274
+ const resolvedStatuses = new Set(["resolved", "closed", "superseded", "cancelled"]);
275
+ const ackDurations = [];
276
+ const blockerDurations = [];
277
+ for (const history of grouped.values()) {
278
+ const first = history[0];
279
+ const startMs = Date.parse(first.createdAt || first.updatedAt || "");
280
+ if (!Number.isFinite(startMs)) {
281
+ continue;
282
+ }
283
+ if (["request", "clarification-request", "human-feedback", "human-escalation"].includes(first.kind)) {
284
+ const acknowledged = history.find((record) => ackedStatuses.has(record.status));
285
+ const ackMs = Date.parse(acknowledged?.updatedAt || "");
286
+ if (Number.isFinite(ackMs) && ackMs >= startMs) {
287
+ ackDurations.push(ackMs - startMs);
288
+ }
289
+ }
290
+ if (first.kind === "blocker") {
291
+ const resolved = history.find((record) => resolvedStatuses.has(record.status));
292
+ const resolvedMs = Date.parse(resolved?.updatedAt || "");
293
+ if (Number.isFinite(resolvedMs) && resolvedMs >= startMs) {
294
+ blockerDurations.push(resolvedMs - startMs);
295
+ }
296
+ }
297
+ }
298
+ return {
299
+ meanTimeToFirstAckMs: averageOrNull(ackDurations),
300
+ meanTimeToBlockerResolutionMs: averageOrNull(blockerDurations),
301
+ };
302
+ }
303
+
304
+ function computeAssignmentAndDependencyTimings(coordinationRecords, dependencySnapshot = null) {
305
+ const grouped = groupCoordinationHistory(coordinationRecords);
306
+ const requestStartById = new Map();
307
+ for (const history of grouped.values()) {
308
+ const first = history[0];
309
+ if (first?.kind === "request") {
310
+ const startMs = Date.parse(first.createdAt || first.updatedAt || "");
311
+ if (Number.isFinite(startMs)) {
312
+ requestStartById.set(first.id, startMs);
313
+ }
314
+ }
315
+ }
316
+ const assignmentDurations = [];
317
+ for (const history of grouped.values()) {
318
+ const first = history[0];
319
+ if (first?.kind !== "decision" || !String(first.id || "").startsWith("assignment:")) {
320
+ continue;
321
+ }
322
+ const requestId = Array.isArray(first.dependsOn) ? first.dependsOn[0] : null;
323
+ const startMs = requestStartById.get(requestId);
324
+ const assignedMs = Date.parse(first.updatedAt || first.createdAt || "");
325
+ if (Number.isFinite(startMs) && Number.isFinite(assignedMs) && assignedMs >= startMs) {
326
+ assignmentDurations.push(assignedMs - startMs);
327
+ }
328
+ }
329
+ const dependencyDurations = [];
330
+ for (const item of [
331
+ ...(dependencySnapshot?.inbound || []),
332
+ ...(dependencySnapshot?.outbound || []),
333
+ ]) {
334
+ if (!["resolved", "closed", "superseded", "cancelled"].includes(String(item.status || ""))) {
335
+ continue;
336
+ }
337
+ const startMs = Date.parse(item.createdAt || "");
338
+ const endMs = Date.parse(item.updatedAt || "");
339
+ if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs) {
340
+ dependencyDurations.push(endMs - startMs);
341
+ }
342
+ }
343
+ return {
344
+ meanTimeToCapabilityAssignmentMs: averageOrNull(assignmentDurations),
345
+ meanTimeToDependencyResolutionMs: averageOrNull(dependencyDurations),
346
+ };
347
+ }
348
+
349
+ function computeProofCompletenessRatio(wave, summariesByAgentId) {
350
+ const evaluatorAgentId = wave?.evaluatorAgentId || "A0";
351
+ const integrationAgentId = wave?.integrationAgentId || "A8";
352
+ const documentationAgentId = wave?.documentationAgentId || "A9";
353
+ const implementationAgents = (wave?.agents || []).filter((agent) =>
354
+ agent.agentId !== evaluatorAgentId &&
355
+ agent.agentId !== integrationAgentId &&
356
+ agent.agentId !== documentationAgentId,
357
+ );
358
+ const contractAgents = implementationAgents.filter((agent) => agent.exitContract);
359
+ if (contractAgents.length === 0) {
360
+ return 1;
361
+ }
362
+ const proofMet = contractAgents.filter((agent) =>
363
+ validateImplementationSummary(agent, summariesByAgentId?.[agent.agentId]).ok,
364
+ );
365
+ return Number((proofMet.length / contractAgents.length).toFixed(2));
366
+ }
367
+
368
+ function countRuntimeFallbacks(agentRuns) {
369
+ return (agentRuns || []).reduce((sum, run) => {
370
+ const history = Array.isArray(run?.agent?.executorResolved?.executorHistory)
371
+ ? run.agent.executorResolved.executorHistory
372
+ : [];
373
+ return sum + Math.max(0, history.length - 1);
374
+ }, 0);
375
+ }
376
+
377
+ function evaluatorReversalFromHistory(historySnapshot, gateSnapshot) {
378
+ const currentStatus = gateSnapshot?.evaluatorGate?.statusCode || null;
379
+ if (!currentStatus) {
380
+ return false;
381
+ }
382
+ const priorStatuses = normalizeHistorySnapshot(historySnapshot).evaluatorStatuses
383
+ .map((entry) => entry.statusCode)
384
+ .filter(Boolean)
385
+ .filter((status) => status !== currentStatus);
386
+ return priorStatuses.length > 0;
387
+ }
388
+
389
+ function resolveHistorySnapshot({
390
+ historySnapshot,
391
+ tracesDir,
392
+ wave,
393
+ attempt,
394
+ agentRuns,
395
+ gateSnapshot,
396
+ }) {
397
+ if (historySnapshot) {
398
+ return normalizeHistorySnapshot(historySnapshot);
399
+ }
400
+ return buildHistorySnapshot({
401
+ tracesDir,
402
+ waveNumber: wave.wave,
403
+ attempt,
404
+ agentRuns,
405
+ gateSnapshot,
406
+ });
407
+ }
408
+
409
+ export function buildQualityMetrics({
410
+ historySnapshot = null,
411
+ tracesDir,
412
+ wave,
413
+ attempt,
414
+ coordinationLogPath,
415
+ coordinationState,
416
+ integrationSummary,
417
+ ledger,
418
+ docsQueue,
419
+ summariesByAgentId,
420
+ agentRuns,
421
+ capabilityAssignments = [],
422
+ dependencySnapshot = null,
423
+ gateSnapshot = null,
424
+ }) {
425
+ const effectiveHistory = resolveHistorySnapshot({
426
+ historySnapshot,
427
+ tracesDir,
428
+ wave,
429
+ attempt,
430
+ agentRuns,
431
+ gateSnapshot,
432
+ });
433
+ const relaunchCounts = buildRelaunchCounts(effectiveHistory.launchEvents);
434
+ const fallbackCount = countRuntimeFallbacks(agentRuns);
435
+ const coordinationRecords = coordinationLogPath ? readCoordinationLog(coordinationLogPath) : [];
436
+ const timings = computeAckAndBlockerTimings(coordinationRecords);
437
+ const assignmentTimings = computeAssignmentAndDependencyTimings(
438
+ coordinationRecords,
439
+ dependencySnapshot,
440
+ );
441
+ const documentationItems = Array.isArray(docsQueue?.items) ? docsQueue.items : [];
442
+ const unresolvedClarificationCount = (coordinationState?.clarifications || []).filter((record) =>
443
+ ["open", "acknowledged", "in_progress"].includes(record.status),
444
+ ).length;
445
+ const clarificationLinkedCount = openClarificationLinkedRequests(coordinationState).length;
446
+ return {
447
+ attempt,
448
+ unresolvedRequestCount: (coordinationState?.requests || []).filter((record) =>
449
+ ["open", "acknowledged", "in_progress"].includes(record.status),
450
+ ).length,
451
+ unresolvedClarificationCount: unresolvedClarificationCount + clarificationLinkedCount,
452
+ humanEscalationCount: (coordinationState?.humanEscalations || []).length,
453
+ orchestratorResolvedClarificationCount:
454
+ (coordinationState?.resolvedByPolicy || []).length +
455
+ (coordinationState?.orchestratorGuidance || []).filter((record) =>
456
+ ["resolved", "closed"].includes(record.status),
457
+ ).length,
458
+ contradictionCount: integrationSummary?.conflictingClaims?.length || 0,
459
+ documentationDriftCount:
460
+ documentationItems.length > 0
461
+ ? documentationItems.length
462
+ : (ledger?.tasks || []).filter(
463
+ (task) => task.kind === "documentation" && task.state !== "done",
464
+ ).length,
465
+ proofCompletenessRatio: computeProofCompletenessRatio(wave, summariesByAgentId),
466
+ relaunchCountByRole: relaunchCounts.byRole,
467
+ relaunchCountByExecutor: relaunchCounts.byExecutor,
468
+ runtimeFallbackCount: fallbackCount,
469
+ runtimeFallbackRate:
470
+ relaunchCounts.totalLaunches > 0
471
+ ? Number((fallbackCount / relaunchCounts.totalLaunches).toFixed(2))
472
+ : 0,
473
+ openCapabilityRequestCount: (capabilityAssignments || []).filter((assignment) => assignment.blocking).length,
474
+ openRequiredDependencyCount:
475
+ (dependencySnapshot?.requiredInbound || []).length +
476
+ (dependencySnapshot?.requiredOutbound || []).length,
477
+ meanTimeToCapabilityAssignmentMs: assignmentTimings.meanTimeToCapabilityAssignmentMs,
478
+ meanTimeToDependencyResolutionMs: assignmentTimings.meanTimeToDependencyResolutionMs,
479
+ helperTaskAssignmentCount: (capabilityAssignments || []).filter((assignment) => assignment.assignedAgentId).length,
480
+ meanTimeToFirstAckMs: timings.meanTimeToFirstAckMs,
481
+ meanTimeToBlockerResolutionMs: timings.meanTimeToBlockerResolutionMs,
482
+ evaluatorReversal: evaluatorReversalFromHistory(effectiveHistory, gateSnapshot),
483
+ finalRecommendation: integrationSummary?.recommendation || "unknown",
484
+ };
485
+ }
486
+
487
+ function buildReplayContext({ lanePaths, wave }) {
488
+ return {
489
+ lane: lanePaths?.lane || null,
490
+ roles: {
491
+ evaluatorAgentId: lanePaths?.evaluatorAgentId || wave.evaluatorAgentId || "A0",
492
+ integrationAgentId: lanePaths?.integrationAgentId || wave.integrationAgentId || "A8",
493
+ documentationAgentId: lanePaths?.documentationAgentId || wave.documentationAgentId || "A9",
494
+ },
495
+ validation: {
496
+ requireDocumentationStewardFromWave:
497
+ lanePaths?.requireDocumentationStewardFromWave ?? null,
498
+ requireContext7DeclarationsFromWave:
499
+ lanePaths?.requireContext7DeclarationsFromWave ?? null,
500
+ requireExitContractsFromWave:
501
+ lanePaths?.requireExitContractsFromWave ?? null,
502
+ requireIntegrationStewardFromWave: lanePaths?.requireIntegrationStewardFromWave ?? null,
503
+ requireComponentPromotionsFromWave:
504
+ lanePaths?.requireComponentPromotionsFromWave ??
505
+ lanePaths?.laneProfile?.validation?.requireComponentPromotionsFromWave ??
506
+ null,
507
+ requireAgentComponentsFromWave:
508
+ lanePaths?.requireAgentComponentsFromWave ?? null,
509
+ },
510
+ };
511
+ }
512
+
513
+ function normalizeGateLogPath(gate, agentArtifacts) {
514
+ if (!gate || typeof gate !== "object") {
515
+ return gate;
516
+ }
517
+ if (!gate.logPath || !gate.agentId) {
518
+ return gate;
519
+ }
520
+ const artifact = agentArtifacts?.[gate.agentId]?.log;
521
+ if (!artifact?.present || !artifact?.path) {
522
+ return gate;
523
+ }
524
+ return {
525
+ ...gate,
526
+ logPath: artifact.path,
527
+ };
528
+ }
529
+
530
+ export function normalizeGateSnapshotForBundle(gateSnapshot, agentArtifacts) {
531
+ if (!gateSnapshot || typeof gateSnapshot !== "object") {
532
+ return gateSnapshot;
533
+ }
534
+ const normalized = { ...gateSnapshot };
535
+ for (const key of [
536
+ "implementationGate",
537
+ "componentGate",
538
+ "helperAssignmentBarrier",
539
+ "dependencyBarrier",
540
+ "integrationGate",
541
+ "integrationBarrier",
542
+ "documentationGate",
543
+ "componentMatrixGate",
544
+ "evaluatorGate",
545
+ "infraGate",
546
+ ]) {
547
+ normalized[key] = normalizeGateLogPath(gateSnapshot[key], agentArtifacts);
548
+ }
549
+ return normalized;
550
+ }
551
+
552
+ function buildStoredOutcomeSnapshot(gateSnapshot, quality) {
553
+ return {
554
+ gateSnapshot: gateSnapshot || null,
555
+ quality: quality || null,
556
+ };
557
+ }
558
+
559
+ function writeArtifactDescriptor(dir, filePath, payload, mode = "json", required = true) {
560
+ if (mode === "json") {
561
+ writeJsonAtomic(filePath, payload || {});
562
+ } else {
563
+ writeTextAtomic(filePath, `${String(payload || "")}\n`);
564
+ }
565
+ return {
566
+ path: relativePathOrNull(filePath, dir),
567
+ required,
568
+ present: true,
569
+ sha256: fileHashOrNull(filePath),
570
+ };
571
+ }
572
+
573
+ function copyArtifactDescriptor(dir, sourcePath, destPath, required = false) {
574
+ const present = copyFileIfExists(sourcePath, destPath);
575
+ return {
576
+ path: relativePathOrNull(destPath, dir),
577
+ required,
578
+ present,
579
+ sha256: present ? fileHashOrNull(destPath) : null,
580
+ };
581
+ }
582
+
583
+ function summaryPathFromStatusPath(statusPath) {
584
+ return statusPath ? statusPath.replace(/\.status$/i, ".summary.json") : null;
585
+ }
586
+
587
+ function readSummaryPayload(filePath) {
588
+ const payload = readJsonOrNull(filePath);
589
+ return payload && typeof payload === "object" ? payload : null;
590
+ }
591
+
592
+ function resolveRunSummaryPayload(wave, run) {
593
+ if (run?.summary && typeof run.summary === "object") {
594
+ return run.summary;
595
+ }
596
+ const sourceSummaryPath = summaryPathFromStatusPath(run?.statusPath);
597
+ if (sourceSummaryPath) {
598
+ const sourceSummary = readSummaryPayload(sourceSummaryPath);
599
+ if (sourceSummary) {
600
+ return sourceSummary;
601
+ }
602
+ }
603
+ const statusRecord = run?.statusPath ? readStatusRecordIfPresent(run.statusPath) : null;
604
+ if (!statusRecord || !run?.logPath || !fs.existsSync(run.logPath)) {
605
+ return null;
606
+ }
607
+ const reportPath =
608
+ run.agent?.agentId === (wave?.evaluatorAgentId || "A0") && wave?.evaluatorReportPath
609
+ ? path.resolve(REPO_ROOT, wave.evaluatorReportPath)
610
+ : null;
611
+ return buildAgentExecutionSummary({
612
+ agent: run.agent,
613
+ statusRecord,
614
+ logPath: run.logPath,
615
+ reportPath,
616
+ });
617
+ }
618
+
619
+ function writeSummaryArtifactDescriptor(dir, wave, run, attempt, slug) {
620
+ const destPath = path.join(dir, "summaries", `${slug}.summary.json`);
621
+ const launchedInAttempt = Number(run?.lastLaunchAttempt) === attempt;
622
+ const payload = resolveRunSummaryPayload(wave, run);
623
+ if (payload && typeof payload === "object") {
624
+ return writeArtifactDescriptor(dir, destPath, payload, "json", launchedInAttempt);
625
+ }
626
+ return {
627
+ path: relativePathOrNull(destPath, dir),
628
+ required: launchedInAttempt,
629
+ present: false,
630
+ sha256: null,
631
+ };
632
+ }
633
+
634
+ function buildAgentMetadata(dir, run, attempt, artifacts) {
635
+ const launchedInAttempt = Number(run.lastLaunchAttempt) === attempt;
636
+ const promptTracePath = artifacts.prompt.path;
637
+ const logTracePath = artifacts.log.path;
638
+ const statusTracePath = artifacts.status.path;
639
+ const summaryTracePath = artifacts.summary.path;
640
+ const inboxTracePath = artifacts.inbox.path;
641
+ return {
642
+ agentId: run.agent.agentId,
643
+ title: run.agent.title,
644
+ launchedInAttempt,
645
+ promptPath: promptTracePath,
646
+ promptHash: promptTracePath ? fileHashOrNull(path.join(dir, promptTracePath)) : null,
647
+ logPath: logTracePath,
648
+ statusPath: statusTracePath,
649
+ status: run.statusPath ? readStatusRecordIfPresent(run.statusPath) : null,
650
+ summaryPath: summaryTracePath,
651
+ inboxPath: inboxTracePath,
652
+ executor: run.agent.executorResolved
653
+ ? {
654
+ role: run.agent.executorResolved.role || null,
655
+ profile: run.agent.executorResolved.profile || null,
656
+ initialExecutorId: run.agent.executorResolved.initialExecutorId || null,
657
+ executorId: run.agent.executorResolved.id || null,
658
+ selectedBy: run.agent.executorResolved.selectedBy || null,
659
+ budget: run.agent.executorResolved.budget || null,
660
+ fallbacks: run.agent.executorResolved.fallbacks || [],
661
+ fallbackUsed: run.agent.executorResolved.fallbackUsed === true,
662
+ fallbackReason: run.agent.executorResolved.fallbackReason || null,
663
+ executorHistory: run.agent.executorResolved.executorHistory || [],
664
+ }
665
+ : null,
666
+ context7: {
667
+ selection: run.agent.context7Resolved || null,
668
+ mode: run.lastContext7?.mode || null,
669
+ warning: run.lastContext7?.warning || "",
670
+ snippetHash:
671
+ run.lastContext7?.snippetHash ||
672
+ (run.lastContext7?.promptText ? hashText(run.lastContext7.promptText) : ""),
673
+ },
674
+ };
675
+ }
676
+
677
+ export function writeTraceBundle({
678
+ tracesDir,
679
+ lanePaths,
680
+ launcherOptions,
681
+ wave,
682
+ attempt,
683
+ manifest,
684
+ coordinationLogPath,
685
+ coordinationState,
686
+ ledger,
687
+ docsQueue,
688
+ capabilityAssignments = [],
689
+ dependencySnapshot = null,
690
+ integrationSummary,
691
+ integrationMarkdownPath,
692
+ clarificationTriage,
693
+ agentRuns,
694
+ quality,
695
+ structuredSignals,
696
+ gateSnapshot = null,
697
+ }) {
698
+ const dir = traceAttemptDir(tracesDir, wave.wave, attempt);
699
+ ensureDirectory(dir);
700
+
701
+ const manifestArtifact = writeArtifactDescriptor(dir, path.join(dir, "manifest.json"), manifest, "json", true);
702
+ const coordinationMaterializedArtifact = writeArtifactDescriptor(
703
+ dir,
704
+ path.join(dir, "coordination.materialized.json"),
705
+ serializeCoordinationState(coordinationState || {}),
706
+ "json",
707
+ true,
708
+ );
709
+ const coordinationRawPath = path.join(dir, "coordination.raw.jsonl");
710
+ const coordinationRawPresent = writeCoordinationLogSnapshot(
711
+ coordinationLogPath,
712
+ coordinationRawPath,
713
+ coordinationState,
714
+ );
715
+ const coordinationRawArtifact = {
716
+ path: relativePathOrNull(coordinationRawPath, dir),
717
+ required: true,
718
+ present: coordinationRawPresent || fs.existsSync(coordinationRawPath),
719
+ sha256: fileHashOrNull(coordinationRawPath),
720
+ };
721
+ const ledgerArtifact = writeArtifactDescriptor(dir, path.join(dir, "ledger.json"), ledger, "json", true);
722
+ const docsQueueArtifact = writeArtifactDescriptor(
723
+ dir,
724
+ path.join(dir, "docs-queue.json"),
725
+ docsQueue,
726
+ "json",
727
+ true,
728
+ );
729
+ const capabilityAssignmentsArtifact = writeArtifactDescriptor(
730
+ dir,
731
+ path.join(dir, "capability-assignments.json"),
732
+ capabilityAssignments,
733
+ "json",
734
+ true,
735
+ );
736
+ const dependencySnapshotArtifact = writeArtifactDescriptor(
737
+ dir,
738
+ path.join(dir, "dependency-snapshot.json"),
739
+ dependencySnapshot || {},
740
+ "json",
741
+ true,
742
+ );
743
+ const integrationArtifact = writeArtifactDescriptor(
744
+ dir,
745
+ path.join(dir, "integration.json"),
746
+ integrationSummary,
747
+ "json",
748
+ true,
749
+ );
750
+ const integrationMarkdownArtifact = copyArtifactDescriptor(
751
+ dir,
752
+ integrationMarkdownPath,
753
+ path.join(dir, "integration.md"),
754
+ false,
755
+ );
756
+ const qualityArtifact = writeArtifactDescriptor(
757
+ dir,
758
+ path.join(dir, "quality.json"),
759
+ quality,
760
+ "json",
761
+ true,
762
+ );
763
+ const structuredSignalsArtifact = writeArtifactDescriptor(
764
+ dir,
765
+ path.join(dir, "structured-signals.json"),
766
+ structuredSignals || {},
767
+ "json",
768
+ true,
769
+ );
770
+ const sharedSummaryArtifact = agentRuns?.[0]?.sharedSummaryPath
771
+ ? copyArtifactDescriptor(
772
+ dir,
773
+ agentRuns[0].sharedSummaryPath,
774
+ path.join(dir, "shared-summary.md"),
775
+ true,
776
+ )
777
+ : {
778
+ path: "shared-summary.md",
779
+ required: true,
780
+ present: false,
781
+ sha256: null,
782
+ };
783
+ const feedbackTriageArtifact = copyArtifactDescriptor(
784
+ dir,
785
+ clarificationTriage?.triagePath,
786
+ path.join(dir, "feedback", "triage.jsonl"),
787
+ false,
788
+ );
789
+ const pendingHumanArtifact = copyArtifactDescriptor(
790
+ dir,
791
+ clarificationTriage?.pendingHumanPath,
792
+ path.join(dir, "feedback", "pending-human.md"),
793
+ false,
794
+ );
795
+ const componentMatrixRequired = Array.isArray(wave.componentPromotions) && wave.componentPromotions.length > 0;
796
+ const componentMatrixArtifact = copyArtifactDescriptor(
797
+ dir,
798
+ lanePaths?.componentCutoverMatrixJsonPath,
799
+ path.join(dir, "component-cutover-matrix.json"),
800
+ componentMatrixRequired,
801
+ );
802
+ const componentMatrixMarkdownArtifact = copyArtifactDescriptor(
803
+ dir,
804
+ lanePaths?.componentCutoverMatrixDocPath,
805
+ path.join(dir, "component-cutover-matrix.md"),
806
+ false,
807
+ );
808
+ const historySnapshot = buildHistorySnapshot({
809
+ tracesDir,
810
+ waveNumber: wave.wave,
811
+ attempt,
812
+ agentRuns,
813
+ gateSnapshot,
814
+ });
815
+
816
+ const agentArtifacts = {};
817
+ const agentsMetadata = [];
818
+ for (const run of agentRuns || []) {
819
+ const slug = run.agent.slug || run.agent.agentId;
820
+ const artifacts = {
821
+ prompt: copyArtifactDescriptor(
822
+ dir,
823
+ run.promptPath,
824
+ path.join(dir, "prompts", `${slug}.prompt.md`),
825
+ Number(run.lastLaunchAttempt) === attempt,
826
+ ),
827
+ log: copyArtifactDescriptor(
828
+ dir,
829
+ run.logPath,
830
+ path.join(dir, "logs", `${slug}.log`),
831
+ Number(run.lastLaunchAttempt) === attempt,
832
+ ),
833
+ status: copyArtifactDescriptor(
834
+ dir,
835
+ run.statusPath,
836
+ path.join(dir, "status", `${slug}.status`),
837
+ Number(run.lastLaunchAttempt) === attempt || Boolean(readStatusRecordIfPresent(run.statusPath)),
838
+ ),
839
+ summary: writeSummaryArtifactDescriptor(dir, wave, run, attempt, slug),
840
+ inbox: copyArtifactDescriptor(
841
+ dir,
842
+ run.inboxPath,
843
+ path.join(dir, "inboxes", `${slug}.md`),
844
+ true,
845
+ ),
846
+ };
847
+ agentArtifacts[run.agent.agentId] = artifacts;
848
+ agentsMetadata.push(buildAgentMetadata(dir, run, attempt, artifacts));
849
+ }
850
+ const replayContext = buildReplayContext({ lanePaths, wave });
851
+ const normalizedGateSnapshot = normalizeGateSnapshotForBundle(gateSnapshot || null, agentArtifacts);
852
+ const outcomeArtifact = writeArtifactDescriptor(
853
+ dir,
854
+ path.join(dir, "outcome.json"),
855
+ buildStoredOutcomeSnapshot(normalizedGateSnapshot, quality),
856
+ "json",
857
+ true,
858
+ );
859
+
860
+ const metadata = {
861
+ traceVersion: TRACE_VERSION,
862
+ replayMode: "hermetic",
863
+ wave: wave.wave,
864
+ lane: lanePaths?.lane || null,
865
+ waveFile: wave.file,
866
+ waveFileHash: fileHashOrNull(path.resolve(REPO_ROOT, wave.file || "")),
867
+ attempt,
868
+ cumulativeAttemptCount: attempt,
869
+ capturedAt: toIsoTimestamp(),
870
+ launcher: {
871
+ timeoutMinutes: launcherOptions?.timeoutMinutes ?? null,
872
+ maxRetriesPerWave: launcherOptions?.maxRetriesPerWave ?? null,
873
+ dryRun: Boolean(launcherOptions?.dryRun),
874
+ runVariant: lanePaths?.runVariant || "live",
875
+ },
876
+ roles: replayContext.roles,
877
+ validation: replayContext.validation,
878
+ replayContext,
879
+ historySnapshot,
880
+ gateSnapshot: normalizedGateSnapshot,
881
+ artifacts: {
882
+ manifest: manifestArtifact,
883
+ coordinationRaw: coordinationRawArtifact,
884
+ coordinationMaterialized: coordinationMaterializedArtifact,
885
+ ledger: ledgerArtifact,
886
+ docsQueue: docsQueueArtifact,
887
+ capabilityAssignments: capabilityAssignmentsArtifact,
888
+ dependencySnapshot: dependencySnapshotArtifact,
889
+ integration: integrationArtifact,
890
+ integrationMarkdown: integrationMarkdownArtifact,
891
+ componentMatrix: componentMatrixArtifact,
892
+ componentMatrixMarkdown: componentMatrixMarkdownArtifact,
893
+ outcome: outcomeArtifact,
894
+ sharedSummary: sharedSummaryArtifact,
895
+ structuredSignals: structuredSignalsArtifact,
896
+ quality: qualityArtifact,
897
+ feedbackTriage: feedbackTriageArtifact,
898
+ pendingHuman: pendingHumanArtifact,
899
+ agents: agentArtifacts,
900
+ },
901
+ agents: agentsMetadata,
902
+ };
903
+
904
+ metadata.artifacts.runMetadata = {
905
+ path: "run-metadata.json",
906
+ required: true,
907
+ present: true,
908
+ sha256: null,
909
+ };
910
+ writeJsonAtomic(path.join(dir, "run-metadata.json"), metadata);
911
+ return dir;
912
+ }
913
+
914
+ export function loadTraceBundle(dir) {
915
+ const metadata = readJsonOrNull(path.join(dir, "run-metadata.json"));
916
+ const manifest = readJsonOrNull(path.join(dir, "manifest.json"));
917
+ const coordinationState = readJsonOrNull(path.join(dir, "coordination.materialized.json"));
918
+ const coordinationRecords = readCoordinationLog(path.join(dir, "coordination.raw.jsonl"));
919
+ return {
920
+ dir,
921
+ metadata,
922
+ manifest,
923
+ coordinationState,
924
+ coordinationRecords,
925
+ ledger: readJsonOrNull(path.join(dir, "ledger.json")),
926
+ docsQueue: readJsonOrNull(path.join(dir, "docs-queue.json")),
927
+ capabilityAssignments: readJsonOrNull(path.join(dir, "capability-assignments.json")),
928
+ dependencySnapshot: readJsonOrNull(path.join(dir, "dependency-snapshot.json")),
929
+ integrationSummary: readJsonOrNull(path.join(dir, "integration.json")),
930
+ quality: readJsonOrNull(path.join(dir, "quality.json")),
931
+ storedOutcome: readJsonOrNull(path.join(dir, "outcome.json")),
932
+ structuredSignals: readJsonOrNull(path.join(dir, "structured-signals.json")),
933
+ componentMatrix: readJsonOrNull(path.join(dir, "component-cutover-matrix.json")),
934
+ componentMatrixPath: path.join(dir, "component-cutover-matrix.json"),
935
+ };
936
+ }
937
+
938
+ function visitArtifactDescriptors(artifacts, callback, prefix = "") {
939
+ if (!artifacts || typeof artifacts !== "object") {
940
+ return;
941
+ }
942
+ for (const [key, value] of Object.entries(artifacts)) {
943
+ const name = prefix ? `${prefix}.${key}` : key;
944
+ if (!value || typeof value !== "object") {
945
+ continue;
946
+ }
947
+ if ("path" in value && "present" in value) {
948
+ callback(name, value);
949
+ continue;
950
+ }
951
+ visitArtifactDescriptors(value, callback, name);
952
+ }
953
+ }
954
+
955
+ function validateArtifactPresence(errors, bundle, name, artifact) {
956
+ if (!artifact?.required) {
957
+ return;
958
+ }
959
+ if (!artifact.present) {
960
+ errors.push(`Missing required artifact ${name}.`);
961
+ return;
962
+ }
963
+ if (!artifact.path) {
964
+ errors.push(`Required artifact ${name} is missing a bundle path.`);
965
+ return;
966
+ }
967
+ const absolutePath = path.join(bundle.dir, artifact.path);
968
+ if (!fs.existsSync(absolutePath)) {
969
+ errors.push(`Artifact ${name} is marked present but missing on disk: ${artifact.path}`);
970
+ }
971
+ }
972
+
973
+ export function validateTraceBundle(bundle) {
974
+ const errors = [];
975
+ const warnings = [];
976
+ if (!bundle?.metadata || typeof bundle.metadata !== "object") {
977
+ return { ok: false, errors: ["Missing run-metadata.json"], warnings, replayMode: "invalid" };
978
+ }
979
+ if (bundle.metadata.traceVersion === LEGACY_TRACE_VERSION) {
980
+ warnings.push(
981
+ "Legacy traceVersion 1 bundle detected; replay is best-effort and may depend on sibling attempts or live repo context.",
982
+ );
983
+ } else if (bundle.metadata.traceVersion !== TRACE_VERSION) {
984
+ errors.push(
985
+ `Unsupported traceVersion ${bundle.metadata.traceVersion}; expected ${TRACE_VERSION}.`,
986
+ );
987
+ }
988
+ if (bundle.metadata.traceVersion >= TRACE_VERSION) {
989
+ if (!bundle.metadata.replayContext || typeof bundle.metadata.replayContext !== "object") {
990
+ errors.push("Hermetic trace bundle is missing replayContext.");
991
+ }
992
+ if (!bundle.metadata.historySnapshot || typeof bundle.metadata.historySnapshot !== "object") {
993
+ errors.push("Hermetic trace bundle is missing historySnapshot.");
994
+ }
995
+ if (!bundle.storedOutcome || typeof bundle.storedOutcome !== "object") {
996
+ errors.push("Hermetic trace bundle is missing outcome.json.");
997
+ }
998
+ }
999
+ if (!bundle.metadata.artifacts || typeof bundle.metadata.artifacts !== "object") {
1000
+ errors.push("Hermetic trace bundle is missing artifacts metadata.");
1001
+ }
1002
+ visitArtifactDescriptors(bundle.metadata.artifacts, (name, artifact) => {
1003
+ validateArtifactPresence(errors, bundle, name, artifact);
1004
+ if (!artifact?.present || !artifact?.path) {
1005
+ return;
1006
+ }
1007
+ const absolutePath = path.join(bundle.dir, artifact.path);
1008
+ if (!fs.existsSync(absolutePath)) {
1009
+ return;
1010
+ }
1011
+ if (typeof artifact.sha256 === "string" && artifact.sha256.length > 0) {
1012
+ const actual = fileHashOrNull(absolutePath);
1013
+ if (actual !== artifact.sha256) {
1014
+ errors.push(
1015
+ `Artifact ${name} hash mismatch: expected ${artifact.sha256}, got ${actual || "missing"}.`,
1016
+ );
1017
+ }
1018
+ }
1019
+ });
1020
+ const wave =
1021
+ bundle.manifest?.waves?.find((entry) => Number(entry.wave) === Number(bundle.metadata.wave)) ||
1022
+ bundle.manifest?.waves?.[0] ||
1023
+ null;
1024
+ if (bundle.metadata.traceVersion >= TRACE_VERSION && wave) {
1025
+ const hasPromotions =
1026
+ Array.isArray(wave.componentPromotions) && wave.componentPromotions.length > 0;
1027
+ const componentMatrixArtifact = bundle.metadata.artifacts?.componentMatrix;
1028
+ if (hasPromotions) {
1029
+ if (!componentMatrixArtifact || typeof componentMatrixArtifact !== "object") {
1030
+ errors.push("Promoted-component trace bundle is missing componentMatrix artifact metadata.");
1031
+ } else {
1032
+ if (componentMatrixArtifact.required !== true) {
1033
+ errors.push(
1034
+ "Promoted-component trace bundle must mark componentMatrix as a required artifact.",
1035
+ );
1036
+ }
1037
+ }
1038
+ }
1039
+ }
1040
+ for (const agent of bundle.metadata.agents || []) {
1041
+ const artifacts = bundle.metadata.artifacts?.agents?.[agent.agentId];
1042
+ if (agent.launchedInAttempt) {
1043
+ for (const key of ["prompt", "log", "status", "summary", "inbox"]) {
1044
+ if (bundle.metadata.traceVersion >= TRACE_VERSION) {
1045
+ if (!artifacts?.[key]) {
1046
+ errors.push(
1047
+ `Hermetic trace bundle is missing ${key} artifact metadata for launched agent ${agent.agentId}.`,
1048
+ );
1049
+ continue;
1050
+ }
1051
+ if (artifacts?.[key]?.required !== true) {
1052
+ errors.push(
1053
+ `Hermetic trace bundle must mark ${key} as required for launched agent ${agent.agentId}.`,
1054
+ );
1055
+ }
1056
+ } else if (key !== "summary" && !artifacts?.[key]?.present) {
1057
+ errors.push(`Missing ${key} artifact for launched agent ${agent.agentId}.`);
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+ if (!Array.isArray(bundle.manifest?.waves) || bundle.manifest.waves.length === 0) {
1063
+ errors.push("Trace manifest is missing wave definitions.");
1064
+ }
1065
+ if (bundle.metadata.traceVersion >= TRACE_VERSION && bundle.storedOutcome) {
1066
+ if (
1067
+ JSON.stringify(bundle.storedOutcome.quality || null) !==
1068
+ JSON.stringify(bundle.quality || null)
1069
+ ) {
1070
+ errors.push("Stored outcome quality snapshot does not match quality.json.");
1071
+ }
1072
+ if (
1073
+ bundle.metadata.gateSnapshot &&
1074
+ JSON.stringify(bundle.storedOutcome.gateSnapshot || null) !==
1075
+ JSON.stringify(bundle.metadata.gateSnapshot || null)
1076
+ ) {
1077
+ warnings.push("Stored outcome gate snapshot differs from inline run-metadata gateSnapshot.");
1078
+ }
1079
+ }
1080
+ return {
1081
+ ok: errors.length === 0,
1082
+ errors,
1083
+ warnings,
1084
+ replayMode: bundle.metadata.traceVersion >= TRACE_VERSION ? "hermetic" : "legacy-best-effort",
1085
+ };
1086
+ }
1087
+
1088
+ export function writeStructuredSignalsSnapshot(filePath, payload) {
1089
+ writeJsonAtomic(filePath, payload);
1090
+ }
1091
+
1092
+ export function writeMarkdownArtifact(filePath, text) {
1093
+ writeTextAtomic(filePath, `${String(text || "")}\n`);
1094
+ }