@chllming/wave-orchestration 0.5.4 → 0.6.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 (126) hide show
  1. package/CHANGELOG.md +52 -3
  2. package/README.md +33 -5
  3. package/docs/README.md +18 -4
  4. package/docs/agents/wave-cont-eval-role.md +36 -0
  5. package/docs/agents/{wave-evaluator-role.md → wave-cont-qa-role.md} +14 -11
  6. package/docs/agents/wave-documentation-role.md +1 -1
  7. package/docs/agents/wave-infra-role.md +1 -1
  8. package/docs/agents/wave-integration-role.md +3 -3
  9. package/docs/agents/wave-launcher-role.md +4 -3
  10. package/docs/agents/wave-security-role.md +40 -0
  11. package/docs/concepts/context7-vs-skills.md +1 -1
  12. package/docs/concepts/what-is-a-wave.md +56 -6
  13. package/docs/evals/README.md +166 -0
  14. package/docs/evals/benchmark-catalog.json +663 -0
  15. package/docs/guides/author-and-run-waves.md +135 -0
  16. package/docs/guides/planner.md +5 -0
  17. package/docs/guides/terminal-surfaces.md +2 -0
  18. package/docs/plans/component-cutover-matrix.json +1 -1
  19. package/docs/plans/component-cutover-matrix.md +1 -1
  20. package/docs/plans/current-state.md +19 -1
  21. package/docs/plans/examples/wave-example-live-proof.md +435 -0
  22. package/docs/plans/migration.md +42 -0
  23. package/docs/plans/wave-orchestrator.md +46 -7
  24. package/docs/plans/waves/wave-0.md +4 -4
  25. package/docs/reference/live-proof-waves.md +177 -0
  26. package/docs/reference/migration-0.2-to-0.5.md +26 -19
  27. package/docs/reference/npmjs-trusted-publishing.md +6 -5
  28. package/docs/reference/runtime-config/README.md +14 -4
  29. package/docs/reference/sample-waves.md +87 -0
  30. package/docs/reference/skills.md +110 -42
  31. package/docs/research/agent-context-sources.md +130 -11
  32. package/docs/research/coordination-failure-review.md +266 -0
  33. package/docs/roadmap.md +6 -2
  34. package/package.json +2 -2
  35. package/releases/manifest.json +35 -2
  36. package/scripts/research/agent-context-archive.mjs +83 -1
  37. package/scripts/research/manifests/agent-context-expanded-2026-03-22.mjs +811 -0
  38. package/scripts/wave-orchestrator/adhoc.mjs +1331 -0
  39. package/scripts/wave-orchestrator/agent-state.mjs +358 -6
  40. package/scripts/wave-orchestrator/artifact-schemas.mjs +173 -0
  41. package/scripts/wave-orchestrator/clarification-triage.mjs +10 -3
  42. package/scripts/wave-orchestrator/config.mjs +48 -12
  43. package/scripts/wave-orchestrator/context7.mjs +2 -0
  44. package/scripts/wave-orchestrator/coord-cli.mjs +51 -19
  45. package/scripts/wave-orchestrator/coordination-store.mjs +26 -4
  46. package/scripts/wave-orchestrator/coordination.mjs +83 -9
  47. package/scripts/wave-orchestrator/dashboard-state.mjs +20 -8
  48. package/scripts/wave-orchestrator/dep-cli.mjs +5 -2
  49. package/scripts/wave-orchestrator/docs-queue.mjs +8 -2
  50. package/scripts/wave-orchestrator/evals.mjs +451 -0
  51. package/scripts/wave-orchestrator/feedback.mjs +15 -1
  52. package/scripts/wave-orchestrator/install.mjs +32 -9
  53. package/scripts/wave-orchestrator/launcher-closure.mjs +281 -0
  54. package/scripts/wave-orchestrator/launcher-runtime.mjs +334 -0
  55. package/scripts/wave-orchestrator/launcher.mjs +709 -601
  56. package/scripts/wave-orchestrator/ledger.mjs +123 -20
  57. package/scripts/wave-orchestrator/local-executor.mjs +99 -12
  58. package/scripts/wave-orchestrator/planner.mjs +177 -42
  59. package/scripts/wave-orchestrator/replay.mjs +6 -3
  60. package/scripts/wave-orchestrator/role-helpers.mjs +84 -0
  61. package/scripts/wave-orchestrator/shared.mjs +75 -11
  62. package/scripts/wave-orchestrator/skills.mjs +637 -106
  63. package/scripts/wave-orchestrator/traces.mjs +71 -48
  64. package/scripts/wave-orchestrator/wave-files.mjs +947 -101
  65. package/scripts/wave.mjs +9 -0
  66. package/skills/README.md +202 -0
  67. package/skills/provider-aws/SKILL.md +111 -0
  68. package/skills/provider-aws/adapters/claude.md +1 -0
  69. package/skills/provider-aws/adapters/codex.md +1 -0
  70. package/skills/provider-aws/references/service-verification.md +39 -0
  71. package/skills/provider-aws/skill.json +50 -1
  72. package/skills/provider-custom-deploy/SKILL.md +59 -0
  73. package/skills/provider-custom-deploy/skill.json +46 -1
  74. package/skills/provider-docker-compose/SKILL.md +90 -0
  75. package/skills/provider-docker-compose/adapters/local.md +1 -0
  76. package/skills/provider-docker-compose/skill.json +49 -1
  77. package/skills/provider-github-release/SKILL.md +116 -1
  78. package/skills/provider-github-release/adapters/claude.md +1 -0
  79. package/skills/provider-github-release/adapters/codex.md +1 -0
  80. package/skills/provider-github-release/skill.json +51 -1
  81. package/skills/provider-kubernetes/SKILL.md +137 -0
  82. package/skills/provider-kubernetes/adapters/claude.md +1 -0
  83. package/skills/provider-kubernetes/adapters/codex.md +1 -0
  84. package/skills/provider-kubernetes/references/kubectl-patterns.md +58 -0
  85. package/skills/provider-kubernetes/skill.json +48 -1
  86. package/skills/provider-railway/SKILL.md +118 -1
  87. package/skills/provider-railway/references/verification-commands.md +39 -0
  88. package/skills/provider-railway/skill.json +67 -1
  89. package/skills/provider-ssh-manual/SKILL.md +91 -0
  90. package/skills/provider-ssh-manual/skill.json +50 -1
  91. package/skills/repo-coding-rules/SKILL.md +84 -0
  92. package/skills/repo-coding-rules/skill.json +30 -1
  93. package/skills/role-cont-eval/SKILL.md +90 -0
  94. package/skills/role-cont-eval/adapters/codex.md +1 -0
  95. package/skills/role-cont-eval/skill.json +36 -0
  96. package/skills/role-cont-qa/SKILL.md +93 -0
  97. package/skills/role-cont-qa/adapters/claude.md +1 -0
  98. package/skills/role-cont-qa/skill.json +36 -0
  99. package/skills/role-deploy/SKILL.md +90 -0
  100. package/skills/role-deploy/skill.json +32 -1
  101. package/skills/role-documentation/SKILL.md +66 -0
  102. package/skills/role-documentation/skill.json +32 -1
  103. package/skills/role-implementation/SKILL.md +62 -0
  104. package/skills/role-implementation/skill.json +32 -1
  105. package/skills/role-infra/SKILL.md +74 -0
  106. package/skills/role-infra/skill.json +32 -1
  107. package/skills/role-integration/SKILL.md +79 -1
  108. package/skills/role-integration/skill.json +32 -1
  109. package/skills/role-research/SKILL.md +58 -0
  110. package/skills/role-research/skill.json +32 -1
  111. package/skills/role-security/SKILL.md +60 -0
  112. package/skills/role-security/skill.json +36 -0
  113. package/skills/runtime-claude/SKILL.md +60 -1
  114. package/skills/runtime-claude/skill.json +32 -1
  115. package/skills/runtime-codex/SKILL.md +52 -1
  116. package/skills/runtime-codex/skill.json +32 -1
  117. package/skills/runtime-local/SKILL.md +39 -0
  118. package/skills/runtime-local/skill.json +32 -1
  119. package/skills/runtime-opencode/SKILL.md +51 -0
  120. package/skills/runtime-opencode/skill.json +32 -1
  121. package/skills/wave-core/SKILL.md +107 -0
  122. package/skills/wave-core/references/marker-syntax.md +62 -0
  123. package/skills/wave-core/skill.json +31 -1
  124. package/wave.config.json +35 -6
  125. package/skills/role-evaluator/SKILL.md +0 -6
  126. package/skills/role-evaluator/skill.json +0 -5
@@ -11,7 +11,6 @@ import {
11
11
  } from "./config.mjs";
12
12
  import {
13
13
  appendOrchestratorBoardEntry,
14
- buildExecutionPrompt,
15
14
  ensureOrchestratorBoard,
16
15
  feedbackStateSignature,
17
16
  readWaveHumanFeedbackRequests,
@@ -34,7 +33,6 @@ import {
34
33
  describeContext7Libraries,
35
34
  hashAgentPromptFingerprint,
36
35
  loadContext7BundleIndex,
37
- prefetchContext7ForSelection,
38
36
  } from "./context7.mjs";
39
37
  import {
40
38
  buildGlobalDashboardState,
@@ -58,7 +56,6 @@ import {
58
56
  DEFAULT_AGENT_RATE_LIMIT_RETRIES,
59
57
  DEFAULT_MAX_RETRIES_PER_WAVE,
60
58
  DEFAULT_TIMEOUT_MINUTES,
61
- DEFAULT_WAIT_PROGRESS_INTERVAL_MS,
62
59
  DEFAULT_WAVE_LANE,
63
60
  compactSingleLine,
64
61
  parseVerdictFromText,
@@ -71,6 +68,7 @@ import {
71
68
  readFileTail,
72
69
  readJsonOrNull,
73
70
  REPORT_VERDICT_REGEX,
71
+ sanitizeAdhocRunId,
74
72
  sanitizeOrchestratorId,
75
73
  shellQuote,
76
74
  sleep,
@@ -96,11 +94,12 @@ import {
96
94
  } from "./terminals.mjs";
97
95
  import {
98
96
  buildCodexExecInvocation,
99
- buildExecutorLaunchSpec,
100
97
  commandForExecutor,
101
98
  isExecutorCommandAvailable,
102
99
  } from "./executors.mjs";
103
100
  import {
101
+ agentRequiresProofCentricValidation,
102
+ buildRunStateEvidence,
104
103
  buildManifest,
105
104
  applyExecutorSelectionsToWave,
106
105
  markWaveCompleted,
@@ -111,16 +110,19 @@ import {
111
110
  validateWaveComponentMatrixCurrentLevels,
112
111
  validateWaveComponentPromotions,
113
112
  validateWaveDefinition,
113
+ waveRequiresProofCentricValidation,
114
114
  writeManifest,
115
115
  } from "./wave-files.mjs";
116
116
  import {
117
117
  agentSummaryPathFromStatusPath,
118
118
  buildAgentExecutionSummary,
119
119
  readAgentExecutionSummary,
120
+ validateContEvalSummary,
121
+ validateContQaSummary,
120
122
  validateDocumentationClosureSummary,
121
- validateEvaluatorSummary,
122
123
  validateIntegrationSummary,
123
124
  validateImplementationSummary,
125
+ validateSecuritySummary,
124
126
  writeAgentExecutionSummary,
125
127
  } from "./agent-state.mjs";
126
128
  import { buildDocsQueue, readDocsQueue, writeDocsQueue } from "./docs-queue.mjs";
@@ -129,9 +131,13 @@ import { buildQualityMetrics, writeTraceBundle } from "./traces.mjs";
129
131
  import { triageClarificationRequests } from "./clarification-triage.mjs";
130
132
  import { readProjectProfile, resolveDefaultTerminalSurface } from "./project-profile.mjs";
131
133
  import {
132
- resolveAgentSkills,
134
+ isContEvalImplementationOwningAgent,
135
+ isContEvalReportOnlyAgent,
136
+ isSecurityReviewAgent,
137
+ resolveSecurityReviewReportPath,
138
+ } from "./role-helpers.mjs";
139
+ import {
133
140
  summarizeResolvedSkills,
134
- writeResolvedSkillArtifacts,
135
141
  } from "./skills.mjs";
136
142
  import {
137
143
  buildDependencySnapshot,
@@ -140,6 +146,22 @@ import {
140
146
  syncAssignmentRecords,
141
147
  writeDependencySnapshotMarkdown,
142
148
  } from "./routing-state.mjs";
149
+ import {
150
+ readRelaunchPlan,
151
+ writeAssignmentSnapshot,
152
+ writeDependencySnapshot,
153
+ writeRelaunchPlan,
154
+ } from "./artifact-schemas.mjs";
155
+ import {
156
+ collectUnexpectedSessionFailures as collectUnexpectedSessionFailuresImpl,
157
+ launchAgentSession as launchAgentSessionImpl,
158
+ refreshResolvedSkillsForRun,
159
+ waitForWaveCompletion as waitForWaveCompletionImpl,
160
+ } from "./launcher-runtime.mjs";
161
+ import {
162
+ readWaveInfraGate as readWaveInfraGateImpl,
163
+ runClosureSweepPhase as runClosureSweepPhaseImpl,
164
+ } from "./launcher-closure.mjs";
143
165
  export { CODEX_SANDBOX_MODES, DEFAULT_CODEX_SANDBOX_MODE, normalizeCodexSandboxMode, buildCodexExecInvocation };
144
166
 
145
167
  export function formatReconcileBlockedWaveLine(blockedWave) {
@@ -228,6 +250,7 @@ function parseArgs(argv) {
228
250
  orchestratorId: null,
229
251
  orchestratorBoardPath: null,
230
252
  coordinationNote: "",
253
+ adhocRunId: null,
231
254
  };
232
255
  let stateFileProvided = false;
233
256
  let manifestOutProvided = false;
@@ -265,7 +288,14 @@ function parseArgs(argv) {
265
288
  orchestratorBoardProvided = true;
266
289
  } else if (arg === "--lane") {
267
290
  options.lane = String(argv[++i] || "").trim();
268
- lanePaths = buildLanePaths(options.lane);
291
+ lanePaths = buildLanePaths(options.lane, {
292
+ adhocRunId: options.adhocRunId,
293
+ });
294
+ } else if (arg === "--adhoc-run") {
295
+ options.adhocRunId = sanitizeAdhocRunId(argv[++i]);
296
+ lanePaths = buildLanePaths(options.lane, {
297
+ adhocRunId: options.adhocRunId,
298
+ });
269
299
  } else if (arg === "--orchestrator-id") {
270
300
  options.orchestratorId = sanitizeOrchestratorId(argv[++i]);
271
301
  } else if (arg === "--orchestrator-board") {
@@ -313,6 +343,7 @@ function parseArgs(argv) {
313
343
 
314
344
  lanePaths = buildLanePaths(options.lane, {
315
345
  runVariant: options.dryRun ? "dry-run" : undefined,
346
+ adhocRunId: options.adhocRunId,
316
347
  });
317
348
  if (!stateFileProvided) {
318
349
  options.runStatePath = lanePaths.defaultRunStatePath;
@@ -353,68 +384,118 @@ function isProcessAlive(pid) {
353
384
  }
354
385
  }
355
386
 
356
- export function readWaveEvaluatorGate(wave, agentRuns, options = {}) {
357
- const evaluatorAgentId = options.evaluatorAgentId || wave.evaluatorAgentId || "A0";
358
- const evaluatorRun =
359
- agentRuns.find((run) => run.agent.agentId === evaluatorAgentId) ?? null;
360
- if (!evaluatorRun) {
387
+ export function readWaveContQaGate(wave, agentRuns, options = {}) {
388
+ const mode = String(options.mode || "compat").trim().toLowerCase();
389
+ const strict = mode === "live";
390
+ const contQaAgentId = options.contQaAgentId || wave.contQaAgentId || "A0";
391
+ const contQaRun =
392
+ agentRuns.find((run) => run.agent.agentId === contQaAgentId) ?? null;
393
+ if (!contQaRun) {
361
394
  return {
362
395
  ok: false,
363
- agentId: evaluatorAgentId,
364
- statusCode: "missing-evaluator",
365
- detail: `Agent ${evaluatorAgentId} is missing.`,
396
+ agentId: contQaAgentId,
397
+ statusCode: "missing-cont-qa",
398
+ detail: `Agent ${contQaAgentId} is missing.`,
366
399
  logPath: null,
367
400
  };
368
401
  }
369
- const summary = readRunExecutionSummary(evaluatorRun);
402
+ const summary = readRunExecutionSummary(contQaRun, strict ? wave : null);
370
403
  if (summary) {
371
- const validation = validateEvaluatorSummary(evaluatorRun.agent, summary);
404
+ const validation = validateContQaSummary(contQaRun.agent, summary, { mode });
372
405
  return {
373
406
  ok: validation.ok,
374
- agentId: evaluatorRun.agent.agentId,
407
+ agentId: contQaRun.agent.agentId,
375
408
  statusCode: validation.statusCode,
376
409
  detail: validation.detail,
377
- logPath: summary.logPath || path.relative(REPO_ROOT, evaluatorRun.logPath),
410
+ logPath: summary.logPath || path.relative(REPO_ROOT, contQaRun.logPath),
378
411
  };
379
412
  }
380
- const evaluatorReportPath = wave.evaluatorReportPath
381
- ? path.resolve(REPO_ROOT, wave.evaluatorReportPath)
413
+ if (strict) {
414
+ return {
415
+ ok: false,
416
+ agentId: contQaRun.agent.agentId,
417
+ statusCode: "missing-wave-gate",
418
+ detail: `Missing structured cont-QA summary for ${contQaRun.agent.agentId}.`,
419
+ logPath: path.relative(REPO_ROOT, contQaRun.logPath),
420
+ };
421
+ }
422
+ const contQaReportPath = wave.contQaReportPath
423
+ ? path.resolve(REPO_ROOT, wave.contQaReportPath)
382
424
  : null;
383
425
  const reportText =
384
- evaluatorReportPath && fs.existsSync(evaluatorReportPath)
385
- ? fs.readFileSync(evaluatorReportPath, "utf8")
426
+ contQaReportPath && fs.existsSync(contQaReportPath)
427
+ ? fs.readFileSync(contQaReportPath, "utf8")
386
428
  : "";
387
429
  const reportVerdict = parseVerdictFromText(reportText, REPORT_VERDICT_REGEX);
388
430
  if (reportVerdict.verdict) {
389
431
  return {
390
432
  ok: reportVerdict.verdict === "pass",
391
- agentId: evaluatorRun.agent.agentId,
392
- statusCode: reportVerdict.verdict === "pass" ? "pass" : `evaluator-${reportVerdict.verdict}`,
393
- detail: reportVerdict.detail || "Verdict read from evaluator report.",
394
- logPath: path.relative(REPO_ROOT, evaluatorRun.logPath),
433
+ agentId: contQaRun.agent.agentId,
434
+ statusCode: reportVerdict.verdict === "pass" ? "pass" : `cont-qa-${reportVerdict.verdict}`,
435
+ detail: reportVerdict.detail || "Verdict read from cont-QA report.",
436
+ logPath: path.relative(REPO_ROOT, contQaRun.logPath),
395
437
  };
396
438
  }
397
439
  const logVerdict = parseVerdictFromText(
398
- readFileTail(evaluatorRun.logPath, 30000),
440
+ readFileTail(contQaRun.logPath, 30000),
399
441
  WAVE_VERDICT_REGEX,
400
442
  );
401
443
  if (logVerdict.verdict) {
402
444
  return {
403
445
  ok: logVerdict.verdict === "pass",
404
- agentId: evaluatorRun.agent.agentId,
405
- statusCode: logVerdict.verdict === "pass" ? "pass" : `evaluator-${logVerdict.verdict}`,
406
- detail: logVerdict.detail || "Verdict read from evaluator log marker.",
407
- logPath: path.relative(REPO_ROOT, evaluatorRun.logPath),
446
+ agentId: contQaRun.agent.agentId,
447
+ statusCode: logVerdict.verdict === "pass" ? "pass" : `cont-qa-${logVerdict.verdict}`,
448
+ detail: logVerdict.detail || "Verdict read from cont-QA log marker.",
449
+ logPath: path.relative(REPO_ROOT, contQaRun.logPath),
408
450
  };
409
451
  }
410
452
  return {
411
453
  ok: false,
412
- agentId: evaluatorRun.agent.agentId,
413
- statusCode: "missing-evaluator-verdict",
414
- detail: evaluatorReportPath
415
- ? `Missing Verdict line in ${path.relative(REPO_ROOT, evaluatorReportPath)} and no [wave-verdict] marker in ${path.relative(REPO_ROOT, evaluatorRun.logPath)}.`
416
- : `Missing evaluator report path and no [wave-verdict] marker in ${path.relative(REPO_ROOT, evaluatorRun.logPath)}.`,
417
- logPath: path.relative(REPO_ROOT, evaluatorRun.logPath),
454
+ agentId: contQaRun.agent.agentId,
455
+ statusCode: "missing-cont-qa-verdict",
456
+ detail: contQaReportPath
457
+ ? `Missing Verdict line in ${path.relative(REPO_ROOT, contQaReportPath)} and no [wave-verdict] marker in ${path.relative(REPO_ROOT, contQaRun.logPath)}.`
458
+ : `Missing cont-QA report path and no [wave-verdict] marker in ${path.relative(REPO_ROOT, contQaRun.logPath)}.`,
459
+ logPath: path.relative(REPO_ROOT, contQaRun.logPath),
460
+ };
461
+ }
462
+
463
+ export function readWaveContEvalGate(wave, agentRuns, options = {}) {
464
+ const mode = String(options.mode || "compat").trim().toLowerCase();
465
+ const strict = mode === "live";
466
+ const contEvalAgentId = options.contEvalAgentId || wave.contEvalAgentId || "E0";
467
+ const contEvalRun =
468
+ agentRuns.find((run) => run.agent.agentId === contEvalAgentId) ?? null;
469
+ if (!contEvalRun) {
470
+ return {
471
+ ok: true,
472
+ agentId: null,
473
+ statusCode: "pass",
474
+ detail: "Wave does not include cont-EVAL.",
475
+ logPath: null,
476
+ };
477
+ }
478
+ const summary = readRunExecutionSummary(contEvalRun, strict ? wave : null);
479
+ if (summary) {
480
+ const validation = validateContEvalSummary(contEvalRun.agent, summary, {
481
+ mode,
482
+ evalTargets: options.evalTargets || wave.evalTargets,
483
+ benchmarkCatalogPath: options.benchmarkCatalogPath,
484
+ });
485
+ return {
486
+ ok: validation.ok,
487
+ agentId: contEvalRun.agent.agentId,
488
+ statusCode: validation.statusCode,
489
+ detail: validation.detail,
490
+ logPath: summary.logPath || path.relative(REPO_ROOT, contEvalRun.logPath),
491
+ };
492
+ }
493
+ return {
494
+ ok: false,
495
+ agentId: contEvalRun.agent.agentId,
496
+ statusCode: "missing-wave-eval",
497
+ detail: `Missing [wave-eval] marker for ${contEvalRun.agent.agentId}.`,
498
+ logPath: path.relative(REPO_ROOT, contEvalRun.logPath),
418
499
  };
419
500
  }
420
501
 
@@ -423,10 +504,19 @@ function materializeAgentExecutionSummaryForRun(wave, runInfo) {
423
504
  if (!statusRecord) {
424
505
  return null;
425
506
  }
426
- const reportPath =
427
- runInfo.agent.agentId === (wave.evaluatorAgentId || "A0") && wave.evaluatorReportPath
428
- ? path.resolve(REPO_ROOT, wave.evaluatorReportPath)
429
- : null;
507
+ const reportPath = (() => {
508
+ if (runInfo.agent.agentId === (wave.contQaAgentId || "A0") && wave.contQaReportPath) {
509
+ return path.resolve(REPO_ROOT, wave.contQaReportPath);
510
+ }
511
+ if (runInfo.agent.agentId === (wave.contEvalAgentId || "E0") && wave.contEvalReportPath) {
512
+ return path.resolve(REPO_ROOT, wave.contEvalReportPath);
513
+ }
514
+ if (isSecurityReviewAgent(runInfo.agent)) {
515
+ const securityReportPath = resolveSecurityReviewReportPath(runInfo.agent);
516
+ return securityReportPath ? path.resolve(REPO_ROOT, securityReportPath) : null;
517
+ }
518
+ return null;
519
+ })();
430
520
  const summary = buildAgentExecutionSummary({
431
521
  agent: runInfo.agent,
432
522
  statusRecord,
@@ -437,7 +527,7 @@ function materializeAgentExecutionSummaryForRun(wave, runInfo) {
437
527
  return summary;
438
528
  }
439
529
 
440
- function readRunExecutionSummary(runInfo) {
530
+ function readRunExecutionSummary(runInfo, wave = null) {
441
531
  if (runInfo?.summary && typeof runInfo.summary === "object") {
442
532
  return runInfo.summary;
443
533
  }
@@ -447,9 +537,19 @@ function readRunExecutionSummary(runInfo) {
447
537
  if (runInfo?.statusPath && fs.existsSync(agentSummaryPathFromStatusPath(runInfo.statusPath))) {
448
538
  return readAgentExecutionSummary(runInfo.statusPath);
449
539
  }
540
+ if (wave && runInfo?.statusPath && runInfo?.logPath && fs.existsSync(runInfo.statusPath)) {
541
+ return materializeAgentExecutionSummaryForRun(wave, runInfo);
542
+ }
450
543
  return null;
451
544
  }
452
545
 
546
+ export function readWaveEvaluatorGate(wave, agentRuns, options = {}) {
547
+ return readWaveContQaGate(wave, agentRuns, {
548
+ ...options,
549
+ contQaAgentId: options.evaluatorAgentId || options.contQaAgentId,
550
+ });
551
+ }
552
+
453
553
  function materializeAgentExecutionSummaries(wave, agentRuns) {
454
554
  return Object.fromEntries(
455
555
  agentRuns.map((runInfo) => [runInfo.agent.agentId, materializeAgentExecutionSummaryForRun(wave, runInfo)]),
@@ -492,6 +592,37 @@ function waveIntegrationMarkdownPath(lanePaths, waveNumber) {
492
592
  return path.join(lanePaths.integrationDir, `wave-${waveNumber}.md`);
493
593
  }
494
594
 
595
+ function waveRelaunchPlanPath(lanePaths, waveNumber) {
596
+ return path.join(lanePaths.statusDir, `relaunch-plan-wave-${waveNumber}.json`);
597
+ }
598
+
599
+ function readWaveRelaunchPlan(lanePaths, waveNumber) {
600
+ return readRelaunchPlan(waveRelaunchPlanPath(lanePaths, waveNumber), { wave: waveNumber });
601
+ }
602
+
603
+ function writeWaveRelaunchPlan(lanePaths, waveNumber, payload) {
604
+ const filePath = waveRelaunchPlanPath(lanePaths, waveNumber);
605
+ writeRelaunchPlan(filePath, payload, { wave: waveNumber });
606
+ return filePath;
607
+ }
608
+
609
+ function clearWaveRelaunchPlan(lanePaths, waveNumber) {
610
+ const filePath = waveRelaunchPlanPath(lanePaths, waveNumber);
611
+ try {
612
+ fs.rmSync(filePath, { force: true });
613
+ } catch {
614
+ // no-op
615
+ }
616
+ }
617
+
618
+ function waveSecurityPath(lanePaths, waveNumber) {
619
+ return path.join(lanePaths.securityDir, `wave-${waveNumber}.json`);
620
+ }
621
+
622
+ function waveSecurityMarkdownPath(lanePaths, waveNumber) {
623
+ return path.join(lanePaths.securityDir, `wave-${waveNumber}.md`);
624
+ }
625
+
495
626
  function uniqueStringEntries(values) {
496
627
  return Array.from(
497
628
  new Set(
@@ -607,6 +738,116 @@ function inferIntegrationRecommendation(evidence) {
607
738
  };
608
739
  }
609
740
 
741
+ export function buildWaveSecuritySummary({
742
+ lanePaths,
743
+ wave,
744
+ attempt,
745
+ summariesByAgentId = {},
746
+ }) {
747
+ const createdAt = toIsoTimestamp();
748
+ const securityAgents = (wave.agents || []).filter((agent) => isSecurityReviewAgent(agent));
749
+ if (securityAgents.length === 0) {
750
+ return {
751
+ wave: wave.wave,
752
+ lane: lanePaths.lane,
753
+ attempt,
754
+ overallState: "not-applicable",
755
+ totalFindings: 0,
756
+ totalApprovals: 0,
757
+ concernAgentIds: [],
758
+ blockedAgentIds: [],
759
+ detail: "No security reviewer declared for this wave.",
760
+ agents: [],
761
+ createdAt,
762
+ updatedAt: createdAt,
763
+ };
764
+ }
765
+ const agents = securityAgents.map((agent) => {
766
+ const summary = summariesByAgentId?.[agent.agentId] || null;
767
+ const validation = validateSecuritySummary(agent, summary);
768
+ const explicitState = summary?.security?.state || null;
769
+ return {
770
+ agentId: agent.agentId,
771
+ title: agent.title || agent.agentId,
772
+ state: validation.ok
773
+ ? explicitState || "clear"
774
+ : explicitState === "blocked"
775
+ ? "blocked"
776
+ : "pending",
777
+ findings: summary?.security?.findings || 0,
778
+ approvals: summary?.security?.approvals || 0,
779
+ detail: validation.ok
780
+ ? summary?.security?.detail || validation.detail || ""
781
+ : validation.detail,
782
+ reportPath: summary?.reportPath || resolveSecurityReviewReportPath(agent) || null,
783
+ statusCode: validation.statusCode,
784
+ ok: validation.ok,
785
+ };
786
+ });
787
+ const blockedAgentIds = agents
788
+ .filter((entry) => entry.state === "blocked")
789
+ .map((entry) => entry.agentId);
790
+ const concernAgentIds = agents
791
+ .filter((entry) => entry.state === "concerns")
792
+ .map((entry) => entry.agentId);
793
+ const pendingAgentIds = agents
794
+ .filter((entry) => entry.state === "pending")
795
+ .map((entry) => entry.agentId);
796
+ const overallState =
797
+ blockedAgentIds.length > 0
798
+ ? "blocked"
799
+ : pendingAgentIds.length > 0
800
+ ? "pending"
801
+ : concernAgentIds.length > 0
802
+ ? "concerns"
803
+ : "clear";
804
+ const totalFindings = agents.reduce((sum, entry) => sum + (entry.findings || 0), 0);
805
+ const totalApprovals = agents.reduce((sum, entry) => sum + (entry.approvals || 0), 0);
806
+ const detail =
807
+ overallState === "blocked"
808
+ ? `Security review blocked by ${blockedAgentIds.join(", ")}.`
809
+ : overallState === "pending"
810
+ ? `Security review output is incomplete for ${pendingAgentIds.join(", ")}.`
811
+ : overallState === "concerns"
812
+ ? `Security review reported advisory concerns from ${concernAgentIds.join(", ")}.`
813
+ : "Security review is clear.";
814
+ return {
815
+ wave: wave.wave,
816
+ lane: lanePaths.lane,
817
+ attempt,
818
+ overallState,
819
+ totalFindings,
820
+ totalApprovals,
821
+ concernAgentIds,
822
+ blockedAgentIds,
823
+ detail,
824
+ agents,
825
+ createdAt,
826
+ updatedAt: createdAt,
827
+ };
828
+ }
829
+
830
+ function renderWaveSecuritySummaryMarkdown(securitySummary) {
831
+ return [
832
+ `# Wave ${securitySummary.wave} Security Summary`,
833
+ "",
834
+ `- State: ${securitySummary.overallState || "unknown"}`,
835
+ `- Detail: ${securitySummary.detail || "n/a"}`,
836
+ `- Total findings: ${securitySummary.totalFindings || 0}`,
837
+ `- Total approvals: ${securitySummary.totalApprovals || 0}`,
838
+ `- Reviewers: ${(securitySummary.agents || []).length}`,
839
+ "",
840
+ "## Reviews",
841
+ ...((securitySummary.agents || []).length > 0
842
+ ? securitySummary.agents.map(
843
+ (entry) =>
844
+ `- ${entry.agentId}: state=${entry.state || "unknown"} findings=${entry.findings || 0} approvals=${entry.approvals || 0}${entry.reportPath ? ` report=${entry.reportPath}` : ""}${entry.detail ? ` detail=${entry.detail}` : ""}`,
845
+ )
846
+ : ["- None."]),
847
+ "",
848
+ ].join("\n");
849
+ }
850
+
610
851
  function padReportedEntries(entries, minimumCount, label) {
611
852
  const padded = [...entries];
612
853
  for (let index = padded.length + 1; index <= minimumCount; index += 1) {
@@ -624,6 +865,7 @@ function buildIntegrationEvidence({
624
865
  agentRuns,
625
866
  dependencySnapshot = null,
626
867
  capabilityAssignments = [],
868
+ securitySummary = null,
627
869
  }) {
628
870
  const openClaims = (coordinationState?.claims || [])
629
871
  .filter((record) => isOpenCoordinationStatus(record.status))
@@ -677,14 +919,37 @@ function buildIntegrationEvidence({
677
919
  ? docsQueue.items.map((item) => summarizeDocsQueueItem(item))
678
920
  : [];
679
921
  const deployRiskEntries = [];
922
+ const securityFindingEntries = [];
923
+ const securityApprovalEntries = [];
680
924
  for (const agent of wave.agents || []) {
681
925
  const summary = summariesByAgentId?.[agent.agentId] || null;
926
+ const contEvalImplementationOwning =
927
+ agent.agentId === lanePaths.contEvalAgentId &&
928
+ isContEvalImplementationOwningAgent(agent, {
929
+ contEvalAgentId: lanePaths.contEvalAgentId,
930
+ });
931
+ if (isSecurityReviewAgent(agent)) {
932
+ continue;
933
+ }
934
+ if (agent.agentId === lanePaths.contEvalAgentId) {
935
+ const validation = validateContEvalSummary(agent, summary, {
936
+ mode: "live",
937
+ evalTargets: wave.evalTargets,
938
+ benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
939
+ });
940
+ if (!validation.ok) {
941
+ proofGapEntries.push(
942
+ summarizeGap(agent.agentId, validation.detail, "cont-EVAL target is not yet satisfied."),
943
+ );
944
+ }
945
+ }
682
946
  if (
683
947
  ![
684
- lanePaths.evaluatorAgentId,
948
+ lanePaths.contQaAgentId,
685
949
  lanePaths.integrationAgentId,
686
950
  lanePaths.documentationAgentId,
687
- ].includes(agent.agentId)
951
+ ].includes(agent.agentId) &&
952
+ (agent.agentId !== lanePaths.contEvalAgentId || contEvalImplementationOwning)
688
953
  ) {
689
954
  const validation = validateImplementationSummary(agent, summary);
690
955
  if (!validation.ok) {
@@ -754,6 +1019,29 @@ function buildIntegrationEvidence({
754
1019
  `${assignment.requestId}: ${assignment.target}${assignment.assignedAgentId ? ` -> ${assignment.assignedAgentId}` : " -> unresolved"} (${assignment.assignmentReason || "n/a"})`,
755
1020
  );
756
1021
 
1022
+ for (const review of securitySummary?.agents || []) {
1023
+ if (review.state === "blocked" || review.state === "concerns") {
1024
+ securityFindingEntries.push(
1025
+ summarizeGap(
1026
+ review.agentId,
1027
+ review.detail,
1028
+ review.state === "blocked"
1029
+ ? "Security review blocked the wave."
1030
+ : "Security review reported advisory concerns.",
1031
+ ),
1032
+ );
1033
+ }
1034
+ if ((review.approvals || 0) > 0) {
1035
+ securityApprovalEntries.push(
1036
+ summarizeGap(
1037
+ review.agentId,
1038
+ review.detail,
1039
+ `${review.approvals} security approval(s) remain open.`,
1040
+ ),
1041
+ );
1042
+ }
1043
+ }
1044
+
757
1045
  return {
758
1046
  openClaims: uniqueStringEntries(openClaims),
759
1047
  conflictingClaims: uniqueStringEntries(conflictingClaims),
@@ -766,6 +1054,9 @@ function buildIntegrationEvidence({
766
1054
  inboundDependencies: uniqueStringEntries(inboundDependencies),
767
1055
  outboundDependencies: uniqueStringEntries(outboundDependencies),
768
1056
  helperAssignments: uniqueStringEntries(helperAssignments),
1057
+ securityState: securitySummary?.overallState || "not-applicable",
1058
+ securityFindings: uniqueStringEntries(securityFindingEntries),
1059
+ securityApprovals: uniqueStringEntries(securityApprovalEntries),
769
1060
  };
770
1061
  }
771
1062
 
@@ -780,6 +1071,7 @@ export function buildWaveIntegrationSummary({
780
1071
  agentRuns,
781
1072
  capabilityAssignments = [],
782
1073
  dependencySnapshot = null,
1074
+ securitySummary = null,
783
1075
  }) {
784
1076
  const explicitIntegration = summariesByAgentId[lanePaths.integrationAgentId]?.integration || null;
785
1077
  const evidence = buildIntegrationEvidence({
@@ -791,6 +1083,7 @@ export function buildWaveIntegrationSummary({
791
1083
  agentRuns,
792
1084
  capabilityAssignments,
793
1085
  dependencySnapshot,
1086
+ securitySummary,
794
1087
  });
795
1088
  if (explicitIntegration) {
796
1089
  return {
@@ -818,6 +1111,9 @@ export function buildWaveIntegrationSummary({
818
1111
  proofGaps: evidence.proofGaps,
819
1112
  docGaps: evidence.docGaps,
820
1113
  deployRisks: evidence.deployRisks,
1114
+ securityState: evidence.securityState,
1115
+ securityFindings: evidence.securityFindings,
1116
+ securityApprovals: evidence.securityApprovals,
821
1117
  inboundDependencies: evidence.inboundDependencies,
822
1118
  outboundDependencies: evidence.outboundDependencies,
823
1119
  helperAssignments: evidence.helperAssignments,
@@ -865,6 +1161,9 @@ function renderIntegrationSummaryMarkdown(integrationSummary) {
865
1161
  `- Proof gaps: ${(integrationSummary.proofGaps || []).length}`,
866
1162
  `- Deploy risks: ${(integrationSummary.deployRisks || []).length}`,
867
1163
  `- Documentation gaps: ${(integrationSummary.docGaps || []).length}`,
1164
+ `- Security review: ${integrationSummary.securityState || "not-applicable"}`,
1165
+ `- Security findings: ${(integrationSummary.securityFindings || []).length}`,
1166
+ `- Security approvals: ${(integrationSummary.securityApprovals || []).length}`,
868
1167
  `- Inbound dependencies: ${(integrationSummary.inboundDependencies || []).length}`,
869
1168
  `- Outbound dependencies: ${(integrationSummary.outboundDependencies || []).length}`,
870
1169
  `- Helper assignments: ${(integrationSummary.helperAssignments || []).length}`,
@@ -879,6 +1178,8 @@ function renderIntegrationSummaryMarkdown(integrationSummary) {
879
1178
  ),
880
1179
  ...renderIntegrationSection("## Proof Gaps", integrationSummary.proofGaps),
881
1180
  ...renderIntegrationSection("## Deploy Risks", integrationSummary.deployRisks),
1181
+ ...renderIntegrationSection("## Security Findings", integrationSummary.securityFindings),
1182
+ ...renderIntegrationSection("## Security Approvals", integrationSummary.securityApprovals),
882
1183
  ...renderIntegrationSection("## Inbound Dependencies", integrationSummary.inboundDependencies),
883
1184
  ...renderIntegrationSection("## Outbound Dependencies", integrationSummary.outboundDependencies),
884
1185
  ...renderIntegrationSection("## Helper Assignments", integrationSummary.helperAssignments),
@@ -913,7 +1214,8 @@ function writeWaveDerivedState({
913
1214
  agents: wave.agents,
914
1215
  componentPromotions: wave.componentPromotions,
915
1216
  sharedPlanDocs: lanePaths.sharedPlanDocs,
916
- evaluatorAgentId: lanePaths.evaluatorAgentId,
1217
+ contQaAgentId: lanePaths.contQaAgentId,
1218
+ contEvalAgentId: lanePaths.contEvalAgentId,
917
1219
  integrationAgentId: lanePaths.integrationAgentId,
918
1220
  documentationAgentId: lanePaths.documentationAgentId,
919
1221
  feedbackRequests,
@@ -956,8 +1258,14 @@ function writeWaveDerivedState({
956
1258
  ledger: existingLedger,
957
1259
  capabilityRouting: lanePaths.capabilityRouting,
958
1260
  });
959
- writeJsonArtifact(waveAssignmentsPath(lanePaths, wave.wave), capabilityAssignments);
960
- writeJsonArtifact(waveDependencySnapshotPath(lanePaths, wave.wave), dependencySnapshot);
1261
+ writeAssignmentSnapshot(waveAssignmentsPath(lanePaths, wave.wave), capabilityAssignments, {
1262
+ lane: lanePaths.lane,
1263
+ wave: wave.wave,
1264
+ });
1265
+ writeDependencySnapshot(waveDependencySnapshotPath(lanePaths, wave.wave), dependencySnapshot, {
1266
+ lane: lanePaths.lane,
1267
+ wave: wave.wave,
1268
+ });
961
1269
  writeDependencySnapshotMarkdown(
962
1270
  waveDependencySnapshotMarkdownPath(lanePaths, wave.wave),
963
1271
  dependencySnapshot,
@@ -969,6 +1277,8 @@ function writeWaveDerivedState({
969
1277
  executorId: agent.executorResolved?.id || null,
970
1278
  profile: agent.executorResolved?.profile || null,
971
1279
  selectedBy: agent.executorResolved?.selectedBy || null,
1280
+ retryPolicy: agent.executorResolved?.retryPolicy || null,
1281
+ allowFallbackOnRetry: agent.executorResolved?.allowFallbackOnRetry !== false,
972
1282
  fallbacks: agent.executorResolved?.fallbacks || [],
973
1283
  fallbackUsed: agent.executorResolved?.fallbackUsed === true,
974
1284
  fallbackReason: agent.executorResolved?.fallbackReason || null,
@@ -983,6 +1293,17 @@ function writeWaveDerivedState({
983
1293
  runtimeAssignments,
984
1294
  });
985
1295
  writeDocsQueue(waveDocsQueuePath(lanePaths, wave.wave), docsQueue);
1296
+ const securitySummary = buildWaveSecuritySummary({
1297
+ lanePaths,
1298
+ wave,
1299
+ attempt,
1300
+ summariesByAgentId,
1301
+ });
1302
+ writeJsonArtifact(waveSecurityPath(lanePaths, wave.wave), securitySummary);
1303
+ writeTextAtomic(
1304
+ waveSecurityMarkdownPath(lanePaths, wave.wave),
1305
+ `${renderWaveSecuritySummaryMarkdown(securitySummary)}\n`,
1306
+ );
986
1307
  const integrationSummary = buildWaveIntegrationSummary({
987
1308
  lanePaths,
988
1309
  wave,
@@ -994,6 +1315,7 @@ function writeWaveDerivedState({
994
1315
  agentRuns,
995
1316
  capabilityAssignments,
996
1317
  dependencySnapshot,
1318
+ securitySummary,
997
1319
  });
998
1320
  writeJsonArtifact(waveIntegrationPath(lanePaths, wave.wave), integrationSummary);
999
1321
  writeTextAtomic(
@@ -1008,9 +1330,11 @@ function writeWaveDerivedState({
1008
1330
  integrationSummary,
1009
1331
  docsQueue,
1010
1332
  attempt,
1011
- evaluatorAgentId: lanePaths.evaluatorAgentId,
1333
+ contQaAgentId: lanePaths.contQaAgentId,
1334
+ contEvalAgentId: lanePaths.contEvalAgentId,
1012
1335
  integrationAgentId: lanePaths.integrationAgentId,
1013
1336
  documentationAgentId: lanePaths.documentationAgentId,
1337
+ benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
1014
1338
  capabilityAssignments,
1015
1339
  dependencySnapshot,
1016
1340
  });
@@ -1067,8 +1391,10 @@ function writeWaveDerivedState({
1067
1391
  docsQueue,
1068
1392
  capabilityAssignments,
1069
1393
  dependencySnapshot,
1394
+ securitySummary,
1070
1395
  integrationSummary,
1071
1396
  integrationMarkdownPath: waveIntegrationMarkdownPath(lanePaths, wave.wave),
1397
+ securityMarkdownPath: waveSecurityMarkdownPath(lanePaths, wave.wave),
1072
1398
  ledger,
1073
1399
  sharedSummaryPath,
1074
1400
  sharedSummaryText: sharedSummary.text,
@@ -1090,14 +1416,19 @@ function applyDerivedStateToDashboard(dashboardState, derivedState) {
1090
1416
  }
1091
1417
 
1092
1418
  export function readWaveImplementationGate(wave, agentRuns) {
1093
- const evaluatorAgentId = wave.evaluatorAgentId || "A0";
1419
+ const contQaAgentId = wave.contQaAgentId || "A0";
1420
+ const contEvalAgentId = wave.contEvalAgentId || "E0";
1094
1421
  const integrationAgentId = wave.integrationAgentId || "A8";
1095
1422
  const documentationAgentId = wave.documentationAgentId || "A9";
1096
1423
  for (const runInfo of agentRuns) {
1097
- if ([evaluatorAgentId, integrationAgentId, documentationAgentId].includes(runInfo.agent.agentId)) {
1424
+ if (
1425
+ [contQaAgentId, integrationAgentId, documentationAgentId].includes(runInfo.agent.agentId) ||
1426
+ isContEvalReportOnlyAgent(runInfo.agent, { contEvalAgentId }) ||
1427
+ isSecurityReviewAgent(runInfo.agent)
1428
+ ) {
1098
1429
  continue;
1099
1430
  }
1100
- const summary = readRunExecutionSummary(runInfo);
1431
+ const summary = readRunExecutionSummary(runInfo, wave);
1101
1432
  const validation = validateImplementationSummary(runInfo.agent, summary);
1102
1433
  if (!validation.ok) {
1103
1434
  return {
@@ -1120,7 +1451,7 @@ export function readWaveImplementationGate(wave, agentRuns) {
1120
1451
 
1121
1452
  export function readWaveComponentGate(wave, agentRuns, options = {}) {
1122
1453
  const summariesByAgentId = Object.fromEntries(
1123
- agentRuns.map((runInfo) => [runInfo.agent.agentId, readRunExecutionSummary(runInfo)]),
1454
+ agentRuns.map((runInfo) => [runInfo.agent.agentId, readRunExecutionSummary(runInfo, wave)]),
1124
1455
  );
1125
1456
  const validation = validateWaveComponentPromotions(wave, summariesByAgentId, options);
1126
1457
  if (validation.ok) {
@@ -1184,7 +1515,7 @@ export function readWaveDocumentationGate(wave, agentRuns) {
1184
1515
  logPath: null,
1185
1516
  };
1186
1517
  }
1187
- const summary = readRunExecutionSummary(docRun);
1518
+ const summary = readRunExecutionSummary(docRun, wave);
1188
1519
  const validation = validateDocumentationClosureSummary(docRun.agent, summary);
1189
1520
  return {
1190
1521
  ok: validation.ok,
@@ -1195,6 +1526,52 @@ export function readWaveDocumentationGate(wave, agentRuns) {
1195
1526
  };
1196
1527
  }
1197
1528
 
1529
+ export function readWaveSecurityGate(wave, agentRuns) {
1530
+ const securityRuns = (agentRuns || []).filter((run) => isSecurityReviewAgent(run.agent));
1531
+ if (securityRuns.length === 0) {
1532
+ return {
1533
+ ok: true,
1534
+ agentId: null,
1535
+ statusCode: "pass",
1536
+ detail: "No security reviewer declared for this wave.",
1537
+ logPath: null,
1538
+ };
1539
+ }
1540
+ const concernAgentIds = [];
1541
+ for (const runInfo of securityRuns) {
1542
+ const summary = readRunExecutionSummary(runInfo, wave);
1543
+ const validation = validateSecuritySummary(runInfo.agent, summary);
1544
+ if (!validation.ok) {
1545
+ return {
1546
+ ok: false,
1547
+ agentId: runInfo.agent.agentId,
1548
+ statusCode: validation.statusCode,
1549
+ detail: validation.detail,
1550
+ logPath: summary?.logPath || path.relative(REPO_ROOT, runInfo.logPath),
1551
+ };
1552
+ }
1553
+ if (summary?.security?.state === "concerns") {
1554
+ concernAgentIds.push(runInfo.agent.agentId);
1555
+ }
1556
+ }
1557
+ if (concernAgentIds.length > 0) {
1558
+ return {
1559
+ ok: true,
1560
+ agentId: null,
1561
+ statusCode: "security-concerns",
1562
+ detail: `Security review reported advisory concerns (${concernAgentIds.join(", ")}).`,
1563
+ logPath: null,
1564
+ };
1565
+ }
1566
+ return {
1567
+ ok: true,
1568
+ agentId: null,
1569
+ statusCode: "pass",
1570
+ detail: "Security review is clear.",
1571
+ logPath: null,
1572
+ };
1573
+ }
1574
+
1198
1575
  export function readWaveIntegrationGate(wave, agentRuns, options = {}) {
1199
1576
  const integrationAgentId =
1200
1577
  options.integrationAgentId || wave.integrationAgentId || "A8";
@@ -1216,7 +1593,7 @@ export function readWaveIntegrationGate(wave, agentRuns, options = {}) {
1216
1593
  logPath: null,
1217
1594
  };
1218
1595
  }
1219
- const summary = readRunExecutionSummary(integrationRun);
1596
+ const summary = readRunExecutionSummary(integrationRun, wave);
1220
1597
  const validation = validateIntegrationSummary(integrationRun.agent, summary);
1221
1598
  return {
1222
1599
  ok: validation.ok,
@@ -1256,45 +1633,6 @@ export function readWaveIntegrationBarrier(wave, agentRuns, derivedState, option
1256
1633
  return markerGate;
1257
1634
  }
1258
1635
 
1259
- function failureResultFromGate(gate, fallbackLogPath) {
1260
- return {
1261
- failures: [
1262
- {
1263
- agentId: gate.agentId,
1264
- statusCode: gate.statusCode,
1265
- logPath: gate.logPath || fallbackLogPath,
1266
- detail: gate.detail,
1267
- },
1268
- ],
1269
- timedOut: false,
1270
- };
1271
- }
1272
-
1273
- function recordClosureGateFailure({
1274
- wave,
1275
- lanePaths,
1276
- gate,
1277
- label,
1278
- recordCombinedEvent,
1279
- appendCoordination,
1280
- actionRequested,
1281
- }) {
1282
- recordCombinedEvent({
1283
- level: "error",
1284
- agentId: gate.agentId,
1285
- message: `${label} blocked wave ${wave.wave}: ${gate.detail}`,
1286
- });
1287
- appendCoordination({
1288
- event: "wave_gate_blocked",
1289
- waves: [wave.wave],
1290
- status: "blocked",
1291
- details: `agent=${gate.agentId}; reason=${gate.statusCode}; ${gate.detail}`,
1292
- actionRequested:
1293
- actionRequested ||
1294
- `Lane ${lanePaths.lane} owners should resolve the ${label.toLowerCase()} before wave progression.`,
1295
- });
1296
- }
1297
-
1298
1636
  export async function runClosureSweepPhase({
1299
1637
  lanePaths,
1300
1638
  wave,
@@ -1310,180 +1648,33 @@ export async function runClosureSweepPhase({
1310
1648
  launchAgentSessionFn = launchAgentSession,
1311
1649
  waitForWaveCompletionFn = waitForWaveCompletion,
1312
1650
  }) {
1313
- const evaluatorAgentId = wave.evaluatorAgentId || "A0";
1314
- const integrationAgentId = wave.integrationAgentId || lanePaths.integrationAgentId || "A8";
1315
- const documentationAgentId = wave.documentationAgentId || "A9";
1316
- const stagedRuns = [
1317
- {
1318
- agentId: integrationAgentId,
1319
- label: "Integration gate",
1320
- runs: closureRuns.filter((run) => run.agent.agentId === integrationAgentId),
1321
- validate: () =>
1322
- readWaveIntegrationBarrier(wave, closureRuns, refreshDerivedState?.(dashboardState?.attempt || 0), {
1323
- integrationAgentId: lanePaths.integrationAgentId,
1324
- requireIntegrationStewardFromWave: lanePaths.requireIntegrationStewardFromWave,
1325
- }),
1326
- actionRequested:
1327
- `Lane ${lanePaths.lane} owners should resolve integration contradictions or blockers before doc/evaluator closure.`,
1328
- },
1329
- {
1330
- agentId: documentationAgentId,
1331
- label: "Documentation closure",
1332
- runs: closureRuns.filter((run) => run.agent.agentId === documentationAgentId),
1333
- validate: () => {
1334
- const documentationGate = readWaveDocumentationGate(wave, closureRuns);
1335
- if (!documentationGate.ok) {
1336
- return documentationGate;
1337
- }
1338
- return readWaveComponentMatrixGate(wave, closureRuns, {
1339
- laneProfile: lanePaths.laneProfile,
1340
- documentationAgentId: lanePaths.documentationAgentId,
1341
- });
1342
- },
1343
- actionRequested:
1344
- `Lane ${lanePaths.lane} owners should resolve the shared-plan or component-matrix closure state before evaluator progression.`,
1345
- },
1346
- {
1347
- agentId: evaluatorAgentId,
1348
- label: "Evaluator gate",
1349
- runs: closureRuns.filter((run) => run.agent.agentId === evaluatorAgentId),
1350
- validate: () => readWaveEvaluatorGate(wave, closureRuns),
1351
- actionRequested:
1352
- `Lane ${lanePaths.lane} owners should resolve the evaluator gate before wave progression.`,
1353
- },
1354
- ];
1355
- for (const stage of stagedRuns) {
1356
- if (stage.runs.length === 0) {
1357
- continue;
1358
- }
1359
- const runInfo = stage.runs[0];
1360
- const existing = dashboardState.agents.find((entry) => entry.agentId === runInfo.agent.agentId);
1361
- setWaveDashboardAgent(dashboardState, runInfo.agent.agentId, {
1362
- state: "launching",
1363
- attempts: (existing?.attempts || 0) + 1,
1364
- startedAt: existing?.startedAt || toIsoTimestamp(),
1365
- completedAt: null,
1366
- exitCode: null,
1367
- detail: "Launching closure sweep",
1368
- });
1369
- flushDashboards();
1370
- const launchResult = await launchAgentSessionFn(lanePaths, {
1371
- wave: wave.wave,
1372
- waveDefinition: wave,
1373
- agent: runInfo.agent,
1374
- sessionName: runInfo.sessionName,
1375
- promptPath: runInfo.promptPath,
1376
- logPath: runInfo.logPath,
1377
- statusPath: runInfo.statusPath,
1378
- messageBoardPath: runInfo.messageBoardPath,
1379
- messageBoardSnapshot: runInfo.messageBoardSnapshot || "",
1380
- sharedSummaryPath: runInfo.sharedSummaryPath,
1381
- sharedSummaryText: runInfo.sharedSummaryText,
1382
- inboxPath: runInfo.inboxPath,
1383
- inboxText: runInfo.inboxText,
1384
- orchestratorId: options.orchestratorId,
1385
- executorMode: options.executorMode,
1386
- codexSandboxMode: options.codexSandboxMode,
1387
- agentRateLimitRetries: options.agentRateLimitRetries,
1388
- agentRateLimitBaseDelaySeconds: options.agentRateLimitBaseDelaySeconds,
1389
- agentRateLimitMaxDelaySeconds: options.agentRateLimitMaxDelaySeconds,
1390
- context7Enabled: options.context7Enabled,
1391
- });
1392
- runInfo.lastLaunchAttempt = dashboardState?.attempt || null;
1393
- runInfo.lastPromptHash = launchResult?.promptHash || null;
1394
- runInfo.lastContext7 = launchResult?.context7 || null;
1395
- runInfo.lastExecutorId = launchResult?.executorId || runInfo.agent.executorResolved?.id || null;
1396
- runInfo.lastSkillProjection = launchResult?.skills || summarizeResolvedSkills(runInfo.agent.skillsResolved);
1397
- setWaveDashboardAgent(dashboardState, runInfo.agent.agentId, {
1398
- state: "running",
1399
- detail: `Closure sweep launched${launchResult?.context7?.mode ? ` (${launchResult.context7.mode})` : ""}`,
1400
- });
1401
- recordCombinedEvent({
1402
- agentId: runInfo.agent.agentId,
1403
- message: `Closure sweep launched in tmux session ${runInfo.sessionName}`,
1404
- });
1405
- flushDashboards();
1406
- const result = await waitForWaveCompletionFn(
1407
- lanePaths,
1408
- [runInfo],
1409
- options.timeoutMinutes,
1410
- ({ pendingAgentIds }) => {
1411
- refreshWaveDashboardAgentStates(dashboardState, [runInfo], pendingAgentIds, (event) =>
1412
- recordCombinedEvent(event),
1413
- );
1414
- monitorWaveHumanFeedback({
1415
- lanePaths,
1416
- waveNumber: wave.wave,
1417
- agentRuns: [runInfo],
1418
- orchestratorId: options.orchestratorId,
1419
- coordinationLogPath,
1420
- feedbackStateByRequestId,
1421
- recordCombinedEvent,
1422
- appendCoordination,
1423
- });
1424
- updateWaveDashboardMessageBoard(dashboardState, runInfo.messageBoardPath);
1425
- flushDashboards();
1426
- },
1427
- );
1428
- materializeAgentExecutionSummaryForRun(wave, runInfo);
1429
- refreshDerivedState?.(dashboardState?.attempt || 0);
1430
- if (result.failures.length > 0) {
1431
- return result;
1432
- }
1433
- const gate = stage.validate();
1434
- if (!gate.ok) {
1435
- recordClosureGateFailure({
1436
- wave,
1437
- lanePaths,
1438
- gate,
1439
- label: stage.label,
1440
- recordCombinedEvent,
1441
- appendCoordination,
1442
- actionRequested: stage.actionRequested,
1443
- });
1444
- return failureResultFromGate(gate, path.relative(REPO_ROOT, runInfo.logPath));
1445
- }
1446
- }
1447
- return { failures: [], timedOut: false };
1651
+ return runClosureSweepPhaseImpl({
1652
+ lanePaths,
1653
+ wave,
1654
+ closureRuns,
1655
+ coordinationLogPath,
1656
+ refreshDerivedState,
1657
+ dashboardState,
1658
+ recordCombinedEvent,
1659
+ flushDashboards,
1660
+ options,
1661
+ feedbackStateByRequestId,
1662
+ appendCoordination,
1663
+ launchAgentSessionFn,
1664
+ waitForWaveCompletionFn,
1665
+ readWaveContEvalGateFn: readWaveContEvalGate,
1666
+ readWaveSecurityGateFn: readWaveSecurityGate,
1667
+ readWaveIntegrationBarrierFn: readWaveIntegrationBarrier,
1668
+ readWaveDocumentationGateFn: readWaveDocumentationGate,
1669
+ readWaveComponentMatrixGateFn: readWaveComponentMatrixGate,
1670
+ readWaveContQaGateFn: readWaveContQaGate,
1671
+ materializeAgentExecutionSummaryForRunFn: materializeAgentExecutionSummaryForRun,
1672
+ monitorWaveHumanFeedbackFn: monitorWaveHumanFeedback,
1673
+ });
1448
1674
  }
1449
1675
 
1450
- const NON_BLOCKING_INFRA_SIGNAL_STATES = new Set([
1451
- "conformant",
1452
- "setup-required",
1453
- "setup-in-progress",
1454
- "action-required",
1455
- "action-approved",
1456
- "action-complete",
1457
- ]);
1458
-
1459
1676
  export function readWaveInfraGate(agentRuns) {
1460
- for (const run of agentRuns) {
1461
- const signals = parseStructuredSignalsFromLog(run.logPath);
1462
- if (!signals?.infra) {
1463
- continue;
1464
- }
1465
- const infra = signals.infra;
1466
- const normalizedState = String(infra.state || "")
1467
- .trim()
1468
- .toLowerCase();
1469
- if (NON_BLOCKING_INFRA_SIGNAL_STATES.has(normalizedState)) {
1470
- continue;
1471
- }
1472
- return {
1473
- ok: false,
1474
- agentId: run.agent.agentId,
1475
- statusCode: `infra-${normalizedState || "blocked"}`,
1476
- detail: `Infra signal ${infra.kind || "unknown"} on ${infra.target || "unknown"} ended in state ${normalizedState || "unknown"}${infra.detail ? ` (${infra.detail})` : ""}.`,
1477
- logPath: path.relative(REPO_ROOT, run.logPath),
1478
- };
1479
- }
1480
- return {
1481
- ok: true,
1482
- agentId: null,
1483
- statusCode: "pass",
1484
- detail: "",
1485
- logPath: null,
1486
- };
1677
+ return readWaveInfraGateImpl(agentRuns);
1487
1678
  }
1488
1679
 
1489
1680
  export function markLauncherFailed(
@@ -1739,23 +1930,9 @@ function cleanupLaneTmuxSessions(lanePaths, { excludeSessionNames = new Set() }
1739
1930
  }
1740
1931
 
1741
1932
  export function collectUnexpectedSessionFailures(lanePaths, agentRuns, pendingAgentIds) {
1742
- const activeSessionNames = new Set(listLaneTmuxSessionNames(lanePaths));
1743
- const failures = [];
1744
- for (const run of agentRuns) {
1745
- if (!pendingAgentIds.has(run.agent.agentId) || fs.existsSync(run.statusPath)) {
1746
- continue;
1747
- }
1748
- if (activeSessionNames.has(run.sessionName)) {
1749
- continue;
1750
- }
1751
- failures.push({
1752
- agentId: run.agent.agentId,
1753
- statusCode: "session-missing",
1754
- logPath: path.relative(REPO_ROOT, run.logPath),
1755
- detail: `tmux session ${run.sessionName} disappeared before ${path.relative(REPO_ROOT, run.statusPath)} was written.`,
1756
- });
1757
- }
1758
- return failures;
1933
+ return collectUnexpectedSessionFailuresImpl(lanePaths, agentRuns, pendingAgentIds, {
1934
+ listLaneTmuxSessionNamesFn: listLaneTmuxSessionNames,
1935
+ });
1759
1936
  }
1760
1937
 
1761
1938
  function launchWaveDashboardSession(lanePaths, { sessionName, dashboardPath, messageBoardPath }) {
@@ -1777,281 +1954,14 @@ function launchWaveDashboardSession(lanePaths, { sessionName, dashboardPath, mes
1777
1954
  );
1778
1955
  }
1779
1956
 
1780
- function refreshResolvedSkillsForRun(runInfo, waveDefinition, lanePaths) {
1781
- runInfo.agent.skillsResolved = resolveAgentSkills(
1782
- runInfo.agent,
1783
- waveDefinition || { deployEnvironments: [] },
1784
- { laneProfile: lanePaths.laneProfile },
1785
- );
1786
- return runInfo.agent.skillsResolved;
1787
- }
1788
-
1789
1957
  async function launchAgentSession(lanePaths, params) {
1790
- const {
1791
- wave,
1792
- waveDefinition = null,
1793
- agent,
1794
- sessionName,
1795
- promptPath,
1796
- logPath,
1797
- statusPath,
1798
- messageBoardPath,
1799
- messageBoardSnapshot,
1800
- sharedSummaryPath,
1801
- sharedSummaryText,
1802
- inboxPath,
1803
- inboxText,
1804
- orchestratorId,
1805
- agentRateLimitRetries,
1806
- agentRateLimitBaseDelaySeconds,
1807
- agentRateLimitMaxDelaySeconds,
1808
- context7Enabled,
1809
- dryRun = false,
1810
- } = params;
1811
- ensureDirectory(path.dirname(promptPath));
1812
- ensureDirectory(path.dirname(logPath));
1813
- ensureDirectory(path.dirname(statusPath));
1814
- fs.rmSync(statusPath, { force: true });
1815
-
1816
- const context7 = await prefetchContext7ForSelection(agent.context7Resolved, {
1817
- cacheDir: lanePaths.context7CacheDir,
1818
- disabled: !context7Enabled,
1819
- });
1820
- const overlayDir = path.join(lanePaths.executorOverlaysDir, `wave-${wave}`, agent.slug);
1821
- ensureDirectory(overlayDir);
1822
- const skillsResolved =
1823
- agent.skillsResolved || resolveAgentSkills(agent, waveDefinition || { deployEnvironments: [] }, {
1824
- laneProfile: lanePaths.laneProfile,
1825
- });
1826
- agent.skillsResolved = skillsResolved;
1827
- const skillArtifacts = writeResolvedSkillArtifacts(overlayDir, skillsResolved);
1828
- if (skillArtifacts) {
1829
- agent.skillsResolved = {
1830
- ...skillsResolved,
1831
- artifacts: skillArtifacts,
1832
- };
1833
- }
1834
- const prompt = buildExecutionPrompt({
1835
- lane: lanePaths.lane,
1836
- wave,
1837
- agent,
1838
- orchestratorId,
1839
- messageBoardPath,
1840
- messageBoardSnapshot,
1841
- sharedSummaryPath,
1842
- sharedSummaryText,
1843
- inboxPath,
1844
- inboxText,
1845
- context7,
1846
- componentPromotions: wave.componentPromotions,
1847
- sharedPlanDocs: lanePaths.sharedPlanDocs,
1848
- evaluatorAgentId: lanePaths.evaluatorAgentId,
1849
- integrationAgentId: lanePaths.integrationAgentId,
1850
- documentationAgentId: lanePaths.documentationAgentId,
1851
- });
1852
- const promptHash = hashAgentPromptFingerprint(agent);
1853
- fs.writeFileSync(promptPath, `${prompt}\n`, "utf8");
1854
- const launchSpec = buildExecutorLaunchSpec({
1855
- agent,
1856
- promptPath,
1857
- logPath,
1858
- overlayDir,
1859
- skillProjection: agent.skillsResolved,
1860
- });
1861
- const resolvedExecutorMode = launchSpec.executorId || agent.executorResolved?.id || "codex";
1862
- if (dryRun) {
1863
- writeJsonAtomic(path.join(overlayDir, "launch-preview.json"), {
1864
- executorId: resolvedExecutorMode,
1865
- command: launchSpec.command,
1866
- env: launchSpec.env || {},
1867
- useRateLimitRetries: launchSpec.useRateLimitRetries === true,
1868
- invocationLines: launchSpec.invocationLines,
1869
- skills: summarizeResolvedSkills(agent.skillsResolved),
1870
- });
1871
- return {
1872
- promptHash,
1873
- context7,
1874
- executorId: resolvedExecutorMode,
1875
- launchSpec,
1876
- dryRun: true,
1877
- skills: summarizeResolvedSkills(agent.skillsResolved),
1878
- };
1879
- }
1880
- killTmuxSessionIfExists(lanePaths.tmuxSocketName, sessionName);
1881
-
1882
- const executionLines = [];
1883
- if (launchSpec.env) {
1884
- for (const [key, value] of Object.entries(launchSpec.env)) {
1885
- executionLines.push(`export ${key}=${shellQuote(value)}`);
1886
- }
1887
- }
1888
- if (!launchSpec.useRateLimitRetries) {
1889
- executionLines.push(...launchSpec.invocationLines);
1890
- executionLines.push("status=$?");
1891
- } else {
1892
- executionLines.push(`: > ${shellQuote(logPath)}`);
1893
- executionLines.push(
1894
- `max_rate_attempts=${Math.max(1, Number.parseInt(String(agentRateLimitRetries || 0), 10) + 1)}`,
1895
- );
1896
- executionLines.push(
1897
- `rate_delay_base=${Math.max(1, Number.parseInt(String(agentRateLimitBaseDelaySeconds || DEFAULT_AGENT_RATE_LIMIT_BASE_DELAY_SECONDS), 10))}`,
1898
- );
1899
- executionLines.push(
1900
- `rate_delay_max=${Math.max(1, Number.parseInt(String(agentRateLimitMaxDelaySeconds || DEFAULT_AGENT_RATE_LIMIT_MAX_DELAY_SECONDS), 10))}`,
1901
- );
1902
- executionLines.push("rate_attempt=1");
1903
- executionLines.push("status=1");
1904
- executionLines.push('while [ "$rate_attempt" -le "$max_rate_attempts" ]; do');
1905
- for (const line of launchSpec.invocationLines) {
1906
- executionLines.push(` ${line}`);
1907
- }
1908
- executionLines.push(" status=$?");
1909
- executionLines.push(' if [ "$status" -eq 0 ]; then');
1910
- executionLines.push(" break");
1911
- executionLines.push(" fi");
1912
- executionLines.push(' if [ "$rate_attempt" -ge "$max_rate_attempts" ]; then');
1913
- executionLines.push(" break");
1914
- executionLines.push(" fi");
1915
- executionLines.push(
1916
- ` if tail -n 120 ${shellQuote(logPath)} | grep -Eqi '429 Too Many Requests|exceeded retry limit|last status: 429|rate limit'; then`,
1917
- );
1918
- executionLines.push(" sleep_seconds=$((rate_delay_base * (2 ** (rate_attempt - 1))))");
1919
- executionLines.push(
1920
- ' if [ "$sleep_seconds" -gt "$rate_delay_max" ]; then sleep_seconds=$rate_delay_max; fi',
1921
- );
1922
- executionLines.push(" jitter=$((RANDOM % 5))");
1923
- executionLines.push(" sleep_seconds=$((sleep_seconds + jitter))");
1924
- executionLines.push(
1925
- ` echo "[${lanePaths.lane}-wave-launcher] rate-limit detected for ${agent.agentId}; retry \${rate_attempt}/\${max_rate_attempts} after \${sleep_seconds}s" | tee -a ${shellQuote(logPath)}`,
1926
- );
1927
- executionLines.push(' sleep "$sleep_seconds"');
1928
- executionLines.push(" rate_attempt=$((rate_attempt + 1))");
1929
- executionLines.push(" continue");
1930
- executionLines.push(" fi");
1931
- executionLines.push(" break");
1932
- executionLines.push("done");
1933
- }
1934
-
1935
- const command = [
1936
- `cd ${shellQuote(REPO_ROOT)}`,
1937
- "set -o pipefail",
1938
- `export WAVE_ORCHESTRATOR_ID=${shellQuote(orchestratorId || "")}`,
1939
- `export WAVE_EXECUTOR_MODE=${shellQuote(resolvedExecutorMode)}`,
1940
- ...executionLines,
1941
- `node -e ${shellQuote(
1942
- "const fs=require('node:fs'); const statusPath=process.argv[1]; const payload={code:Number(process.argv[2]),promptHash:process.argv[3]||null,orchestratorId:process.argv[4]||null,completedAt:new Date().toISOString()}; fs.writeFileSync(statusPath, JSON.stringify(payload, null, 2)+'\\n', 'utf8');",
1943
- )} ${shellQuote(statusPath)} "$status" ${shellQuote(promptHash)} ${shellQuote(orchestratorId || "")}`,
1944
- `echo "[${lanePaths.lane}-wave-launcher] ${sessionName} finished with code $status"`,
1945
- "exec bash -l",
1946
- ].join("\n");
1947
-
1948
- runTmux(
1949
- lanePaths,
1950
- ["new-session", "-d", "-s", sessionName, `bash -lc ${shellQuote(command)}`],
1951
- `launch session ${sessionName}`,
1952
- );
1953
- return {
1954
- promptHash,
1955
- context7,
1956
- executorId: resolvedExecutorMode,
1957
- skills: summarizeResolvedSkills(agent.skillsResolved),
1958
- };
1958
+ return launchAgentSessionImpl(lanePaths, params, { runTmuxFn: runTmux });
1959
1959
  }
1960
1960
 
1961
1961
  async function waitForWaveCompletion(lanePaths, agentRuns, timeoutMinutes, onProgress = null) {
1962
- const defaultTimeoutMs = timeoutMinutes * 60 * 1000;
1963
- const startedAt = Date.now();
1964
- const timeoutAtByAgentId = new Map(
1965
- agentRuns.map((run) => {
1966
- const budgetMinutes = Number(run.agent.executorResolved?.budget?.minutes || 0);
1967
- const effectiveBudgetMs =
1968
- Number.isFinite(budgetMinutes) && budgetMinutes > 0
1969
- ? Math.min(defaultTimeoutMs, budgetMinutes * 60 * 1000)
1970
- : defaultTimeoutMs;
1971
- return [run.agent.agentId, startedAt + effectiveBudgetMs];
1972
- }),
1973
- );
1974
- const pending = new Set(agentRuns.map((run) => run.agent.agentId));
1975
- const timedOutAgentIds = new Set();
1976
- let sessionFailures = [];
1977
-
1978
- const refreshPending = () => {
1979
- for (const run of agentRuns) {
1980
- if (pending.has(run.agent.agentId) && fs.existsSync(run.statusPath)) {
1981
- pending.delete(run.agent.agentId);
1982
- }
1983
- }
1984
- };
1985
-
1986
- await new Promise((resolve) => {
1987
- const interval = setInterval(() => {
1988
- refreshPending();
1989
- onProgress?.({ pendingAgentIds: new Set(pending), timedOut: false });
1990
- if (pending.size === 0) {
1991
- clearInterval(interval);
1992
- resolve();
1993
- return;
1994
- }
1995
- sessionFailures = collectUnexpectedSessionFailures(lanePaths, agentRuns, pending);
1996
- if (sessionFailures.length > 0) {
1997
- onProgress?.({
1998
- pendingAgentIds: new Set(pending),
1999
- timedOut: false,
2000
- failures: sessionFailures,
2001
- });
2002
- clearInterval(interval);
2003
- resolve();
2004
- return;
2005
- }
2006
- const now = Date.now();
2007
- for (const run of agentRuns) {
2008
- if (!pending.has(run.agent.agentId)) {
2009
- continue;
2010
- }
2011
- const deadline = timeoutAtByAgentId.get(run.agent.agentId) || startedAt + defaultTimeoutMs;
2012
- if (now <= deadline) {
2013
- continue;
2014
- }
2015
- timedOutAgentIds.add(run.agent.agentId);
2016
- pending.delete(run.agent.agentId);
2017
- killTmuxSessionIfExists(lanePaths.tmuxSocketName, run.sessionName);
2018
- }
2019
- if (pending.size === 0) {
2020
- clearInterval(interval);
2021
- resolve();
2022
- }
2023
- }, DEFAULT_WAIT_PROGRESS_INTERVAL_MS);
2024
- refreshPending();
2025
- onProgress?.({ pendingAgentIds: new Set(pending), timedOut: false });
1962
+ return waitForWaveCompletionImpl(lanePaths, agentRuns, timeoutMinutes, onProgress, {
1963
+ collectUnexpectedSessionFailuresFn: collectUnexpectedSessionFailures,
2026
1964
  });
2027
-
2028
- if (sessionFailures.length > 0) {
2029
- onProgress?.({ pendingAgentIds: new Set(), timedOut: false, failures: sessionFailures });
2030
- return { failures: sessionFailures, timedOut: false };
2031
- }
2032
-
2033
- const failures = [];
2034
- for (const run of agentRuns) {
2035
- const code = readStatusCodeIfPresent(run.statusPath);
2036
- if (code === 0) {
2037
- continue;
2038
- }
2039
- if (code === null || timedOutAgentIds.has(run.agent.agentId)) {
2040
- failures.push({
2041
- agentId: run.agent.agentId,
2042
- statusCode: timedOutAgentIds.has(run.agent.agentId) ? "timeout-no-status" : "missing-status",
2043
- logPath: path.relative(REPO_ROOT, run.logPath),
2044
- });
2045
- continue;
2046
- }
2047
- failures.push({
2048
- agentId: run.agent.agentId,
2049
- statusCode: String(code),
2050
- logPath: path.relative(REPO_ROOT, run.logPath),
2051
- });
2052
- }
2053
- onProgress?.({ pendingAgentIds: new Set(), timedOut: timedOutAgentIds.size > 0 });
2054
- return { failures, timedOut: timedOutAgentIds.size > 0 };
2055
1965
  }
2056
1966
 
2057
1967
  function monitorWaveHumanFeedback({
@@ -2192,24 +2102,107 @@ function monitorWaveHumanFeedback({
2192
2102
  }
2193
2103
  }
2194
2104
 
2195
- export function hasReusableSuccessStatus(agent, statusPath) {
2105
+ function proofCentricReuseBlocked(derivedState) {
2106
+ if (!derivedState) {
2107
+ return false;
2108
+ }
2109
+ return (
2110
+ readClarificationBarrier(derivedState).ok === false ||
2111
+ readWaveAssignmentBarrier(derivedState).ok === false ||
2112
+ readWaveDependencyBarrier(derivedState).ok === false
2113
+ );
2114
+ }
2115
+
2116
+ function applyPersistedRelaunchPlan(agentRuns, persistedPlan, lanePaths, waveDefinition) {
2117
+ if (!persistedPlan || !Array.isArray(persistedPlan.selectedAgentIds)) {
2118
+ return [];
2119
+ }
2120
+ const runsByAgentId = new Map(agentRuns.map((run) => [run.agent.agentId, run]));
2121
+ for (const [agentId, executorState] of Object.entries(persistedPlan.executorStates || {})) {
2122
+ const run = runsByAgentId.get(agentId);
2123
+ if (!run || !executorState || typeof executorState !== "object") {
2124
+ continue;
2125
+ }
2126
+ run.agent.executorResolved = executorState;
2127
+ refreshResolvedSkillsForRun(run, waveDefinition, lanePaths);
2128
+ }
2129
+ return persistedPlan.selectedAgentIds
2130
+ .map((agentId) => runsByAgentId.get(agentId))
2131
+ .filter(Boolean);
2132
+ }
2133
+
2134
+ function relaunchReasonBuckets(runs, failures, derivedState) {
2135
+ const selectedAgentIds = new Set((runs || []).map((run) => run.agent.agentId));
2136
+ return {
2137
+ clarification: openClarificationLinkedRequests(derivedState?.coordinationState)
2138
+ .flatMap((record) => record.targets || [])
2139
+ .some((target) => {
2140
+ const agentId = String(target || "").startsWith("agent:")
2141
+ ? String(target).slice("agent:".length)
2142
+ : String(target || "");
2143
+ return selectedAgentIds.has(agentId);
2144
+ }),
2145
+ helperAssignment: (derivedState?.capabilityAssignments || []).some(
2146
+ (assignment) => assignment.blocking && selectedAgentIds.has(assignment.assignedAgentId),
2147
+ ),
2148
+ dependency: ((derivedState?.dependencySnapshot?.openInbound || []).some((record) =>
2149
+ selectedAgentIds.has(record.assignedAgentId),
2150
+ )),
2151
+ blocker: (derivedState?.coordinationState?.blockers || []).some(
2152
+ (record) =>
2153
+ isOpenCoordinationStatus(record.status) &&
2154
+ (selectedAgentIds.has(record.agentId) ||
2155
+ (record.targets || []).some((target) => {
2156
+ const agentId = String(target || "").startsWith("agent:")
2157
+ ? String(target).slice("agent:".length)
2158
+ : String(target || "");
2159
+ return selectedAgentIds.has(agentId);
2160
+ })),
2161
+ ),
2162
+ closureGate: (failures || []).some(
2163
+ (failure) => failure.agentId && selectedAgentIds.has(failure.agentId),
2164
+ ),
2165
+ };
2166
+ }
2167
+
2168
+ export function hasReusableSuccessStatus(agent, statusPath, options = {}) {
2196
2169
  const statusRecord = readStatusRecordIfPresent(statusPath);
2197
- return Boolean(
2170
+ const basicReuseOk = Boolean(
2198
2171
  statusRecord && statusRecord.code === 0 && statusRecord.promptHash === hashAgentPromptFingerprint(agent),
2199
2172
  );
2173
+ if (!basicReuseOk) {
2174
+ return false;
2175
+ }
2176
+ const proofCentric =
2177
+ agentRequiresProofCentricValidation(agent) || waveRequiresProofCentricValidation(options.wave);
2178
+ if (!proofCentric) {
2179
+ return true;
2180
+ }
2181
+ const summary = readAgentExecutionSummary(statusPath);
2182
+ if (!summary) {
2183
+ return false;
2184
+ }
2185
+ if (!validateImplementationSummary(agent, summary).ok) {
2186
+ return false;
2187
+ }
2188
+ if (proofCentricReuseBlocked(options.derivedState)) {
2189
+ return false;
2190
+ }
2191
+ return true;
2200
2192
  }
2201
2193
 
2202
- function isClosureAgentId(agentId, lanePaths) {
2194
+ function isClosureAgentId(agent, lanePaths) {
2203
2195
  return [
2204
- lanePaths.integrationAgentId,
2205
- lanePaths.documentationAgentId,
2206
- lanePaths.evaluatorAgentId,
2207
- ].includes(agentId);
2196
+ lanePaths.contEvalAgentId || "E0",
2197
+ lanePaths.integrationAgentId || "A8",
2198
+ lanePaths.documentationAgentId || "A9",
2199
+ lanePaths.contQaAgentId || "A0",
2200
+ ].includes(agent?.agentId) || isSecurityReviewAgent(agent);
2208
2201
  }
2209
2202
 
2210
2203
  export function selectInitialWaveRuns(agentRuns, lanePaths) {
2211
2204
  const implementationRuns = (agentRuns || []).filter(
2212
- (run) => !isClosureAgentId(run?.agent?.agentId, lanePaths),
2205
+ (run) => !isClosureAgentId(run?.agent, lanePaths),
2213
2206
  );
2214
2207
  return implementationRuns.length > 0 ? implementationRuns : agentRuns;
2215
2208
  }
@@ -2244,6 +2237,12 @@ function nextExecutorModel(executorState, executorId) {
2244
2237
  }
2245
2238
 
2246
2239
  function executorFallbackChain(executorState) {
2240
+ if (
2241
+ executorState?.retryPolicy === "sticky" ||
2242
+ executorState?.allowFallbackOnRetry === false
2243
+ ) {
2244
+ return [];
2245
+ }
2247
2246
  return Array.isArray(executorState?.fallbacks)
2248
2247
  ? executorState.fallbacks.filter(Boolean)
2249
2248
  : [];
@@ -2490,6 +2489,7 @@ export function buildGateSnapshot({
2490
2489
  lanePaths,
2491
2490
  componentMatrixPayload,
2492
2491
  componentMatrixJsonPath,
2492
+ validationMode = "compat",
2493
2493
  }) {
2494
2494
  const implementationGate = readWaveImplementationGate(wave, agentRuns);
2495
2495
  const componentGate = readWaveComponentGate(wave, agentRuns, {
@@ -2510,8 +2510,16 @@ export function buildGateSnapshot({
2510
2510
  componentMatrixPayload,
2511
2511
  componentMatrixJsonPath,
2512
2512
  });
2513
- const evaluatorGate = readWaveEvaluatorGate(wave, agentRuns, {
2514
- evaluatorAgentId: lanePaths?.evaluatorAgentId,
2513
+ const contEvalGate = readWaveContEvalGate(wave, agentRuns, {
2514
+ contEvalAgentId: lanePaths?.contEvalAgentId,
2515
+ mode: validationMode,
2516
+ evalTargets: wave.evalTargets,
2517
+ benchmarkCatalogPath: lanePaths?.laneProfile?.paths?.benchmarkCatalogPath,
2518
+ });
2519
+ const securityGate = readWaveSecurityGate(wave, agentRuns);
2520
+ const contQaGate = readWaveContQaGate(wave, agentRuns, {
2521
+ contQaAgentId: lanePaths?.contQaAgentId,
2522
+ mode: validationMode,
2515
2523
  });
2516
2524
  const infraGate = readWaveInfraGate(agentRuns);
2517
2525
  const clarificationBarrier = readClarificationBarrier(derivedState);
@@ -2522,10 +2530,12 @@ export function buildGateSnapshot({
2522
2530
  ["componentGate", componentGate],
2523
2531
  ["helperAssignmentBarrier", helperAssignmentBarrier],
2524
2532
  ["dependencyBarrier", dependencyBarrier],
2533
+ ["contEvalGate", contEvalGate],
2534
+ ["securityGate", securityGate],
2525
2535
  ["integrationBarrier", integrationBarrier],
2526
2536
  ["documentationGate", documentationGate],
2527
2537
  ["componentMatrixGate", componentMatrixGate],
2528
- ["evaluatorGate", evaluatorGate],
2538
+ ["contQaGate", contQaGate],
2529
2539
  ["infraGate", infraGate],
2530
2540
  ["clarificationBarrier", clarificationBarrier],
2531
2541
  ];
@@ -2537,7 +2547,9 @@ export function buildGateSnapshot({
2537
2547
  integrationBarrier,
2538
2548
  documentationGate,
2539
2549
  componentMatrixGate,
2540
- evaluatorGate,
2550
+ contEvalGate,
2551
+ securityGate,
2552
+ contQaGate,
2541
2553
  infraGate,
2542
2554
  clarificationBarrier,
2543
2555
  helperAssignmentBarrier,
@@ -2697,9 +2709,21 @@ export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths
2697
2709
  barrier: null,
2698
2710
  };
2699
2711
  }
2700
- if (derivedState?.ledger?.phase === "evaluator-closure") {
2712
+ if (derivedState?.ledger?.phase === "security-review") {
2713
+ return {
2714
+ runs: agentRuns.filter((run) => isSecurityReviewAgent(run.agent)),
2715
+ barrier: null,
2716
+ };
2717
+ }
2718
+ if (derivedState?.ledger?.phase === "cont-eval") {
2701
2719
  return {
2702
- runs: [runsByAgentId.get(lanePaths.evaluatorAgentId)].filter(Boolean),
2720
+ runs: [runsByAgentId.get(lanePaths.contEvalAgentId)].filter(Boolean),
2721
+ barrier: null,
2722
+ };
2723
+ }
2724
+ if (derivedState?.ledger?.phase === "cont-qa-closure") {
2725
+ return {
2726
+ runs: [runsByAgentId.get(lanePaths.contQaAgentId)].filter(Boolean),
2703
2727
  barrier: null,
2704
2728
  };
2705
2729
  }
@@ -2786,6 +2810,7 @@ export async function runLauncherCli(argv) {
2786
2810
  ensureDirectory(lanePaths.inboxesDir);
2787
2811
  ensureDirectory(lanePaths.ledgerDir);
2788
2812
  ensureDirectory(lanePaths.integrationDir);
2813
+ ensureDirectory(lanePaths.securityDir);
2789
2814
  ensureDirectory(lanePaths.dependencySnapshotsDir);
2790
2815
  ensureDirectory(lanePaths.docsQueueDir);
2791
2816
  ensureDirectory(lanePaths.tracesDir);
@@ -2837,7 +2862,8 @@ export async function runLauncherCli(argv) {
2837
2862
  lane: lanePaths.lane,
2838
2863
  bundleIndex: context7BundleIndex,
2839
2864
  }),
2840
- evaluatorAgentId: lanePaths.evaluatorAgentId,
2865
+ contQaAgentId: lanePaths.contQaAgentId,
2866
+ contEvalAgentId: lanePaths.contEvalAgentId,
2841
2867
  integrationAgentId: lanePaths.integrationAgentId,
2842
2868
  documentationAgentId: lanePaths.documentationAgentId,
2843
2869
  }),
@@ -2850,7 +2876,8 @@ export async function runLauncherCli(argv) {
2850
2876
  {
2851
2877
  logsDir: lanePaths.logsDir,
2852
2878
  coordinationDir: lanePaths.coordinationDir,
2853
- evaluatorAgentId: lanePaths.evaluatorAgentId,
2879
+ contQaAgentId: lanePaths.contQaAgentId,
2880
+ contEvalAgentId: lanePaths.contEvalAgentId,
2854
2881
  integrationAgentId: lanePaths.integrationAgentId,
2855
2882
  documentationAgentId: lanePaths.documentationAgentId,
2856
2883
  requireExitContractsFromWave: lanePaths.requireExitContractsFromWave,
@@ -2945,7 +2972,7 @@ export async function runLauncherCli(argv) {
2945
2972
  event: "launcher_start",
2946
2973
  waves: selectedWavesForCoordination,
2947
2974
  status: options.dryRun ? "dry-run" : "running",
2948
- details: `pid=${process.pid}; range=${filteredWaves[0]?.wave ?? "?"}..${filteredWaves.at(-1)?.wave ?? "?"}; timeout_minutes=${options.timeoutMinutes}; retries=${options.maxRetriesPerWave}; ${options.coordinationNote ? `note=${options.coordinationNote}` : "note=n/a"}`,
2975
+ details: `pid=${process.pid}; run_kind=${lanePaths.runKind}; run_id=${lanePaths.runId || "none"}; range=${filteredWaves[0]?.wave ?? "?"}..${filteredWaves.at(-1)?.wave ?? "?"}; timeout_minutes=${options.timeoutMinutes}; retries=${options.maxRetriesPerWave}; ${options.coordinationNote ? `note=${options.coordinationNote}` : "note=n/a"}`,
2949
2976
  });
2950
2977
 
2951
2978
  if (options.dryRun) {
@@ -3159,7 +3186,7 @@ export async function runLauncherCli(argv) {
3159
3186
  const refreshDerivedState = (attemptNumber = 0) => {
3160
3187
  const summariesByAgentId = Object.fromEntries(
3161
3188
  agentRuns
3162
- .map((run) => [run.agent.agentId, readRunExecutionSummary(run)])
3189
+ .map((run) => [run.agent.agentId, readRunExecutionSummary(run, wave)])
3163
3190
  .filter(([, summary]) => summary),
3164
3191
  );
3165
3192
  const feedbackRequests = readWaveHumanFeedbackRequests({
@@ -3190,6 +3217,7 @@ export async function runLauncherCli(argv) {
3190
3217
  };
3191
3218
 
3192
3219
  refreshDerivedState(0);
3220
+ const persistedRelaunchPlan = readWaveRelaunchPlan(lanePaths, wave.wave);
3193
3221
 
3194
3222
  dashboardState = buildWaveDashboardState({
3195
3223
  lane: lanePaths.lane,
@@ -3207,7 +3235,10 @@ export async function runLauncherCli(argv) {
3207
3235
  .filter(
3208
3236
  (run) =>
3209
3237
  !isClosureAgentId(run.agent.agentId, lanePaths) &&
3210
- hasReusableSuccessStatus(run.agent, run.statusPath),
3238
+ hasReusableSuccessStatus(run.agent, run.statusPath, {
3239
+ wave,
3240
+ derivedState,
3241
+ }),
3211
3242
  )
3212
3243
  .map((run) => run.agent.agentId),
3213
3244
  );
@@ -3245,12 +3276,19 @@ export async function runLauncherCli(argv) {
3245
3276
  });
3246
3277
  }
3247
3278
 
3248
- let runsToLaunch = selectInitialWaveRuns(
3249
- agentRuns.filter((run) => !preCompletedAgentIds.has(run.agent.agentId)),
3279
+ const availableRuns = agentRuns.filter((run) => !preCompletedAgentIds.has(run.agent.agentId));
3280
+ const persistedRuns = applyPersistedRelaunchPlan(
3281
+ availableRuns,
3282
+ persistedRelaunchPlan,
3250
3283
  lanePaths,
3284
+ wave,
3251
3285
  );
3286
+ let runsToLaunch =
3287
+ persistedRuns.length > 0 ? persistedRuns : selectInitialWaveRuns(availableRuns, lanePaths);
3252
3288
  let attempt = 1;
3253
3289
  const feedbackStateByRequestId = new Map();
3290
+ let completionGateSnapshot = null;
3291
+ let completionTraceDir = null;
3254
3292
 
3255
3293
  while (attempt <= options.maxRetriesPerWave + 1) {
3256
3294
  refreshDerivedState(attempt - 1);
@@ -3264,19 +3302,21 @@ export async function runLauncherCli(argv) {
3264
3302
  const launchedImplementationRuns = runsToLaunch.filter(
3265
3303
  (run) =>
3266
3304
  ![
3267
- lanePaths.evaluatorAgentId,
3305
+ lanePaths.contEvalAgentId,
3306
+ lanePaths.contQaAgentId,
3268
3307
  lanePaths.integrationAgentId,
3269
3308
  lanePaths.documentationAgentId,
3270
3309
  ].includes(
3271
3310
  run.agent.agentId,
3272
3311
  ),
3273
3312
  );
3274
- const evaluatorOnlyRetry =
3313
+ const closureOnlyRetry =
3275
3314
  runsToLaunch.length > 0 &&
3276
3315
  launchedImplementationRuns.length === 0 &&
3277
3316
  runsToLaunch.every((run) =>
3278
3317
  [
3279
- lanePaths.evaluatorAgentId,
3318
+ lanePaths.contEvalAgentId,
3319
+ lanePaths.contQaAgentId,
3280
3320
  lanePaths.integrationAgentId,
3281
3321
  lanePaths.documentationAgentId,
3282
3322
  ].includes(
@@ -3286,7 +3326,7 @@ export async function runLauncherCli(argv) {
3286
3326
 
3287
3327
  let failures = [];
3288
3328
  let timedOut = false;
3289
- if (evaluatorOnlyRetry) {
3329
+ if (closureOnlyRetry) {
3290
3330
  const closureResult = await runClosureSweepPhase({
3291
3331
  lanePaths,
3292
3332
  wave,
@@ -3515,7 +3555,8 @@ export async function runLauncherCli(argv) {
3515
3555
  wave,
3516
3556
  closureRuns: agentRuns.filter((run) =>
3517
3557
  [
3518
- lanePaths.evaluatorAgentId,
3558
+ lanePaths.contEvalAgentId,
3559
+ lanePaths.contQaAgentId,
3519
3560
  lanePaths.integrationAgentId,
3520
3561
  lanePaths.documentationAgentId,
3521
3562
  ].includes(
@@ -3594,6 +3635,36 @@ export async function runLauncherCli(argv) {
3594
3635
  }
3595
3636
  }
3596
3637
 
3638
+ if (failures.length === 0) {
3639
+ const contEvalGate = readWaveContEvalGate(wave, agentRuns, {
3640
+ contEvalAgentId: lanePaths.contEvalAgentId,
3641
+ mode: "live",
3642
+ evalTargets: wave.evalTargets,
3643
+ benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
3644
+ });
3645
+ if (!contEvalGate.ok) {
3646
+ failures = [
3647
+ {
3648
+ agentId: contEvalGate.agentId,
3649
+ statusCode: contEvalGate.statusCode,
3650
+ logPath: contEvalGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
3651
+ },
3652
+ ];
3653
+ recordCombinedEvent({
3654
+ level: "error",
3655
+ agentId: contEvalGate.agentId,
3656
+ message: `cont-EVAL blocked wave ${wave.wave}: ${contEvalGate.detail}`,
3657
+ });
3658
+ appendCoordination({
3659
+ event: "wave_gate_blocked",
3660
+ waves: [wave.wave],
3661
+ status: "blocked",
3662
+ details: `agent=${contEvalGate.agentId}; reason=${contEvalGate.statusCode}; ${contEvalGate.detail}`,
3663
+ actionRequested: `Lane ${lanePaths.lane} owners should resolve cont-EVAL tuning gaps before integration closure.`,
3664
+ });
3665
+ }
3666
+ }
3667
+
3597
3668
  if (failures.length === 0) {
3598
3669
  const integrationGate = readWaveIntegrationGate(wave, agentRuns, {
3599
3670
  integrationAgentId: lanePaths.integrationAgentId,
@@ -3617,7 +3688,7 @@ export async function runLauncherCli(argv) {
3617
3688
  waves: [wave.wave],
3618
3689
  status: "blocked",
3619
3690
  details: `agent=${integrationGate.agentId}; reason=${integrationGate.statusCode}; ${integrationGate.detail}`,
3620
- actionRequested: `Lane ${lanePaths.lane} owners should resolve integration contradictions or blockers before doc/evaluator closure.`,
3691
+ actionRequested: `Lane ${lanePaths.lane} owners should resolve integration contradictions or blockers before documentation and cont-QA closure.`,
3621
3692
  });
3622
3693
  }
3623
3694
  }
@@ -3677,38 +3748,38 @@ export async function runLauncherCli(argv) {
3677
3748
  }
3678
3749
 
3679
3750
  if (failures.length === 0) {
3680
- const evaluatorGate = readWaveEvaluatorGate(wave, agentRuns);
3681
- if (!evaluatorGate.ok) {
3751
+ const contQaGate = readWaveContQaGate(wave, agentRuns, { mode: "live" });
3752
+ if (!contQaGate.ok) {
3682
3753
  failures = [
3683
3754
  {
3684
- agentId: evaluatorGate.agentId,
3685
- statusCode: evaluatorGate.statusCode,
3686
- logPath: evaluatorGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
3755
+ agentId: contQaGate.agentId,
3756
+ statusCode: contQaGate.statusCode,
3757
+ logPath: contQaGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
3687
3758
  },
3688
3759
  ];
3689
3760
  recordCombinedEvent({
3690
3761
  level: "error",
3691
- agentId: evaluatorGate.agentId,
3692
- message: `Evaluator gate blocked wave ${wave.wave}: ${evaluatorGate.detail}`,
3762
+ agentId: contQaGate.agentId,
3763
+ message: `cont-QA gate blocked wave ${wave.wave}: ${contQaGate.detail}`,
3693
3764
  });
3694
3765
  appendCoordination({
3695
3766
  event: "wave_gate_blocked",
3696
3767
  waves: [wave.wave],
3697
3768
  status: "blocked",
3698
- details: `agent=${evaluatorGate.agentId}; reason=${evaluatorGate.statusCode}; ${evaluatorGate.detail}`,
3699
- actionRequested: `Lane ${lanePaths.lane} owners should resolve the evaluator gate before wave progression.`,
3769
+ details: `agent=${contQaGate.agentId}; reason=${contQaGate.statusCode}; ${contQaGate.detail}`,
3770
+ actionRequested: `Lane ${lanePaths.lane} owners should resolve the cont-QA gate before wave progression.`,
3700
3771
  });
3701
3772
  } else {
3702
- setWaveDashboardAgent(dashboardState, evaluatorGate.agentId, {
3703
- detail: evaluatorGate.detail
3704
- ? `Exit 0; evaluator PASS (${evaluatorGate.detail})`
3705
- : "Exit 0; evaluator PASS",
3773
+ setWaveDashboardAgent(dashboardState, contQaGate.agentId, {
3774
+ detail: contQaGate.detail
3775
+ ? `Exit 0; cont-QA PASS (${contQaGate.detail})`
3776
+ : "Exit 0; cont-QA PASS",
3706
3777
  });
3707
3778
  recordCombinedEvent({
3708
- agentId: evaluatorGate.agentId,
3709
- message: evaluatorGate.detail
3710
- ? `Evaluator verdict PASS: ${evaluatorGate.detail}`
3711
- : "Evaluator verdict PASS.",
3779
+ agentId: contQaGate.agentId,
3780
+ message: contQaGate.detail
3781
+ ? `cont-QA verdict PASS: ${contQaGate.detail}`
3782
+ : "cont-QA verdict PASS.",
3712
3783
  });
3713
3784
  }
3714
3785
  }
@@ -3769,7 +3840,7 @@ export async function runLauncherCli(argv) {
3769
3840
  );
3770
3841
  const summariesByAgentId = Object.fromEntries(
3771
3842
  agentRuns
3772
- .map((run) => [run.agent.agentId, readRunExecutionSummary(run)])
3843
+ .map((run) => [run.agent.agentId, readRunExecutionSummary(run, wave)])
3773
3844
  .filter(([, summary]) => summary),
3774
3845
  );
3775
3846
  const gateSnapshot = buildGateSnapshot({
@@ -3777,7 +3848,9 @@ export async function runLauncherCli(argv) {
3777
3848
  agentRuns,
3778
3849
  derivedState,
3779
3850
  lanePaths,
3851
+ validationMode: "live",
3780
3852
  });
3853
+ completionGateSnapshot = gateSnapshot;
3781
3854
  const traceDir = writeTraceBundle({
3782
3855
  tracesDir: lanePaths.tracesDir,
3783
3856
  lanePaths,
@@ -3791,6 +3864,7 @@ export async function runLauncherCli(argv) {
3791
3864
  docsQueue: derivedState.docsQueue,
3792
3865
  capabilityAssignments: derivedState.capabilityAssignments,
3793
3866
  dependencySnapshot: derivedState.dependencySnapshot,
3867
+ securitySummary: derivedState.securitySummary,
3794
3868
  integrationSummary: derivedState.integrationSummary,
3795
3869
  integrationMarkdownPath: derivedState.integrationMarkdownPath,
3796
3870
  clarificationTriage: derivedState.clarificationTriage,
@@ -3813,6 +3887,7 @@ export async function runLauncherCli(argv) {
3813
3887
  coordinationLogPath: derivedState.coordinationLogPath,
3814
3888
  }),
3815
3889
  });
3890
+ completionTraceDir = traceDir;
3816
3891
 
3817
3892
  if (failures.length === 0) {
3818
3893
  dashboardState.status = "completed";
@@ -3846,8 +3921,8 @@ export async function runLauncherCli(argv) {
3846
3921
  if (
3847
3922
  failures.every(
3848
3923
  (failure) =>
3849
- String(failure.statusCode).startsWith("evaluator-") ||
3850
- String(failure.statusCode) === "missing-evaluator-verdict",
3924
+ String(failure.statusCode).startsWith("cont-qa-") ||
3925
+ String(failure.statusCode) === "missing-cont-qa-verdict",
3851
3926
  )
3852
3927
  ) {
3853
3928
  error.exitCode = 42;
@@ -3875,6 +3950,7 @@ export async function runLauncherCli(argv) {
3875
3950
  wave,
3876
3951
  );
3877
3952
  if (relaunchResolution.barrier) {
3953
+ clearWaveRelaunchPlan(lanePaths, wave.wave);
3878
3954
  for (const failure of relaunchResolution.barrier.failures) {
3879
3955
  recordCombinedEvent({
3880
3956
  level: "error",
@@ -3902,6 +3978,7 @@ export async function runLauncherCli(argv) {
3902
3978
  }
3903
3979
  runsToLaunch = relaunchResolution.runs;
3904
3980
  if (runsToLaunch.length === 0) {
3981
+ clearWaveRelaunchPlan(lanePaths, wave.wave);
3905
3982
  const error = new Error(
3906
3983
  `Wave ${wave.wave} is waiting on human feedback or unresolved coordination state; no safe relaunch target is available.`,
3907
3984
  );
@@ -3914,11 +3991,42 @@ export async function runLauncherCli(argv) {
3914
3991
  detail: "Queued for retry",
3915
3992
  });
3916
3993
  }
3994
+ writeWaveRelaunchPlan(lanePaths, wave.wave, {
3995
+ wave: wave.wave,
3996
+ attempt: attempt + 1,
3997
+ phase: derivedState?.ledger?.phase || null,
3998
+ selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
3999
+ reasonBuckets: relaunchReasonBuckets(runsToLaunch, failures, derivedState),
4000
+ executorStates: Object.fromEntries(
4001
+ runsToLaunch.map((run) => [run.agent.agentId, run.agent.executorResolved || null]),
4002
+ ),
4003
+ fallbackHistory: Object.fromEntries(
4004
+ runsToLaunch.map((run) => [
4005
+ run.agent.agentId,
4006
+ run.agent.executorResolved?.executorHistory || [],
4007
+ ]),
4008
+ ),
4009
+ createdAt: toIsoTimestamp(),
4010
+ });
3917
4011
  flushDashboards();
3918
4012
  attempt += 1;
3919
4013
  }
3920
4014
 
3921
- const runState = markWaveCompleted(options.runStatePath, wave.wave);
4015
+ clearWaveRelaunchPlan(lanePaths, wave.wave);
4016
+ const runState = markWaveCompleted(options.runStatePath, wave.wave, {
4017
+ source: "live-launcher",
4018
+ reasonCode: "wave-complete",
4019
+ detail: `Wave ${wave.wave} completed after ${dashboardState?.attempt || 1} attempt(s).`,
4020
+ evidence: buildRunStateEvidence({
4021
+ wave,
4022
+ agentRuns,
4023
+ coordinationLogPath: derivedState.coordinationLogPath,
4024
+ assignmentsPath: waveAssignmentsPath(lanePaths, wave.wave),
4025
+ dependencySnapshotPath: waveDependencySnapshotPath(lanePaths, wave.wave),
4026
+ gateSnapshot: completionGateSnapshot,
4027
+ traceDir: completionTraceDir,
4028
+ }),
4029
+ });
3922
4030
  console.log(
3923
4031
  `[state] completed waves (${path.relative(REPO_ROOT, options.runStatePath)}): ${runState.completedWaves.join(", ") || "none"}`,
3924
4032
  );