@chllming/wave-orchestration 0.8.4 → 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 (45) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +25 -12
  3. package/docs/README.md +2 -0
  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/current-state.md +5 -1
  9. package/docs/plans/end-state-architecture.md +7 -2
  10. package/docs/plans/examples/wave-example-design-handoff.md +262 -0
  11. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  12. package/docs/plans/migration.md +208 -75
  13. package/docs/plans/wave-orchestrator.md +13 -3
  14. package/docs/reference/cli-reference.md +12 -0
  15. package/docs/reference/npmjs-trusted-publishing.md +2 -2
  16. package/docs/reference/sample-waves.md +14 -7
  17. package/docs/reference/skills.md +10 -0
  18. package/package.json +1 -1
  19. package/releases/manifest.json +19 -0
  20. package/scripts/wave-orchestrator/agent-state.mjs +64 -0
  21. package/scripts/wave-orchestrator/config.mjs +5 -0
  22. package/scripts/wave-orchestrator/coordination.mjs +42 -1
  23. package/scripts/wave-orchestrator/gate-engine.mjs +106 -2
  24. package/scripts/wave-orchestrator/install.mjs +3 -0
  25. package/scripts/wave-orchestrator/launcher-runtime.mjs +7 -1
  26. package/scripts/wave-orchestrator/launcher.mjs +55 -1
  27. package/scripts/wave-orchestrator/ledger.mjs +56 -27
  28. package/scripts/wave-orchestrator/local-executor.mjs +37 -0
  29. package/scripts/wave-orchestrator/planner.mjs +24 -4
  30. package/scripts/wave-orchestrator/result-envelope.mjs +32 -1
  31. package/scripts/wave-orchestrator/retry-control.mjs +17 -2
  32. package/scripts/wave-orchestrator/retry-engine.mjs +85 -0
  33. package/scripts/wave-orchestrator/role-helpers.mjs +73 -1
  34. package/scripts/wave-orchestrator/shared.mjs +1 -0
  35. package/scripts/wave-orchestrator/skills.mjs +1 -0
  36. package/scripts/wave-orchestrator/task-entity.mjs +65 -45
  37. package/scripts/wave-orchestrator/wave-files.mjs +85 -1
  38. package/scripts/wave-orchestrator/wave-state-reducer.mjs +24 -7
  39. package/skills/README.md +7 -0
  40. package/skills/role-design/SKILL.md +50 -0
  41. package/skills/role-design/skill.json +36 -0
  42. package/skills/tui-design/SKILL.md +77 -0
  43. package/skills/tui-design/references/tui-design.md +259 -0
  44. package/skills/tui-design/skill.json +36 -0
  45. package/wave.config.json +15 -1
@@ -109,6 +109,7 @@ Top-level and lane-local skill attachment use the same shape:
109
109
  "dir": "skills",
110
110
  "base": ["wave-core", "repo-coding-rules"],
111
111
  "byRole": {
112
+ "design": ["role-design"],
112
113
  "deploy": ["role-deploy"]
113
114
  },
114
115
  "byRuntime": {
@@ -123,6 +124,8 @@ Top-level and lane-local skill attachment use the same shape:
123
124
 
124
125
  Lane-local `lanes.<lane>.skills` extends the global config instead of replacing it.
125
126
 
127
+ Optional design workers in the shipped `0.8.5` surface normally attach `role-design`. That bundle is intended for docs/spec-first design packets and explicit implementation handoff work before implementation starts. When the design packet covers terminal UX, dashboards, or other operator surfaces, add `tui-design` explicitly in the wave's `### Skills`.
128
+
126
129
  ## Resolution Order
127
130
 
128
131
  Resolved skills are gathered in this order:
@@ -195,6 +198,12 @@ Runtime delivery:
195
198
 
196
199
  These runtime projections are guidance surfaces. They should stay aligned with the canonical authority model, but they are not replay inputs or decision state on their own.
197
200
 
201
+ For the optional `design` worker role, the default pattern is:
202
+
203
+ - `role-design` for the design packet contract
204
+ - `tui-design` only when the packet covers terminal UX, dashboards, or other operator surfaces
205
+ - no runtime-specific coding bundle unless the wave explicitly gives the design steward code ownership and makes it a hybrid design steward
206
+
198
207
  ## Generated Artifacts
199
208
 
200
209
  Executor overlay directories can contain:
@@ -225,3 +234,4 @@ Missing or malformed bundles are configuration errors, not silent no-ops.
225
234
  - Use explicit per-agent `### Skills` for true exceptions, not as a substitute for missing activation metadata.
226
235
  - Keep provider skills role-scoped unless every role genuinely needs the provider context.
227
236
  - Keep bundle ids stable so traces and prompt fingerprints remain intelligible across runs.
237
+ - Keep `role-design` docs/spec-first by default; add `tui-design` when terminal or operator-surface work is in scope, and only attach broader coding bundles when the wave explicitly assigns code ownership and expects the same design steward to return for implementation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chllming/wave-orchestration",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "license": "MIT",
5
5
  "description": "Generic wave-based multi-agent orchestration for repository work.",
6
6
  "repository": {
@@ -2,6 +2,25 @@
2
2
  "schemaVersion": 1,
3
3
  "packageName": "@chllming/wave-orchestration",
4
4
  "releases": [
5
+ {
6
+ "version": "0.8.5",
7
+ "date": "2026-03-25",
8
+ "summary": "Shipped design-role support, hybrid design-steward execution, release-surface alignment, and a practical migration guide.",
9
+ "features": [
10
+ "The optional `design` worker role is now part of the published release surface, including the standing design prompt, `role-design`, and `tui-design` starter bundles.",
11
+ "Design stewards are docs-first by default, but waves can now explicitly give them implementation ownership so the same agent runs a design pass first and then rejoins implementation with normal proof obligations.",
12
+ "Design-aware validation, prompts, gates, retry or resume planning, reducer state, local-executor smoke behavior, and result-envelope projection now all agree on the same hybrid-design contract.",
13
+ "The migration guide now covers fresh adoption plus upgrades from `0.8.4`, `0.8.0`-`0.8.4`, `0.6.x`-`0.7.x`, and `0.5.x` or earlier, including repo-owned starter-surface sync guidance and `planner-agentic` corpus notes.",
14
+ "Shipped package metadata, README, current-state notes, sample-wave docs, and publishing guidance now point at the `0.8.5` release surface."
15
+ ],
16
+ "manualSteps": [
17
+ "Run `pnpm exec wave doctor` and `pnpm exec wave launch --lane main --dry-run --no-dashboard` after upgrading so the repo validates against the `0.8.5` design-role and hybrid-design behavior.",
18
+ "If your repo copied starter prompts, skills, or authoring docs, sync `docs/agents/wave-design-role.md`, `skills/role-design/`, `skills/tui-design/`, `wave.config.json` design-role keys, and any local planner or runbook pages that should describe the new design-steward model.",
19
+ "If a repo uses hybrid design stewards, make sure each one still owns a design packet path and that any explicit implementation ownership also carries the expected exit contract, deliverables, proof artifacts, and component declarations where your lane validation requires them.",
20
+ "If your repo uses planner workflows and copied the planner starter corpus, keep `docs/agents/wave-planner-role.md`, `skills/role-planner/`, `docs/context7/planner-agent/`, `docs/reference/wave-planning-lessons.md`, and the `planner-agentic` bundle entry in sync before relying on local planner docs."
21
+ ],
22
+ "breaking": false
23
+ },
5
24
  {
6
25
  "version": "0.8.4",
7
26
  "date": "2026-03-25",
@@ -47,6 +47,8 @@ const WAVE_EVAL_REGEX =
47
47
  /^\[wave-eval\]\s*state=(satisfied|needs-more-work|blocked)\s+targets=(\d+)\s+benchmarks=(\d+)\s+regressions=(\d+)(?:\s+target_ids=([^\s]+))?(?:\s+benchmark_ids=([^\s]+))?\s*(?:detail=(.*))?$/gim;
48
48
  const WAVE_SECURITY_REGEX =
49
49
  /^\[wave-security\]\s*state=(clear|concerns|blocked)\s+findings=(\d+)\s+approvals=(\d+)\s*(?:detail=(.*))?$/gim;
50
+ const WAVE_DESIGN_REGEX =
51
+ /^\[wave-design\]\s*state=(ready-for-implementation|needs-clarification|blocked)\s+decisions=(\d+)\s+assumptions=(\d+)\s+open_questions=(\d+)\s*(?:detail=(.*))?$/gim;
50
52
  const WAVE_GATE_REGEX =
51
53
  /^\[wave-gate\]\s*architecture=(pass|concerns|blocked)\s+integration=(pass|concerns|blocked)\s+durability=(pass|concerns|blocked)\s+live=(pass|concerns|blocked)\s+docs=(pass|concerns|blocked)\s*(?:detail=(.*))?$/gim;
52
54
  const WAVE_GAP_REGEX =
@@ -64,6 +66,7 @@ const STRUCTURED_SIGNAL_KIND_BY_TAG = {
64
66
  integration: "integration",
65
67
  eval: "eval",
66
68
  security: "security",
69
+ design: "design",
67
70
  gate: "gate",
68
71
  gap: "gap",
69
72
  component: "component",
@@ -76,6 +79,7 @@ const STRUCTURED_SIGNAL_LINE_REGEX_BY_KIND = {
76
79
  integration: new RegExp(WAVE_INTEGRATION_REGEX.source, "i"),
77
80
  eval: new RegExp(WAVE_EVAL_REGEX.source, "i"),
78
81
  security: new RegExp(WAVE_SECURITY_REGEX.source, "i"),
82
+ design: new RegExp(WAVE_DESIGN_REGEX.source, "i"),
79
83
  gate: new RegExp(WAVE_GATE_REGEX.source, "i"),
80
84
  gap: new RegExp(WAVE_GAP_REGEX.source, "i"),
81
85
  component: new RegExp(WAVE_COMPONENT_REGEX.source, "i"),
@@ -89,6 +93,7 @@ function buildEmptyStructuredSignalDiagnostics() {
89
93
  integration: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
90
94
  eval: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
91
95
  security: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
96
+ design: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
92
97
  gate: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
93
98
  gap: { rawCount: 0, acceptedCount: 0, rejectedSamples: [] },
94
99
  component: { rawCount: 0, acceptedCount: 0, rejectedSamples: [], seenComponentIds: [] },
@@ -509,6 +514,13 @@ export function buildAgentExecutionSummary({ agent, statusRecord, logPath, repor
509
514
  approvals: Number.parseInt(String(match[3] || "0"), 10) || 0,
510
515
  detail: cleanText(match[4]),
511
516
  })),
517
+ design: findLastMatch(signalText, WAVE_DESIGN_REGEX, (match) => ({
518
+ state: match[1],
519
+ decisions: Number.parseInt(String(match[2] || "0"), 10) || 0,
520
+ assumptions: Number.parseInt(String(match[3] || "0"), 10) || 0,
521
+ openQuestions: Number.parseInt(String(match[4] || "0"), 10) || 0,
522
+ detail: cleanText(match[5]),
523
+ })),
512
524
  gate: findLastMatch(signalText, WAVE_GATE_REGEX, (match) => ({
513
525
  architecture: match[1],
514
526
  integration: match[2],
@@ -951,6 +963,58 @@ export function validateSecuritySummary(agent, summary) {
951
963
  };
952
964
  }
953
965
 
966
+ export function validateDesignSummary(agent, summary) {
967
+ if (!summary?.design) {
968
+ return {
969
+ ok: false,
970
+ statusCode: "missing-wave-design",
971
+ detail: appendTerminationHint(
972
+ `Missing [wave-design] marker for ${agent?.agentId || "D1"}.`,
973
+ summary,
974
+ ),
975
+ };
976
+ }
977
+ if (!summary.reportPath) {
978
+ return {
979
+ ok: false,
980
+ statusCode: "missing-design-packet",
981
+ detail: `Missing design packet path for ${agent?.agentId || "D1"}.`,
982
+ };
983
+ }
984
+ if (!fs.existsSync(path.resolve(REPO_ROOT, summary.reportPath))) {
985
+ return {
986
+ ok: false,
987
+ statusCode: "missing-design-packet",
988
+ detail: `Missing design packet at ${summary.reportPath}.`,
989
+ };
990
+ }
991
+ if (summary.design.state === "blocked") {
992
+ return {
993
+ ok: false,
994
+ statusCode: "design-blocked",
995
+ detail:
996
+ summary.design.detail ||
997
+ `Design packet reported blocked for ${agent?.agentId || "D1"}.`,
998
+ };
999
+ }
1000
+ if (summary.design.state === "needs-clarification") {
1001
+ return {
1002
+ ok: false,
1003
+ statusCode: "design-needs-clarification",
1004
+ detail:
1005
+ summary.design.detail ||
1006
+ `Design packet requested clarification for ${agent?.agentId || "D1"}.`,
1007
+ };
1008
+ }
1009
+ return {
1010
+ ok: true,
1011
+ statusCode: "pass",
1012
+ detail:
1013
+ summary.design.detail ||
1014
+ "Design packet is ready for implementation.",
1015
+ };
1016
+ }
1017
+
954
1018
  export function validateIntegrationSummary(agent, summary) {
955
1019
  if (!summary?.integration) {
956
1020
  return {
@@ -28,6 +28,7 @@ export const DEFAULT_INTEGRATION_ROLE_PROMPT_PATH = "docs/agents/wave-integratio
28
28
  export const DEFAULT_DOCUMENTATION_ROLE_PROMPT_PATH =
29
29
  "docs/agents/wave-documentation-role.md";
30
30
  export const DEFAULT_SECURITY_ROLE_PROMPT_PATH = "docs/agents/wave-security-role.md";
31
+ export const DEFAULT_DESIGN_ROLE_PROMPT_PATH = "docs/agents/wave-design-role.md";
31
32
  export const DEFAULT_TERMINALS_PATH = ".vscode/terminals.json";
32
33
  export const DEFAULT_DOCS_DIR = "docs";
33
34
  export const DEFAULT_STATE_ROOT = ".tmp";
@@ -348,6 +349,10 @@ function normalizeRoles(rawRoles = {}) {
348
349
  rawRoles.securityRolePromptPath || DEFAULT_SECURITY_ROLE_PROMPT_PATH,
349
350
  "roles.securityRolePromptPath",
350
351
  ),
352
+ designRolePromptPath: normalizeRepoRelativePath(
353
+ rawRoles.designRolePromptPath || DEFAULT_DESIGN_ROLE_PROMPT_PATH,
354
+ "roles.designRolePromptPath",
355
+ ),
351
356
  };
352
357
  }
353
358
 
@@ -17,6 +17,8 @@ import {
17
17
  import { resolveEvalTargetsAgainstCatalog } from "./evals.mjs";
18
18
  import {
19
19
  isContEvalImplementationOwningAgent,
20
+ isDesignAgent,
21
+ isImplementationOwningDesignAgent,
20
22
  isSecurityReviewAgent,
21
23
  } from "./role-helpers.mjs";
22
24
 
@@ -199,6 +201,8 @@ export function buildExecutionPrompt({
199
201
  evalTargets = null,
200
202
  benchmarkCatalogPath = null,
201
203
  sharedPlanDocs = null,
204
+ designPacketPaths = null,
205
+ designExecutionMode = null,
202
206
  contQaAgentId = "A0",
203
207
  contEvalAgentId = "E0",
204
208
  integrationAgentId = "A8",
@@ -222,6 +226,10 @@ export function buildExecutionPrompt({
222
226
  const contEvalImplementationOwning = isContEvalImplementationOwningAgent(agent, {
223
227
  contEvalAgentId,
224
228
  });
229
+ const hybridDesignAgent = isImplementationOwningDesignAgent(agent);
230
+ const designAgent = isDesignAgent(agent);
231
+ const designImplementationPass = designAgent && hybridDesignAgent && designExecutionMode === "implementation-pass";
232
+ const designPacketPass = designAgent && !designImplementationPass;
225
233
  const resolvedEvalTargets = (() => {
226
234
  try {
227
235
  return resolveEvalTargetsAgainstCatalog(evalTargets, { benchmarkCatalogPath }).targets;
@@ -269,6 +277,22 @@ export function buildExecutionPrompt({
269
277
  "- Use `clear` only when no unresolved findings or approvals remain. Use `blocked` only when the wave must stop before integration.",
270
278
  ]
271
279
  : [];
280
+ const designRequirements = designAgent
281
+ ? [
282
+ designImplementationPass
283
+ ? "- You are in the hybrid design steward's implementation follow-through pass. Keep the design packet current, implement only your explicit owned paths, and finish with both `[wave-design]` and the normal implementation proof markers."
284
+ : "- You are the wave's design steward. Stay packet-first and docs/spec-owned unless the wave explicitly assigns more.",
285
+ "- Leave one design packet with these sections in order: `Problem`, `Constraints`, `Decisions`, `Assumptions`, `Open Questions`, `Interface Impacts`, `Validation Plan`, `Implementation Handoff`.",
286
+ "- Emit one final structured design marker: `[wave-design] state=<ready-for-implementation|needs-clarification|blocked> decisions=<n> assumptions=<n> open_questions=<n> detail=<short-note>`.",
287
+ "- Use `ready-for-implementation` only when downstream implementation owners can start without unresolved design ambiguity.",
288
+ "- Use `needs-clarification` when the wave should stop for a specific question or decision before coding starts.",
289
+ ...(hybridDesignAgent && !designImplementationPass
290
+ ? [
291
+ "- This wave also assigns you explicit implementation-owned files, but this first pass is still design-only. Do not claim implementation proof yet; the code-owning pass starts only after the design packet is ready.",
292
+ ]
293
+ : []),
294
+ ]
295
+ : [];
272
296
  const coordinationCommand = [
273
297
  "pnpm exec wave coord post",
274
298
  `--lane ${lane}`,
@@ -280,6 +304,7 @@ export function buildExecutionPrompt({
280
304
  ].join(" ");
281
305
  const implementationRequirements =
282
306
  ![contQaAgentId, documentationAgentId].includes(agent.agentId) &&
307
+ (!designAgent || designImplementationPass) &&
283
308
  !isSecurityReviewAgent(agent) &&
284
309
  (agent.agentId !== contEvalAgentId || contEvalImplementationOwning)
285
310
  ? [
@@ -294,7 +319,8 @@ export function buildExecutionPrompt({
294
319
  `- Route unresolved architecture, integration, durability, ops, or docs issues through \`${coordinationCommand}\`. Do not append \`[wave-gap]\` lines after the final implementation markers.`,
295
320
  ]
296
321
  : [];
297
- const exitContractLines = agent.exitContract
322
+ const exitContractLines =
323
+ implementationRequirements.length > 0 && agent.exitContract
298
324
  ? [
299
325
  "Exit contract for this run:",
300
326
  `- completion: ${agent.exitContract.completion}`,
@@ -385,6 +411,7 @@ export function buildExecutionPrompt({
385
411
  : [];
386
412
  const ownedComponentLines =
387
413
  ![contQaAgentId, documentationAgentId].includes(agent.agentId) &&
414
+ (!designAgent || designImplementationPass) &&
388
415
  (agent.agentId !== contEvalAgentId || contEvalImplementationOwning) &&
389
416
  Array.isArray(agent.components) &&
390
417
  agent.components.length > 0
@@ -398,6 +425,7 @@ export function buildExecutionPrompt({
398
425
  ]
399
426
  : [];
400
427
  const deliverableLines =
428
+ (!designAgent || designImplementationPass) &&
401
429
  Array.isArray(agent.deliverables) && agent.deliverables.length > 0
402
430
  ? [
403
431
  "Deliverables required for this agent:",
@@ -405,7 +433,18 @@ export function buildExecutionPrompt({
405
433
  "",
406
434
  ]
407
435
  : [];
436
+ const designPacketLines =
437
+ !designAgent &&
438
+ Array.isArray(designPacketPaths) &&
439
+ designPacketPaths.length > 0
440
+ ? [
441
+ "Same-wave design packets to read before coding:",
442
+ ...designPacketPaths.map((designPacketPath) => `- ${designPacketPath}`),
443
+ "",
444
+ ]
445
+ : [];
408
446
  const proofArtifactLines =
447
+ implementationRequirements.length > 0 &&
409
448
  Array.isArray(agent.proofArtifacts) && agent.proofArtifacts.length > 0
410
449
  ? [
411
450
  "Proof artifacts required for this agent:",
@@ -473,6 +512,7 @@ export function buildExecutionPrompt({
473
512
  ...contEvalRequirements,
474
513
  ...docStewardRequirements,
475
514
  ...securityRequirements,
515
+ ...designRequirements,
476
516
  ...implementationRequirements,
477
517
  `- Update docs impacted by your implementation. If your work changes status, sequencing, ownership, or explicit proof expectations, update the relevant docs. If shared plan docs need changes outside your owned files, post the exact doc paths and exact delta needed for ${sharedPlanDocList} as a coordination record instead of leaving documentation drift for later cleanup.`,
478
518
  "- If the wave defines a documentation steward or other explicit owner for shared plan docs, coordinate those updates through that owner, notify them as soon as the delta is known, and stay engaged until they confirm `closed` or `no-change`. Do not treat the ownership boundary as the definition of done.",
@@ -503,6 +543,7 @@ export function buildExecutionPrompt({
503
543
  ...evalTargetLines,
504
544
  ...ownedComponentLines,
505
545
  ...deliverableLines,
546
+ ...designPacketLines,
506
547
  ...proofArtifactLines,
507
548
  ...skillLines,
508
549
  ...context7PromptLines,
@@ -4,6 +4,7 @@ import {
4
4
  agentSummaryPathFromStatusPath,
5
5
  buildAgentExecutionSummary,
6
6
  readAgentExecutionSummary,
7
+ validateDesignSummary,
7
8
  validateContQaSummary,
8
9
  validateContEvalSummary,
9
10
  validateImplementationSummary,
@@ -33,7 +34,10 @@ import {
33
34
  writeJsonAtomic,
34
35
  } from "./shared.mjs";
35
36
  import {
37
+ isDocsOnlyDesignAgent,
36
38
  isSecurityReviewAgent,
39
+ isDesignAgent,
40
+ resolveDesignReportPath,
37
41
  resolveSecurityReviewReportPath,
38
42
  isContEvalReportOnlyAgent,
39
43
  } from "./role-helpers.mjs";
@@ -94,6 +98,10 @@ function resolveRunReportPath(wave, runInfo) {
94
98
  const securityReportPath = resolveSecurityReviewReportPath(runInfo.agent);
95
99
  return securityReportPath ? path.resolve(REPO_ROOT, securityReportPath) : null;
96
100
  }
101
+ if (isDesignAgent(runInfo.agent)) {
102
+ const designReportPath = resolveDesignReportPath(runInfo.agent);
103
+ return designReportPath ? path.resolve(REPO_ROOT, designReportPath) : null;
104
+ }
97
105
  return null;
98
106
  }
99
107
 
@@ -513,6 +521,7 @@ export function readWaveImplementationGate(wave, agentRuns, options = {}) {
513
521
  if (
514
522
  [contQaAgentId, integrationAgentId, documentationAgentId].includes(runInfo.agent.agentId) ||
515
523
  isContEvalReportOnlyAgent(runInfo.agent, { contEvalAgentId }) ||
524
+ isDocsOnlyDesignAgent(runInfo.agent) ||
516
525
  isSecurityReviewAgent(runInfo.agent)
517
526
  ) {
518
527
  continue;
@@ -563,6 +572,65 @@ export function readWaveImplementationGate(wave, agentRuns, options = {}) {
563
572
  };
564
573
  }
565
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
+
566
634
  export function analyzePromotedComponentOwners(componentId, agentRuns, summariesByAgentId) {
567
635
  const ownerRuns = (agentRuns || []).filter((runInfo) =>
568
636
  runInfo.agent.components?.includes(componentId),
@@ -1088,6 +1156,7 @@ export function readWaveImplementationGatePure(wave, agentResults, options = {})
1088
1156
  if (
1089
1157
  [contQaAgentId, integrationAgentId, documentationAgentId].includes(agent.agentId) ||
1090
1158
  isContEvalReportOnlyAgent(agent, { contEvalAgentId }) ||
1159
+ isDocsOnlyDesignAgent(agent) ||
1091
1160
  isSecurityReviewAgent(agent)
1092
1161
  ) {
1093
1162
  continue;
@@ -1110,6 +1179,40 @@ export function readWaveImplementationGatePure(wave, agentResults, options = {})
1110
1179
  };
1111
1180
  }
1112
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
+
1113
1216
  export function readWaveContQaGatePure(wave, agentResults, options = {}) {
1114
1217
  const mode = String(options.mode || "live").trim().toLowerCase();
1115
1218
  const contQaAgentId = options.contQaAgentId || wave.contQaAgentId || "A0";
@@ -1312,6 +1415,7 @@ export function readWaveInfraGatePure(wave, agentResults, options = {}) {
1312
1415
  }
1313
1416
 
1314
1417
  export function buildGateSnapshotPure({ wave, agentResults, derivedState, validationMode = "live", laneConfig = {} }) {
1418
+ const designGate = readWaveDesignGatePure(wave, agentResults);
1315
1419
  const implementationGate = readWaveImplementationGatePure(wave, agentResults, {
1316
1420
  contQaAgentId: laneConfig.contQaAgentId, contEvalAgentId: laneConfig.contEvalAgentId,
1317
1421
  integrationAgentId: laneConfig.integrationAgentId, documentationAgentId: laneConfig.documentationAgentId,
@@ -1363,7 +1467,7 @@ export function buildGateSnapshotPure({ wave, agentResults, derivedState, valida
1363
1467
  const helperAssignmentBarrier = derivedState?.helperAssignmentBarrier || { ok: true, statusCode: "pass", detail: "" };
1364
1468
  const dependencyBarrier = derivedState?.dependencyBarrier || { ok: true, statusCode: "pass", detail: "" };
1365
1469
  const orderedGates = [
1366
- ["implementationGate", implementationGate], ["componentGate", componentGate],
1470
+ ["designGate", designGate], ["implementationGate", implementationGate], ["componentGate", componentGate],
1367
1471
  ["helperAssignmentBarrier", helperAssignmentBarrier], ["dependencyBarrier", dependencyBarrier],
1368
1472
  ["contEvalGate", contEvalGate], ["securityGate", securityGate],
1369
1473
  ["integrationBarrier", integrationBarrier], ["documentationGate", documentationGate],
@@ -1372,7 +1476,7 @@ export function buildGateSnapshotPure({ wave, agentResults, derivedState, valida
1372
1476
  ];
1373
1477
  const firstFailure = orderedGates.find(([, gate]) => gate?.ok === false);
1374
1478
  return {
1375
- implementationGate, componentGate, integrationGate: integrationMarkerGate,
1479
+ designGate, implementationGate, componentGate, integrationGate: integrationMarkerGate,
1376
1480
  integrationBarrier, documentationGate, componentMatrixGate,
1377
1481
  contEvalGate, securityGate, contQaGate, infraGate,
1378
1482
  clarificationBarrier, helperAssignmentBarrier, dependencyBarrier,
@@ -29,6 +29,7 @@ export const STARTER_TEMPLATE_PATHS = [
29
29
  "wave.config.json",
30
30
  "docs/README.md",
31
31
  "docs/agents/wave-documentation-role.md",
32
+ "docs/agents/wave-design-role.md",
32
33
  "docs/agents/wave-cont-qa-role.md",
33
34
  "docs/agents/wave-cont-eval-role.md",
34
35
  "docs/agents/wave-integration-role.md",
@@ -74,6 +75,8 @@ export const STARTER_TEMPLATE_PATHS = [
74
75
  "docs/reference/sample-waves.md",
75
76
  "docs/reference/skills.md",
76
77
  "docs/reference/wave-planning-lessons.md",
78
+ "skills/role-design/SKILL.md",
79
+ "skills/role-design/skill.json",
77
80
  "docs/reference/runtime-config/README.md",
78
81
  "docs/reference/runtime-config/codex.md",
79
82
  "docs/reference/runtime-config/claude.md",
@@ -14,7 +14,7 @@ import { readStatusCodeIfPresent } from "./dashboard-state.mjs";
14
14
  import { buildExecutorLaunchSpec } from "./executors.mjs";
15
15
  import { hashAgentPromptFingerprint, prefetchContext7ForSelection } from "./context7.mjs";
16
16
  import { killTmuxSessionIfExists } from "./terminals.mjs";
17
- import { resolveWaveRoleBindings } from "./role-helpers.mjs";
17
+ import { isDesignAgent, resolveDesignReportPath, resolveWaveRoleBindings } from "./role-helpers.mjs";
18
18
  import {
19
19
  resolveAgentSkills,
20
20
  summarizeResolvedSkills,
@@ -77,6 +77,7 @@ export async function launchAgentSession(lanePaths, params, { runTmuxFn }) {
77
77
  agentRateLimitBaseDelaySeconds,
78
78
  agentRateLimitMaxDelaySeconds,
79
79
  context7Enabled,
80
+ designExecutionMode = null,
80
81
  dryRun = false,
81
82
  } = params;
82
83
  ensureDirectory(path.dirname(promptPath));
@@ -123,6 +124,11 @@ export async function launchAgentSession(lanePaths, params, { runTmuxFn }) {
123
124
  evalTargets: resolvedWaveDefinition.evalTargets,
124
125
  benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
125
126
  sharedPlanDocs: lanePaths.sharedPlanDocs,
127
+ designPacketPaths: (resolvedWaveDefinition.agents || [])
128
+ .filter((waveAgent) => isDesignAgent(waveAgent))
129
+ .map((waveAgent) => resolveDesignReportPath(waveAgent))
130
+ .filter(Boolean),
131
+ designExecutionMode,
126
132
  });
127
133
  const promptHash = hashAgentPromptFingerprint(agent);
128
134
  fs.writeFileSync(promptPath, `${prompt}\n`, "utf8");
@@ -131,6 +131,8 @@ import {
131
131
  isContEvalImplementationOwningAgent,
132
132
  isContEvalReportOnlyAgent,
133
133
  isClosureRoleAgentId,
134
+ isDesignAgent,
135
+ isImplementationOwningDesignAgent,
134
136
  isSecurityReviewAgent,
135
137
  resolveWaveRoleBindings,
136
138
  resolveSecurityReviewReportPath,
@@ -496,6 +498,8 @@ function buildGateSnapshot(params) {
496
498
 
497
499
  function waveGateLabel(gateName) {
498
500
  switch (gateName) {
501
+ case "designGate":
502
+ return "Design packet";
499
503
  case "implementationGate":
500
504
  return "Implementation exit contract";
501
505
  case "componentGate":
@@ -527,6 +531,8 @@ function waveGateLabel(gateName) {
527
531
 
528
532
  function waveGateActionRequested(gateName, lanePaths) {
529
533
  switch (gateName) {
534
+ case "designGate":
535
+ return `Lane ${lanePaths.lane} owners should close the design packet or clarification gap before implementation starts.`;
530
536
  case "implementationGate":
531
537
  return `Lane ${lanePaths.lane} owners should resolve the implementation contract gap before wave progression.`;
532
538
  case "componentGate":
@@ -1335,8 +1341,11 @@ export async function runLauncherCli(argv) {
1335
1341
  });
1336
1342
 
1337
1343
  const launchedImplementationRuns = runsToLaunch.filter(
1338
- (run) => !isClosureRoleAgentId(run.agent.agentId, roleBindings),
1344
+ (run) =>
1345
+ !isClosureRoleAgentId(run.agent.agentId, roleBindings) &&
1346
+ (!isDesignAgent(run.agent) || isImplementationOwningDesignAgent(run.agent)),
1339
1347
  );
1348
+ const launchedDesignRuns = runsToLaunch.filter((run) => isDesignAgent(run.agent));
1340
1349
  const closureOnlyRetry =
1341
1350
  runsToLaunch.length > 0 &&
1342
1351
  launchedImplementationRuns.length === 0 &&
@@ -1395,6 +1404,14 @@ export async function runLauncherCli(argv) {
1395
1404
  agentRateLimitBaseDelaySeconds: options.agentRateLimitBaseDelaySeconds,
1396
1405
  agentRateLimitMaxDelaySeconds: options.agentRateLimitMaxDelaySeconds,
1397
1406
  context7Enabled: options.context7Enabled,
1407
+ designExecutionMode:
1408
+ isDesignAgent(runInfo.agent)
1409
+ ? launchedImplementationRuns.some(
1410
+ (candidate) => candidate.agent.agentId === runInfo.agent.agentId,
1411
+ )
1412
+ ? "implementation-pass"
1413
+ : "design-pass"
1414
+ : null,
1398
1415
  attempt,
1399
1416
  controlPlane: {
1400
1417
  waveNumber: wave.wave,
@@ -1507,6 +1524,43 @@ export async function runLauncherCli(argv) {
1507
1524
  }
1508
1525
 
1509
1526
  if (failures.length === 0) {
1527
+ if (launchedDesignRuns.length > 0 && launchedImplementationRuns.length === 0) {
1528
+ const reducerDecision = refreshReducerSnapshot(attempt);
1529
+ const designGate = reducerDecision?.reducerState?.gateSnapshot?.designGate || null;
1530
+ const remainingImplementationRuns = agentRuns.filter(
1531
+ (run) =>
1532
+ !preCompletedAgentIds.has(run.agent.agentId) &&
1533
+ !isClosureRoleAgentId(run.agent.agentId, roleBindings) &&
1534
+ (!isDesignAgent(run.agent) || isImplementationOwningDesignAgent(run.agent)),
1535
+ );
1536
+ if (designGate?.ok && remainingImplementationRuns.length > 0) {
1537
+ recordAttemptState(lanePaths, wave.wave, attempt, "completed", {
1538
+ selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
1539
+ detail: `Design pass complete; continuing with implementation agents ${remainingImplementationRuns.map((run) => run.agent.agentId).join(", ")}.`,
1540
+ });
1541
+ recordCombinedEvent({
1542
+ message: `Design pass complete; launching implementation agents next: ${remainingImplementationRuns.map((run) => run.agent.agentId).join(", ")}.`,
1543
+ });
1544
+ appendCoordination({
1545
+ event: "wave_design_ready",
1546
+ waves: [wave.wave],
1547
+ status: "running",
1548
+ details: `next_agents=${remainingImplementationRuns.map((run) => run.agent.agentId).join(",")}`,
1549
+ actionRequested: "None",
1550
+ });
1551
+ runsToLaunch = remainingImplementationRuns;
1552
+ for (const run of runsToLaunch) {
1553
+ setWaveDashboardAgent(dashboardState, run.agent.agentId, {
1554
+ state: "pending",
1555
+ detail: "Queued after design handoff",
1556
+ });
1557
+ }
1558
+ flushDashboards();
1559
+ attempt += 1;
1560
+ traceAttempt += 1;
1561
+ continue;
1562
+ }
1563
+ }
1510
1564
  const implementationGate = readWaveImplementationGate(wave, agentRuns);
1511
1565
  if (!implementationGate.ok) {
1512
1566
  failures = [