@chllming/wave-orchestration 0.8.3 → 0.8.5

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 (59) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/README.md +60 -11
  3. package/docs/README.md +8 -2
  4. package/docs/agents/wave-design-role.md +47 -0
  5. package/docs/concepts/what-is-a-wave.md +11 -7
  6. package/docs/guides/author-and-run-waves.md +24 -0
  7. package/docs/guides/planner.md +44 -0
  8. package/docs/plans/architecture-hardening-migration.md +8 -1
  9. package/docs/plans/current-state.md +19 -7
  10. package/docs/plans/end-state-architecture.md +88 -70
  11. package/docs/plans/examples/wave-example-design-handoff.md +262 -0
  12. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  13. package/docs/plans/migration.md +370 -64
  14. package/docs/plans/wave-orchestrator.md +49 -13
  15. package/docs/reference/cli-reference.md +46 -14
  16. package/docs/reference/coordination-and-closure.md +19 -6
  17. package/docs/reference/npmjs-trusted-publishing.md +5 -4
  18. package/docs/reference/sample-waves.md +14 -7
  19. package/docs/reference/skills.md +10 -0
  20. package/package.json +1 -1
  21. package/releases/manifest.json +39 -0
  22. package/scripts/wave-orchestrator/agent-state.mjs +64 -491
  23. package/scripts/wave-orchestrator/autonomous.mjs +10 -6
  24. package/scripts/wave-orchestrator/{launcher-closure.mjs → closure-engine.mjs} +190 -74
  25. package/scripts/wave-orchestrator/config.mjs +5 -0
  26. package/scripts/wave-orchestrator/coordination.mjs +42 -1
  27. package/scripts/wave-orchestrator/{launcher-derived-state.mjs → derived-state-engine.mjs} +34 -146
  28. package/scripts/wave-orchestrator/{launcher-gates.mjs → gate-engine.mjs} +501 -141
  29. package/scripts/wave-orchestrator/human-input-resolution.mjs +14 -10
  30. package/scripts/wave-orchestrator/human-input-workflow.mjs +104 -0
  31. package/scripts/wave-orchestrator/implementation-engine.mjs +120 -0
  32. package/scripts/wave-orchestrator/install.mjs +3 -0
  33. package/scripts/wave-orchestrator/launcher-runtime.mjs +11 -6
  34. package/scripts/wave-orchestrator/launcher.mjs +324 -723
  35. package/scripts/wave-orchestrator/ledger.mjs +56 -27
  36. package/scripts/wave-orchestrator/local-executor.mjs +37 -0
  37. package/scripts/wave-orchestrator/planner.mjs +24 -4
  38. package/scripts/wave-orchestrator/projection-writer.mjs +256 -0
  39. package/scripts/wave-orchestrator/reconcile-format.mjs +32 -0
  40. package/scripts/wave-orchestrator/reducer-snapshot.mjs +297 -0
  41. package/scripts/wave-orchestrator/replay.mjs +3 -1
  42. package/scripts/wave-orchestrator/result-envelope.mjs +620 -0
  43. package/scripts/wave-orchestrator/retry-control.mjs +22 -2
  44. package/scripts/wave-orchestrator/{launcher-retry.mjs → retry-engine.mjs} +352 -18
  45. package/scripts/wave-orchestrator/role-helpers.mjs +124 -1
  46. package/scripts/wave-orchestrator/{launcher-supervisor.mjs → session-supervisor.mjs} +178 -103
  47. package/scripts/wave-orchestrator/shared.mjs +2 -0
  48. package/scripts/wave-orchestrator/skills.mjs +1 -0
  49. package/scripts/wave-orchestrator/task-entity.mjs +65 -45
  50. package/scripts/wave-orchestrator/traces.mjs +10 -1
  51. package/scripts/wave-orchestrator/wave-files.mjs +96 -10
  52. package/scripts/wave-orchestrator/wave-state-reducer.mjs +76 -12
  53. package/skills/README.md +7 -0
  54. package/skills/role-design/SKILL.md +50 -0
  55. package/skills/role-design/skill.json +36 -0
  56. package/skills/tui-design/SKILL.md +77 -0
  57. package/skills/tui-design/references/tui-design.md +259 -0
  58. package/skills/tui-design/skill.json +36 -0
  59. package/wave.config.json +15 -1
@@ -3,10 +3,8 @@ import path from "node:path";
3
3
  import {
4
4
  agentSummaryPathFromStatusPath,
5
5
  buildAgentExecutionSummary,
6
- buildEnvelopeFromLegacySignals,
7
- buildExecutionSummaryFromEnvelope,
8
6
  readAgentExecutionSummary,
9
- readAgentResultEnvelope,
7
+ validateDesignSummary,
10
8
  validateContQaSummary,
11
9
  validateContEvalSummary,
12
10
  validateImplementationSummary,
@@ -14,8 +12,17 @@ import {
14
12
  validateSecuritySummary,
15
13
  validateIntegrationSummary,
16
14
  writeAgentExecutionSummary,
17
- writeAgentResultEnvelope,
18
15
  } from "./agent-state.mjs";
16
+ import {
17
+ projectLegacySummaryFromEnvelope,
18
+ readAgentResultEnvelope,
19
+ readAgentResultEnvelopeForRun,
20
+ resolveRunEnvelopeContext,
21
+ synthesizeLegacyEnvelope,
22
+ validateResultEnvelope,
23
+ writeAgentResultEnvelope,
24
+ writeAgentResultEnvelopeForRun,
25
+ } from "./result-envelope.mjs";
19
26
  import {
20
27
  REPO_ROOT,
21
28
  readFileTail,
@@ -27,7 +34,10 @@ import {
27
34
  writeJsonAtomic,
28
35
  } from "./shared.mjs";
29
36
  import {
37
+ isDocsOnlyDesignAgent,
30
38
  isSecurityReviewAgent,
39
+ isDesignAgent,
40
+ resolveDesignReportPath,
31
41
  resolveSecurityReviewReportPath,
32
42
  isContEvalReportOnlyAgent,
33
43
  } from "./role-helpers.mjs";
@@ -88,9 +98,40 @@ function resolveRunReportPath(wave, runInfo) {
88
98
  const securityReportPath = resolveSecurityReviewReportPath(runInfo.agent);
89
99
  return securityReportPath ? path.resolve(REPO_ROOT, securityReportPath) : null;
90
100
  }
101
+ if (isDesignAgent(runInfo.agent)) {
102
+ const designReportPath = resolveDesignReportPath(runInfo.agent);
103
+ return designReportPath ? path.resolve(REPO_ROOT, designReportPath) : null;
104
+ }
91
105
  return null;
92
106
  }
93
107
 
108
+ function normalizeReadMode(mode) {
109
+ return String(mode || "compat").trim().toLowerCase() === "live" ? "live" : "compat";
110
+ }
111
+
112
+ function buildEnvelopeReadOptions(runInfo, wave, statusRecord, reportPath) {
113
+ return {
114
+ agent: runInfo?.agent,
115
+ waveNumber: wave?.wave ?? null,
116
+ attempt: statusRecord?.attempt ?? null,
117
+ logPath: runInfo?.logPath || null,
118
+ reportPath,
119
+ };
120
+ }
121
+
122
+ function validateEnvelopeForRun(runInfo, envelope, options = {}) {
123
+ const validation = validateResultEnvelope(envelope, {
124
+ agent: runInfo?.agent,
125
+ waveNumber: options.wave?.wave ?? null,
126
+ });
127
+ return {
128
+ valid: validation.valid,
129
+ errors: validation.errors || [],
130
+ detail: validation.valid ? null : validation.errors.join(" "),
131
+ envelope: validation.valid ? envelope : null,
132
+ };
133
+ }
134
+
94
135
  export function materializeAgentExecutionSummaryForRun(wave, runInfo) {
95
136
  const statusRecord = readStatusRecordIfPresent(runInfo.statusPath);
96
137
  if (!statusRecord) {
@@ -104,13 +145,17 @@ export function materializeAgentExecutionSummaryForRun(wave, runInfo) {
104
145
  reportPath,
105
146
  });
106
147
  writeAgentExecutionSummary(runInfo.statusPath, summary);
107
- writeAgentResultEnvelope(
108
- runInfo.statusPath,
109
- buildEnvelopeFromLegacySignals(runInfo.agent, summary, {
148
+ writeAgentResultEnvelopeForRun(
149
+ runInfo,
150
+ wave,
151
+ synthesizeLegacyEnvelope(runInfo.agent, summary, {
110
152
  waveNumber: wave?.wave ?? null,
111
153
  attempt: statusRecord.attempt ?? null,
112
154
  exitCode: typeof statusRecord.code === "number" ? statusRecord.code : 0,
113
155
  }),
156
+ {
157
+ statusRecord,
158
+ },
114
159
  );
115
160
  if (runInfo?.previewPath && fs.existsSync(runInfo.previewPath)) {
116
161
  const previewPayload = readJsonOrNull(runInfo.previewPath);
@@ -141,48 +186,164 @@ export function materializeAgentExecutionSummaryForRun(wave, runInfo) {
141
186
  return summary;
142
187
  }
143
188
 
144
- export function readRunExecutionSummary(runInfo, wave = null) {
145
- const applyProofRegistry = (summary) =>
146
- runInfo?.proofRegistry ? augmentSummaryWithProofRegistry(runInfo.agent, summary, runInfo.proofRegistry) : summary;
189
+ export function readRunResultEnvelope(runInfo, wave = null, options = {}) {
190
+ const mode = normalizeReadMode(options.mode);
147
191
  const statusRecord = runInfo?.statusPath ? readStatusRecordIfPresent(runInfo.statusPath) : null;
148
- const reportPath = wave && runInfo?.logPath ? resolveRunReportPath(wave, runInfo) : null;
149
- const summaryReadOptions =
150
- wave && runInfo?.logPath
151
- ? {
152
- agent: runInfo.agent,
153
- statusPath: runInfo.statusPath,
154
- statusRecord,
155
- logPath: runInfo.logPath,
156
- reportPath,
157
- }
158
- : {};
159
- const envelopeReadOptions = {
160
- agent: runInfo?.agent,
161
- waveNumber: wave?.wave ?? null,
162
- attempt: statusRecord?.attempt ?? null,
163
- logPath: runInfo?.logPath || null,
164
- reportPath,
192
+ const reportPath = wave ? resolveRunReportPath(wave, runInfo) : null;
193
+ const runEnvelopeContext = resolveRunEnvelopeContext(runInfo, wave, { statusRecord });
194
+ const envelopeReadOptions = buildEnvelopeReadOptions(runInfo, wave, statusRecord, reportPath);
195
+ const synthesizeFromSummary = (summary, source) => {
196
+ if (!summary || mode === "live") {
197
+ return null;
198
+ }
199
+ const envelope = synthesizeLegacyEnvelope(runInfo?.agent, summary, {
200
+ waveNumber: wave?.wave ?? null,
201
+ attempt: statusRecord?.attempt ?? null,
202
+ exitCode:
203
+ typeof statusRecord?.code === "number"
204
+ ? statusRecord.code
205
+ : typeof summary?.exitCode === "number"
206
+ ? summary.exitCode
207
+ : 0,
208
+ });
209
+ return {
210
+ source,
211
+ ...validateEnvelopeForRun(runInfo, envelope, { wave }),
212
+ };
165
213
  };
214
+
166
215
  if (runInfo?.summary && typeof runInfo.summary === "object") {
167
- const summary = runInfo.summary.schemaVersion === 2
168
- ? buildExecutionSummaryFromEnvelope(runInfo.summary, envelopeReadOptions)
169
- : runInfo.summary;
170
- return applyProofRegistry(summary);
216
+ if (runInfo.summary.schemaVersion === 2) {
217
+ return {
218
+ source: "inline-envelope",
219
+ ...validateEnvelopeForRun(runInfo, runInfo.summary, { wave }),
220
+ };
221
+ }
222
+ const synthesized = synthesizeFromSummary(runInfo.summary, "inline-legacy-summary");
223
+ if (synthesized) {
224
+ return synthesized;
225
+ }
171
226
  }
172
227
  if (runInfo?.statusPath && fs.existsSync(runInfo.statusPath)) {
228
+ const envelope = readAgentResultEnvelopeForRun(runInfo, wave, { statusRecord });
229
+ if (envelope) {
230
+ const validation = {
231
+ source: "run-envelope",
232
+ ...validateEnvelopeForRun(runInfo, envelope, { wave }),
233
+ };
234
+ if (validation.valid || mode === "live") {
235
+ return validation;
236
+ }
237
+ }
238
+ }
239
+ if (mode !== "live" && runInfo?.statusPath && fs.existsSync(runInfo.statusPath)) {
173
240
  const envelope = readAgentResultEnvelope(runInfo.statusPath);
174
241
  if (envelope) {
175
- return applyProofRegistry(buildExecutionSummaryFromEnvelope(envelope, envelopeReadOptions));
242
+ const validation = {
243
+ source: "legacy-status-envelope",
244
+ ...validateEnvelopeForRun(runInfo, envelope, { wave }),
245
+ };
246
+ if (validation.valid) {
247
+ return validation;
248
+ }
249
+ }
250
+ }
251
+ if (mode !== "live" && runInfo?.summaryPath && fs.existsSync(runInfo.summaryPath)) {
252
+ const summary = readAgentExecutionSummary(runInfo.summaryPath, {
253
+ agent: runInfo.agent,
254
+ statusPath: runInfo.summaryPath,
255
+ statusRecord,
256
+ logPath: runInfo.logPath,
257
+ reportPath,
258
+ });
259
+ const synthesized = synthesizeFromSummary(summary, "summary-file-legacy");
260
+ if (synthesized) {
261
+ return synthesized;
176
262
  }
177
263
  }
264
+ if (
265
+ mode !== "live" &&
266
+ runInfo?.statusPath &&
267
+ fs.existsSync(agentSummaryPathFromStatusPath(runInfo.statusPath))
268
+ ) {
269
+ const summary = readAgentExecutionSummary(runInfo.statusPath, {
270
+ agent: runInfo.agent,
271
+ statusPath: runInfo.statusPath,
272
+ statusRecord,
273
+ logPath: runInfo.logPath,
274
+ reportPath,
275
+ });
276
+ const synthesized = synthesizeFromSummary(summary, "status-summary-legacy");
277
+ if (synthesized) {
278
+ return synthesized;
279
+ }
280
+ }
281
+ if (
282
+ mode !== "live" &&
283
+ wave &&
284
+ runInfo?.statusPath &&
285
+ runInfo?.logPath &&
286
+ fs.existsSync(runInfo.statusPath)
287
+ ) {
288
+ materializeAgentExecutionSummaryForRun(wave, runInfo);
289
+ const envelope =
290
+ readAgentResultEnvelopeForRun(runInfo, wave, { statusRecord }) ||
291
+ readAgentResultEnvelope(runInfo.statusPath);
292
+ if (envelope) {
293
+ return {
294
+ source: "materialized-legacy-envelope",
295
+ ...validateEnvelopeForRun(runInfo, envelope, { wave }),
296
+ };
297
+ }
298
+ }
299
+ return {
300
+ source: "missing-envelope",
301
+ valid: false,
302
+ errors: [
303
+ `Missing result envelope for ${runInfo?.agent?.agentId || "unknown-agent"} at ${path.relative(REPO_ROOT, runEnvelopeContext.envelopePath)}.`,
304
+ ],
305
+ detail: `Missing result envelope for ${runInfo?.agent?.agentId || "unknown-agent"} at ${path.relative(REPO_ROOT, runEnvelopeContext.envelopePath)}.`,
306
+ envelope: null,
307
+ envelopeReadOptions,
308
+ };
309
+ }
310
+
311
+ export function readRunExecutionSummary(runInfo, wave = null, options = {}) {
312
+ const mode = normalizeReadMode(options.mode);
313
+ const applyProofRegistry = (summary) =>
314
+ runInfo?.proofRegistry ? augmentSummaryWithProofRegistry(runInfo.agent, summary, runInfo.proofRegistry) : summary;
315
+ const statusRecord = runInfo?.statusPath ? readStatusRecordIfPresent(runInfo.statusPath) : null;
316
+ const reportPath = wave ? resolveRunReportPath(wave, runInfo) : null;
317
+ const envelopeReadOptions = buildEnvelopeReadOptions(runInfo, wave, statusRecord, reportPath);
318
+ const envelopeResult = readRunResultEnvelope(runInfo, wave, { mode });
319
+ if (envelopeResult?.valid && envelopeResult.envelope) {
320
+ return applyProofRegistry(
321
+ projectLegacySummaryFromEnvelope(envelopeResult.envelope, envelopeReadOptions),
322
+ );
323
+ }
324
+ if (mode === "live") {
325
+ return null;
326
+ }
327
+ if (runInfo?.summary && typeof runInfo.summary === "object") {
328
+ return applyProofRegistry(runInfo.summary);
329
+ }
178
330
  if (runInfo?.summaryPath && fs.existsSync(runInfo.summaryPath)) {
179
- return applyProofRegistry(readAgentExecutionSummary(runInfo.summaryPath, summaryReadOptions));
331
+ return applyProofRegistry(readAgentExecutionSummary(runInfo.summaryPath, {
332
+ agent: runInfo.agent,
333
+ statusPath: runInfo.summaryPath,
334
+ statusRecord,
335
+ logPath: runInfo.logPath,
336
+ reportPath,
337
+ }));
180
338
  }
181
339
  if (runInfo?.statusPath && fs.existsSync(agentSummaryPathFromStatusPath(runInfo.statusPath))) {
182
- return applyProofRegistry(readAgentExecutionSummary(runInfo.statusPath, summaryReadOptions));
183
- }
184
- if (wave && runInfo?.statusPath && runInfo?.logPath && fs.existsSync(runInfo.statusPath)) {
185
- return applyProofRegistry(materializeAgentExecutionSummaryForRun(wave, runInfo));
340
+ return applyProofRegistry(readAgentExecutionSummary(runInfo.statusPath, {
341
+ agent: runInfo.agent,
342
+ statusPath: runInfo.statusPath,
343
+ statusRecord,
344
+ logPath: runInfo.logPath,
345
+ reportPath,
346
+ }));
186
347
  }
187
348
  return null;
188
349
  }
@@ -208,7 +369,18 @@ export function readWaveContQaGate(wave, agentRuns, options = {}) {
208
369
  logPath: null,
209
370
  };
210
371
  }
211
- const summary = readRunExecutionSummary(contQaRun, strict ? wave : null);
372
+ const envelopeResult = readRunResultEnvelope(contQaRun, wave, { mode });
373
+ const summary = envelopeResult.valid
374
+ ? projectLegacySummaryFromEnvelope(
375
+ envelopeResult.envelope,
376
+ buildEnvelopeReadOptions(
377
+ contQaRun,
378
+ wave,
379
+ contQaRun?.statusPath ? readStatusRecordIfPresent(contQaRun.statusPath) : null,
380
+ resolveRunReportPath(wave, contQaRun),
381
+ ),
382
+ )
383
+ : readRunExecutionSummary(contQaRun, wave, { mode });
212
384
  if (summary) {
213
385
  const validation = validateContQaSummary(contQaRun.agent, summary, { mode });
214
386
  return {
@@ -223,8 +395,13 @@ export function readWaveContQaGate(wave, agentRuns, options = {}) {
223
395
  return {
224
396
  ok: false,
225
397
  agentId: contQaRun.agent.agentId,
226
- statusCode: "missing-wave-gate",
227
- detail: `Missing structured cont-QA summary for ${contQaRun.agent.agentId}.`,
398
+ statusCode:
399
+ envelopeResult.source === "missing-envelope"
400
+ ? "missing-result-envelope"
401
+ : "invalid-result-envelope",
402
+ detail:
403
+ envelopeResult.detail ||
404
+ `Missing structured cont-QA result envelope for ${contQaRun.agent.agentId}.`,
228
405
  logPath: path.relative(REPO_ROOT, contQaRun.logPath),
229
406
  };
230
407
  }
@@ -284,7 +461,18 @@ export function readWaveContEvalGate(wave, agentRuns, options = {}) {
284
461
  logPath: null,
285
462
  };
286
463
  }
287
- const summary = readRunExecutionSummary(contEvalRun, strict ? wave : null);
464
+ const envelopeResult = readRunResultEnvelope(contEvalRun, wave, { mode });
465
+ const summary = envelopeResult.valid
466
+ ? projectLegacySummaryFromEnvelope(
467
+ envelopeResult.envelope,
468
+ buildEnvelopeReadOptions(
469
+ contEvalRun,
470
+ wave,
471
+ contEvalRun?.statusPath ? readStatusRecordIfPresent(contEvalRun.statusPath) : null,
472
+ resolveRunReportPath(wave, contEvalRun),
473
+ ),
474
+ )
475
+ : readRunExecutionSummary(contEvalRun, wave, { mode });
288
476
  if (summary) {
289
477
  const validation = validateContEvalSummary(contEvalRun.agent, summary, {
290
478
  mode,
@@ -302,8 +490,16 @@ export function readWaveContEvalGate(wave, agentRuns, options = {}) {
302
490
  return {
303
491
  ok: false,
304
492
  agentId: contEvalRun.agent.agentId,
305
- statusCode: "missing-wave-eval",
306
- detail: `Missing [wave-eval] marker for ${contEvalRun.agent.agentId}.`,
493
+ statusCode:
494
+ strict && envelopeResult.source !== "missing-envelope"
495
+ ? "invalid-result-envelope"
496
+ : strict
497
+ ? "missing-result-envelope"
498
+ : "missing-wave-eval",
499
+ detail:
500
+ strict && envelopeResult.detail
501
+ ? envelopeResult.detail
502
+ : `Missing [wave-eval] marker for ${contEvalRun.agent.agentId}.`,
307
503
  logPath: path.relative(REPO_ROOT, contEvalRun.logPath),
308
504
  };
309
505
  }
@@ -315,7 +511,8 @@ export function readWaveEvaluatorGate(wave, agentRuns, options = {}) {
315
511
  });
316
512
  }
317
513
 
318
- export function readWaveImplementationGate(wave, agentRuns) {
514
+ export function readWaveImplementationGate(wave, agentRuns, options = {}) {
515
+ const mode = normalizeReadMode(options.mode || "live");
319
516
  const contQaAgentId = wave.contQaAgentId || "A0";
320
517
  const contEvalAgentId = wave.contEvalAgentId || "E0";
321
518
  const integrationAgentId = wave.integrationAgentId || "A8";
@@ -324,11 +521,37 @@ export function readWaveImplementationGate(wave, agentRuns) {
324
521
  if (
325
522
  [contQaAgentId, integrationAgentId, documentationAgentId].includes(runInfo.agent.agentId) ||
326
523
  isContEvalReportOnlyAgent(runInfo.agent, { contEvalAgentId }) ||
524
+ isDocsOnlyDesignAgent(runInfo.agent) ||
327
525
  isSecurityReviewAgent(runInfo.agent)
328
526
  ) {
329
527
  continue;
330
528
  }
331
- const summary = readRunExecutionSummary(runInfo, wave);
529
+ const envelopeResult = readRunResultEnvelope(runInfo, wave, { mode });
530
+ if (mode === "live" && !envelopeResult.valid) {
531
+ return {
532
+ ok: false,
533
+ agentId: runInfo.agent.agentId,
534
+ statusCode:
535
+ envelopeResult.source === "missing-envelope"
536
+ ? "missing-result-envelope"
537
+ : "invalid-result-envelope",
538
+ detail:
539
+ envelopeResult.detail ||
540
+ `Missing structured implementation result envelope for ${runInfo.agent.agentId}.`,
541
+ logPath: path.relative(REPO_ROOT, runInfo.logPath),
542
+ };
543
+ }
544
+ const summary = envelopeResult.valid
545
+ ? projectLegacySummaryFromEnvelope(
546
+ envelopeResult.envelope,
547
+ buildEnvelopeReadOptions(
548
+ runInfo,
549
+ wave,
550
+ runInfo?.statusPath ? readStatusRecordIfPresent(runInfo.statusPath) : null,
551
+ resolveRunReportPath(wave, runInfo),
552
+ ),
553
+ )
554
+ : readRunExecutionSummary(runInfo, wave, { mode });
332
555
  const validation = validateImplementationSummary(runInfo.agent, summary);
333
556
  if (!validation.ok) {
334
557
  return {
@@ -349,6 +572,65 @@ export function readWaveImplementationGate(wave, agentRuns) {
349
572
  };
350
573
  }
351
574
 
575
+ export function readWaveDesignGate(wave, agentRuns, options = {}) {
576
+ const mode = normalizeReadMode(options.mode || "live");
577
+ const designRuns = (agentRuns || []).filter((run) => isDesignAgent(run.agent));
578
+ if (designRuns.length === 0) {
579
+ return {
580
+ ok: true,
581
+ agentId: null,
582
+ statusCode: "pass",
583
+ detail: "No design agent declared for this wave.",
584
+ logPath: null,
585
+ };
586
+ }
587
+ for (const runInfo of designRuns) {
588
+ const envelopeResult = readRunResultEnvelope(runInfo, wave, { mode });
589
+ if (mode === "live" && !envelopeResult.valid) {
590
+ return {
591
+ ok: false,
592
+ agentId: runInfo.agent.agentId,
593
+ statusCode:
594
+ envelopeResult.source === "missing-envelope"
595
+ ? "missing-result-envelope"
596
+ : "invalid-result-envelope",
597
+ detail:
598
+ envelopeResult.detail ||
599
+ `Missing structured design result envelope for ${runInfo.agent.agentId}.`,
600
+ logPath: path.relative(REPO_ROOT, runInfo.logPath),
601
+ };
602
+ }
603
+ const summary = envelopeResult.valid
604
+ ? projectLegacySummaryFromEnvelope(
605
+ envelopeResult.envelope,
606
+ buildEnvelopeReadOptions(
607
+ runInfo,
608
+ wave,
609
+ runInfo?.statusPath ? readStatusRecordIfPresent(runInfo.statusPath) : null,
610
+ resolveRunReportPath(wave, runInfo),
611
+ ),
612
+ )
613
+ : readRunExecutionSummary(runInfo, wave, { mode });
614
+ const validation = validateDesignSummary(runInfo.agent, summary);
615
+ if (!validation.ok) {
616
+ return {
617
+ ok: false,
618
+ agentId: runInfo.agent.agentId,
619
+ statusCode: validation.statusCode,
620
+ detail: validation.detail,
621
+ logPath: summary?.logPath || path.relative(REPO_ROOT, runInfo.logPath),
622
+ };
623
+ }
624
+ }
625
+ return {
626
+ ok: true,
627
+ agentId: null,
628
+ statusCode: "pass",
629
+ detail: "All design packets are ready for implementation.",
630
+ logPath: null,
631
+ };
632
+ }
633
+
352
634
  export function analyzePromotedComponentOwners(componentId, agentRuns, summariesByAgentId) {
353
635
  const ownerRuns = (agentRuns || []).filter((runInfo) =>
354
636
  runInfo.agent.components?.includes(componentId),
@@ -423,8 +705,12 @@ export function buildSharedComponentSiblingPendingFailure(componentState) {
423
705
  }
424
706
 
425
707
  export function readWaveComponentGate(wave, agentRuns, options = {}) {
708
+ const mode = normalizeReadMode(options.mode);
426
709
  const summariesByAgentId = Object.fromEntries(
427
- agentRuns.map((runInfo) => [runInfo.agent.agentId, readRunExecutionSummary(runInfo, wave)]),
710
+ agentRuns.map((runInfo) => [
711
+ runInfo.agent.agentId,
712
+ readRunExecutionSummary(runInfo, wave, { mode }),
713
+ ]),
428
714
  );
429
715
  const validation = validateWaveComponentPromotions(wave, summariesByAgentId, options);
430
716
  const sharedPending = (wave.componentPromotions || [])
@@ -493,7 +779,8 @@ export function readWaveComponentMatrixGate(wave, agentRuns, options = {}) {
493
779
  };
494
780
  }
495
781
 
496
- export function readWaveDocumentationGate(wave, agentRuns) {
782
+ export function readWaveDocumentationGate(wave, agentRuns, options = {}) {
783
+ const mode = normalizeReadMode(options.mode || "live");
497
784
  const documentationAgentId = wave.documentationAgentId || "A9";
498
785
  const docRun =
499
786
  agentRuns.find((run) => run.agent.agentId === documentationAgentId) ?? null;
@@ -506,7 +793,32 @@ export function readWaveDocumentationGate(wave, agentRuns) {
506
793
  logPath: null,
507
794
  };
508
795
  }
509
- const summary = readRunExecutionSummary(docRun, wave);
796
+ const envelopeResult = readRunResultEnvelope(docRun, wave, { mode });
797
+ if (mode === "live" && !envelopeResult.valid) {
798
+ return {
799
+ ok: false,
800
+ agentId: docRun.agent.agentId,
801
+ statusCode:
802
+ envelopeResult.source === "missing-envelope"
803
+ ? "missing-result-envelope"
804
+ : "invalid-result-envelope",
805
+ detail:
806
+ envelopeResult.detail ||
807
+ `Missing structured documentation result envelope for ${docRun.agent.agentId}.`,
808
+ logPath: path.relative(REPO_ROOT, docRun.logPath),
809
+ };
810
+ }
811
+ const summary = envelopeResult.valid
812
+ ? projectLegacySummaryFromEnvelope(
813
+ envelopeResult.envelope,
814
+ buildEnvelopeReadOptions(
815
+ docRun,
816
+ wave,
817
+ docRun?.statusPath ? readStatusRecordIfPresent(docRun.statusPath) : null,
818
+ resolveRunReportPath(wave, docRun),
819
+ ),
820
+ )
821
+ : readRunExecutionSummary(docRun, wave, { mode });
510
822
  const validation = validateDocumentationClosureSummary(docRun.agent, summary);
511
823
  return {
512
824
  ok: validation.ok,
@@ -517,7 +829,8 @@ export function readWaveDocumentationGate(wave, agentRuns) {
517
829
  };
518
830
  }
519
831
 
520
- export function readWaveSecurityGate(wave, agentRuns) {
832
+ export function readWaveSecurityGate(wave, agentRuns, options = {}) {
833
+ const mode = normalizeReadMode(options.mode || "live");
521
834
  const securityRuns = (agentRuns || []).filter((run) => isSecurityReviewAgent(run.agent));
522
835
  if (securityRuns.length === 0) {
523
836
  return {
@@ -530,7 +843,32 @@ export function readWaveSecurityGate(wave, agentRuns) {
530
843
  }
531
844
  const concernAgentIds = [];
532
845
  for (const runInfo of securityRuns) {
533
- const summary = readRunExecutionSummary(runInfo, wave);
846
+ const envelopeResult = readRunResultEnvelope(runInfo, wave, { mode });
847
+ if (mode === "live" && !envelopeResult.valid) {
848
+ return {
849
+ ok: false,
850
+ agentId: runInfo.agent.agentId,
851
+ statusCode:
852
+ envelopeResult.source === "missing-envelope"
853
+ ? "missing-result-envelope"
854
+ : "invalid-result-envelope",
855
+ detail:
856
+ envelopeResult.detail ||
857
+ `Missing structured security result envelope for ${runInfo.agent.agentId}.`,
858
+ logPath: path.relative(REPO_ROOT, runInfo.logPath),
859
+ };
860
+ }
861
+ const summary = envelopeResult.valid
862
+ ? projectLegacySummaryFromEnvelope(
863
+ envelopeResult.envelope,
864
+ buildEnvelopeReadOptions(
865
+ runInfo,
866
+ wave,
867
+ runInfo?.statusPath ? readStatusRecordIfPresent(runInfo.statusPath) : null,
868
+ resolveRunReportPath(wave, runInfo),
869
+ ),
870
+ )
871
+ : readRunExecutionSummary(runInfo, wave, { mode });
534
872
  const validation = validateSecuritySummary(runInfo.agent, summary);
535
873
  if (!validation.ok) {
536
874
  return {
@@ -564,6 +902,7 @@ export function readWaveSecurityGate(wave, agentRuns) {
564
902
  }
565
903
 
566
904
  export function readWaveIntegrationGate(wave, agentRuns, options = {}) {
905
+ const mode = normalizeReadMode(options.mode || "live");
567
906
  const integrationAgentId =
568
907
  options.integrationAgentId || wave.integrationAgentId || "A8";
569
908
  const requireIntegration =
@@ -584,7 +923,32 @@ export function readWaveIntegrationGate(wave, agentRuns, options = {}) {
584
923
  logPath: null,
585
924
  };
586
925
  }
587
- const summary = readRunExecutionSummary(integrationRun, wave);
926
+ const envelopeResult = readRunResultEnvelope(integrationRun, wave, { mode });
927
+ if (mode === "live" && !envelopeResult.valid) {
928
+ return {
929
+ ok: false,
930
+ agentId: integrationRun.agent.agentId,
931
+ statusCode:
932
+ envelopeResult.source === "missing-envelope"
933
+ ? "missing-result-envelope"
934
+ : "invalid-result-envelope",
935
+ detail:
936
+ envelopeResult.detail ||
937
+ `Missing structured integration result envelope for ${integrationRun.agent.agentId}.`,
938
+ logPath: path.relative(REPO_ROOT, integrationRun.logPath),
939
+ };
940
+ }
941
+ const summary = envelopeResult.valid
942
+ ? projectLegacySummaryFromEnvelope(
943
+ envelopeResult.envelope,
944
+ buildEnvelopeReadOptions(
945
+ integrationRun,
946
+ wave,
947
+ integrationRun?.statusPath ? readStatusRecordIfPresent(integrationRun.statusPath) : null,
948
+ resolveRunReportPath(wave, integrationRun),
949
+ ),
950
+ )
951
+ : readRunExecutionSummary(integrationRun, wave, { mode });
588
952
  const validation = validateIntegrationSummary(integrationRun.agent, summary);
589
953
  return {
590
954
  ok: validation.ok,
@@ -737,91 +1101,51 @@ export function buildGateSnapshot({
737
1101
  validationMode = "compat",
738
1102
  readWaveInfraGateFn,
739
1103
  }) {
740
- const implementationGate = readWaveImplementationGate(wave, agentRuns);
741
- const componentGate = readWaveComponentGate(wave, agentRuns, {
742
- laneProfile: lanePaths?.laneProfile,
743
- });
744
- const integrationGate = readWaveIntegrationGate(wave, agentRuns, {
745
- integrationAgentId: lanePaths?.integrationAgentId,
746
- requireIntegrationStewardFromWave: lanePaths?.requireIntegrationStewardFromWave,
747
- });
748
- const integrationBarrier = readWaveIntegrationBarrier(wave, agentRuns, derivedState, {
749
- integrationAgentId: lanePaths?.integrationAgentId,
750
- requireIntegrationStewardFromWave: lanePaths?.requireIntegrationStewardFromWave,
751
- });
752
- const documentationGate = readWaveDocumentationGate(wave, agentRuns);
753
- const componentMatrixGate = readWaveComponentMatrixGate(wave, agentRuns, {
754
- laneProfile: lanePaths?.laneProfile,
755
- documentationAgentId: lanePaths?.documentationAgentId,
756
- componentMatrixPayload,
757
- componentMatrixJsonPath,
758
- });
759
- const contEvalGate = readWaveContEvalGate(wave, agentRuns, {
760
- contEvalAgentId: lanePaths?.contEvalAgentId,
761
- mode: validationMode,
762
- evalTargets: wave.evalTargets,
763
- benchmarkCatalogPath: lanePaths?.laneProfile?.paths?.benchmarkCatalogPath,
764
- });
765
- const securityGate = readWaveSecurityGate(wave, agentRuns);
766
- const contQaGate = readWaveContQaGate(wave, agentRuns, {
767
- contQaAgentId: lanePaths?.contQaAgentId,
768
- mode: validationMode,
1104
+ const agentResults = Object.fromEntries(
1105
+ (agentRuns || [])
1106
+ .map((runInfo) => [
1107
+ runInfo.agent.agentId,
1108
+ readRunExecutionSummary(runInfo, wave, { mode: validationMode }),
1109
+ ])
1110
+ .filter(([, summary]) => Boolean(summary)),
1111
+ );
1112
+ return buildGateSnapshotPure({
1113
+ wave,
1114
+ agentResults,
1115
+ derivedState: {
1116
+ ...derivedState,
1117
+ clarificationBarrier:
1118
+ derivedState?.clarificationBarrier || readClarificationBarrier(derivedState),
1119
+ helperAssignmentBarrier:
1120
+ derivedState?.helperAssignmentBarrier || readWaveAssignmentBarrier(derivedState),
1121
+ dependencyBarrier:
1122
+ derivedState?.dependencyBarrier || readWaveDependencyBarrier(derivedState),
1123
+ },
1124
+ validationMode,
1125
+ laneConfig: {
1126
+ contQaAgentId: lanePaths?.contQaAgentId,
1127
+ contEvalAgentId: lanePaths?.contEvalAgentId,
1128
+ integrationAgentId: lanePaths?.integrationAgentId,
1129
+ documentationAgentId: lanePaths?.documentationAgentId,
1130
+ requireIntegrationStewardFromWave: lanePaths?.requireIntegrationStewardFromWave,
1131
+ laneProfile: lanePaths?.laneProfile,
1132
+ benchmarkCatalogPath: lanePaths?.laneProfile?.paths?.benchmarkCatalogPath,
1133
+ componentMatrixPayload,
1134
+ componentMatrixJsonPath,
1135
+ },
769
1136
  });
770
- const infraGate = readWaveInfraGateFn(agentRuns);
771
- const clarificationBarrier = readClarificationBarrier(derivedState);
772
- const helperAssignmentBarrier = readWaveAssignmentBarrier(derivedState);
773
- const dependencyBarrier = readWaveDependencyBarrier(derivedState);
774
- const orderedGates = [
775
- ["implementationGate", implementationGate],
776
- ["componentGate", componentGate],
777
- ["helperAssignmentBarrier", helperAssignmentBarrier],
778
- ["dependencyBarrier", dependencyBarrier],
779
- ["contEvalGate", contEvalGate],
780
- ["securityGate", securityGate],
781
- ["integrationBarrier", integrationBarrier],
782
- ["documentationGate", documentationGate],
783
- ["componentMatrixGate", componentMatrixGate],
784
- ["contQaGate", contQaGate],
785
- ["infraGate", infraGate],
786
- ["clarificationBarrier", clarificationBarrier],
787
- ];
788
- const firstFailure = orderedGates.find(([, gate]) => gate?.ok === false);
789
- return {
790
- implementationGate,
791
- componentGate,
792
- integrationGate,
793
- integrationBarrier,
794
- documentationGate,
795
- componentMatrixGate,
796
- contEvalGate,
797
- securityGate,
798
- contQaGate,
799
- infraGate,
800
- clarificationBarrier,
801
- helperAssignmentBarrier,
802
- dependencyBarrier,
803
- overall: firstFailure
804
- ? {
805
- ok: false,
806
- gate: firstFailure[0],
807
- statusCode: firstFailure[1].statusCode,
808
- detail: firstFailure[1].detail,
809
- agentId: firstFailure[1].agentId || null,
810
- }
811
- : {
812
- ok: true,
813
- gate: "pass",
814
- statusCode: "pass",
815
- detail: "All replayed wave gates passed.",
816
- agentId: null,
817
- },
818
- };
819
1137
  }
820
1138
 
821
1139
  // --- Pure gate variants (no file I/O) ---
822
1140
  // These accept agentResults map { agentId: executionSummary } instead of runInfo objects.
823
1141
  // Used by the wave-state-reducer for deterministic replay.
824
1142
 
1143
+ function waveDeclaresAgent(wave, agentId) {
1144
+ return (Array.isArray(wave?.agents) ? wave.agents : []).some(
1145
+ (agent) => agent?.agentId === agentId,
1146
+ );
1147
+ }
1148
+
825
1149
  export function readWaveImplementationGatePure(wave, agentResults, options = {}) {
826
1150
  const contQaAgentId = options.contQaAgentId || wave.contQaAgentId || "A0";
827
1151
  const contEvalAgentId = options.contEvalAgentId || wave.contEvalAgentId || "E0";
@@ -832,6 +1156,7 @@ export function readWaveImplementationGatePure(wave, agentResults, options = {})
832
1156
  if (
833
1157
  [contQaAgentId, integrationAgentId, documentationAgentId].includes(agent.agentId) ||
834
1158
  isContEvalReportOnlyAgent(agent, { contEvalAgentId }) ||
1159
+ isDocsOnlyDesignAgent(agent) ||
835
1160
  isSecurityReviewAgent(agent)
836
1161
  ) {
837
1162
  continue;
@@ -854,6 +1179,40 @@ export function readWaveImplementationGatePure(wave, agentResults, options = {})
854
1179
  };
855
1180
  }
856
1181
 
1182
+ export function readWaveDesignGatePure(wave, agentResults) {
1183
+ const agents = Array.isArray(wave.agents) ? wave.agents : [];
1184
+ const designAgents = agents.filter((agent) => isDesignAgent(agent));
1185
+ if (designAgents.length === 0) {
1186
+ return {
1187
+ ok: true,
1188
+ agentId: null,
1189
+ statusCode: "pass",
1190
+ detail: "No design agent declared for this wave.",
1191
+ logPath: null,
1192
+ };
1193
+ }
1194
+ for (const agent of designAgents) {
1195
+ const summary = agentResults?.[agent.agentId] || null;
1196
+ const validation = validateDesignSummary(agent, summary);
1197
+ if (!validation.ok) {
1198
+ return {
1199
+ ok: false,
1200
+ agentId: agent.agentId,
1201
+ statusCode: validation.statusCode,
1202
+ detail: validation.detail,
1203
+ logPath: summary?.logPath || null,
1204
+ };
1205
+ }
1206
+ }
1207
+ return {
1208
+ ok: true,
1209
+ agentId: null,
1210
+ statusCode: "pass",
1211
+ detail: "All design packets are ready for implementation.",
1212
+ logPath: null,
1213
+ };
1214
+ }
1215
+
857
1216
  export function readWaveContQaGatePure(wave, agentResults, options = {}) {
858
1217
  const mode = String(options.mode || "live").trim().toLowerCase();
859
1218
  const contQaAgentId = options.contQaAgentId || wave.contQaAgentId || "A0";
@@ -865,24 +1224,24 @@ export function readWaveContQaGatePure(wave, agentResults, options = {}) {
865
1224
  const agent = { agentId: contQaAgentId };
866
1225
  const validation = validateContQaSummary(agent, summary, { mode });
867
1226
  return { ok: validation.ok, agentId: contQaAgentId, statusCode: validation.statusCode,
868
- detail: validation.detail, logPath: summary.logPath || null };
1227
+ detail: validation.detail, logPath: summary?.logPath || null };
869
1228
  }
870
1229
 
871
1230
  export function readWaveContEvalGatePure(wave, agentResults, options = {}) {
872
1231
  const mode = String(options.mode || "live").trim().toLowerCase();
873
1232
  const contEvalAgentId = options.contEvalAgentId || wave.contEvalAgentId || "E0";
874
- const summary = agentResults?.[contEvalAgentId] || null;
875
- if (!summary) {
1233
+ if (!waveDeclaresAgent(wave, contEvalAgentId)) {
876
1234
  return { ok: true, agentId: null, statusCode: "pass",
877
1235
  detail: "Wave does not include cont-EVAL.", logPath: null };
878
1236
  }
1237
+ const summary = agentResults?.[contEvalAgentId] || null;
879
1238
  const agent = { agentId: contEvalAgentId };
880
1239
  const validation = validateContEvalSummary(agent, summary, {
881
1240
  mode, evalTargets: options.evalTargets || wave.evalTargets,
882
1241
  benchmarkCatalogPath: options.benchmarkCatalogPath,
883
1242
  });
884
1243
  return { ok: validation.ok, agentId: contEvalAgentId, statusCode: validation.statusCode,
885
- detail: validation.detail, logPath: summary.logPath || null };
1244
+ detail: validation.detail, logPath: summary?.logPath || null };
886
1245
  }
887
1246
 
888
1247
  export function readWaveEvaluatorGatePure(wave, agentResults, options = {}) {
@@ -976,15 +1335,15 @@ export function readWaveComponentMatrixGatePure(wave, agentResults, options = {}
976
1335
 
977
1336
  export function readWaveDocumentationGatePure(wave, agentResults, options = {}) {
978
1337
  const documentationAgentId = options.documentationAgentId || wave.documentationAgentId || "A9";
979
- const summary = agentResults?.[documentationAgentId] || null;
980
- if (!summary) {
1338
+ if (!waveDeclaresAgent(wave, documentationAgentId)) {
981
1339
  return { ok: true, agentId: null, statusCode: "pass",
982
1340
  detail: "No documentation steward declared for this wave.", logPath: null };
983
1341
  }
1342
+ const summary = agentResults?.[documentationAgentId] || null;
984
1343
  const agent = { agentId: documentationAgentId };
985
1344
  const validation = validateDocumentationClosureSummary(agent, summary);
986
1345
  return { ok: validation.ok, agentId: documentationAgentId, statusCode: validation.statusCode,
987
- detail: validation.detail, logPath: summary.logPath || null };
1346
+ detail: validation.detail, logPath: summary?.logPath || null };
988
1347
  }
989
1348
 
990
1349
  export function readWaveSecurityGatePure(wave, agentResults, options = {}) {
@@ -1016,8 +1375,7 @@ export function readWaveIntegrationGatePure(wave, agentResults, options = {}) {
1016
1375
  const integrationAgentId = options.integrationAgentId || wave.integrationAgentId || "A8";
1017
1376
  const requireIntegration = options.requireIntegrationSteward === true ||
1018
1377
  (options.requireIntegrationStewardFromWave != null && wave.wave >= options.requireIntegrationStewardFromWave);
1019
- const summary = agentResults?.[integrationAgentId] || null;
1020
- if (!summary) {
1378
+ if (!waveDeclaresAgent(wave, integrationAgentId)) {
1021
1379
  return {
1022
1380
  ok: !requireIntegration,
1023
1381
  agentId: requireIntegration ? integrationAgentId : null,
@@ -1027,10 +1385,11 @@ export function readWaveIntegrationGatePure(wave, agentResults, options = {}) {
1027
1385
  logPath: null,
1028
1386
  };
1029
1387
  }
1388
+ const summary = agentResults?.[integrationAgentId] || null;
1030
1389
  const agent = { agentId: integrationAgentId };
1031
1390
  const validation = validateIntegrationSummary(agent, summary);
1032
1391
  return { ok: validation.ok, agentId: integrationAgentId, statusCode: validation.statusCode,
1033
- detail: validation.detail, logPath: summary.logPath || null };
1392
+ detail: validation.detail, logPath: summary?.logPath || null };
1034
1393
  }
1035
1394
 
1036
1395
  const NON_BLOCKING_INFRA_SIGNAL_STATES = new Set([
@@ -1056,6 +1415,7 @@ export function readWaveInfraGatePure(wave, agentResults, options = {}) {
1056
1415
  }
1057
1416
 
1058
1417
  export function buildGateSnapshotPure({ wave, agentResults, derivedState, validationMode = "live", laneConfig = {} }) {
1418
+ const designGate = readWaveDesignGatePure(wave, agentResults);
1059
1419
  const implementationGate = readWaveImplementationGatePure(wave, agentResults, {
1060
1420
  contQaAgentId: laneConfig.contQaAgentId, contEvalAgentId: laneConfig.contEvalAgentId,
1061
1421
  integrationAgentId: laneConfig.integrationAgentId, documentationAgentId: laneConfig.documentationAgentId,
@@ -1107,7 +1467,7 @@ export function buildGateSnapshotPure({ wave, agentResults, derivedState, valida
1107
1467
  const helperAssignmentBarrier = derivedState?.helperAssignmentBarrier || { ok: true, statusCode: "pass", detail: "" };
1108
1468
  const dependencyBarrier = derivedState?.dependencyBarrier || { ok: true, statusCode: "pass", detail: "" };
1109
1469
  const orderedGates = [
1110
- ["implementationGate", implementationGate], ["componentGate", componentGate],
1470
+ ["designGate", designGate], ["implementationGate", implementationGate], ["componentGate", componentGate],
1111
1471
  ["helperAssignmentBarrier", helperAssignmentBarrier], ["dependencyBarrier", dependencyBarrier],
1112
1472
  ["contEvalGate", contEvalGate], ["securityGate", securityGate],
1113
1473
  ["integrationBarrier", integrationBarrier], ["documentationGate", documentationGate],
@@ -1116,7 +1476,7 @@ export function buildGateSnapshotPure({ wave, agentResults, derivedState, valida
1116
1476
  ];
1117
1477
  const firstFailure = orderedGates.find(([, gate]) => gate?.ok === false);
1118
1478
  return {
1119
- implementationGate, componentGate, integrationGate: integrationMarkerGate,
1479
+ designGate, implementationGate, componentGate, integrationGate: integrationMarkerGate,
1120
1480
  integrationBarrier, documentationGate, componentMatrixGate,
1121
1481
  contEvalGate, securityGate, contQaGate, infraGate,
1122
1482
  clarificationBarrier, helperAssignmentBarrier, dependencyBarrier,