@chllming/wave-orchestration 0.8.4 → 0.8.6

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 (60) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/README.md +31 -13
  3. package/docs/README.md +4 -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/context7/bundles.json +19 -20
  7. package/docs/context7/planner-agent/README.md +4 -1
  8. package/docs/guides/author-and-run-waves.md +27 -0
  9. package/docs/guides/planner.md +46 -0
  10. package/docs/guides/signal-wrappers.md +165 -0
  11. package/docs/guides/terminal-surfaces.md +13 -0
  12. package/docs/plans/context7-wave-orchestrator.md +24 -7
  13. package/docs/plans/current-state.md +10 -2
  14. package/docs/plans/end-state-architecture.md +22 -5
  15. package/docs/plans/examples/wave-example-design-handoff.md +262 -0
  16. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  17. package/docs/plans/migration.md +301 -75
  18. package/docs/plans/wave-orchestrator.md +16 -3
  19. package/docs/reference/cli-reference.md +22 -1
  20. package/docs/reference/npmjs-trusted-publishing.md +2 -2
  21. package/docs/reference/sample-waves.md +14 -7
  22. package/docs/reference/skills.md +18 -0
  23. package/docs/reference/wave-control.md +2 -0
  24. package/package.json +1 -1
  25. package/releases/manifest.json +38 -0
  26. package/scripts/context7-api-check.sh +57 -13
  27. package/scripts/wave-orchestrator/agent-state.mjs +64 -0
  28. package/scripts/wave-orchestrator/config.mjs +5 -0
  29. package/scripts/wave-orchestrator/control-cli.mjs +19 -0
  30. package/scripts/wave-orchestrator/coordination.mjs +77 -1
  31. package/scripts/wave-orchestrator/gate-engine.mjs +106 -2
  32. package/scripts/wave-orchestrator/install.mjs +5 -0
  33. package/scripts/wave-orchestrator/launcher-runtime.mjs +18 -1
  34. package/scripts/wave-orchestrator/launcher.mjs +75 -1
  35. package/scripts/wave-orchestrator/ledger.mjs +56 -27
  36. package/scripts/wave-orchestrator/local-executor.mjs +37 -0
  37. package/scripts/wave-orchestrator/planner.mjs +24 -4
  38. package/scripts/wave-orchestrator/result-envelope.mjs +32 -1
  39. package/scripts/wave-orchestrator/retry-control.mjs +17 -2
  40. package/scripts/wave-orchestrator/retry-engine.mjs +85 -0
  41. package/scripts/wave-orchestrator/role-helpers.mjs +73 -1
  42. package/scripts/wave-orchestrator/session-supervisor.mjs +113 -0
  43. package/scripts/wave-orchestrator/shared.mjs +2 -0
  44. package/scripts/wave-orchestrator/signals.mjs +681 -0
  45. package/scripts/wave-orchestrator/skills.mjs +1 -0
  46. package/scripts/wave-orchestrator/task-entity.mjs +65 -45
  47. package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
  48. package/scripts/wave-orchestrator/wave-files.mjs +85 -1
  49. package/scripts/wave-orchestrator/wave-state-reducer.mjs +24 -7
  50. package/scripts/wave-status.sh +200 -0
  51. package/scripts/wave-watch.sh +200 -0
  52. package/skills/README.md +10 -0
  53. package/skills/role-design/SKILL.md +50 -0
  54. package/skills/role-design/skill.json +36 -0
  55. package/skills/signal-hygiene/SKILL.md +51 -0
  56. package/skills/signal-hygiene/skill.json +20 -0
  57. package/skills/tui-design/SKILL.md +77 -0
  58. package/skills/tui-design/references/tui-design.md +259 -0
  59. package/skills/tui-design/skill.json +36 -0
  60. package/wave.config.json +15 -1
@@ -1,6 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import { toIsoTimestamp } from "./shared.mjs";
3
3
  import {
4
+ validateDesignSummary,
4
5
  validateImplementationSummary,
5
6
  validateContQaSummary,
6
7
  validateContEvalSummary,
@@ -11,6 +12,8 @@ import {
11
12
  import {
12
13
  isContEvalImplementationOwningAgent,
13
14
  isContEvalReportOnlyAgent,
15
+ isDesignAgent,
16
+ isImplementationOwningDesignAgent,
14
17
  isSecurityReviewAgent,
15
18
  } from "./role-helpers.mjs";
16
19
  import {
@@ -19,6 +22,7 @@ import {
19
22
  } from "./coordination-store.mjs";
20
23
 
21
24
  export const TASK_TYPES = new Set([
25
+ "design",
22
26
  "implementation",
23
27
  "integration",
24
28
  "documentation",
@@ -484,7 +488,8 @@ export function buildTasksFromWaveDefinition(waveDefinition, laneConfig = {}) {
484
488
 
485
489
  for (const agent of agents) {
486
490
  const agentId = agent.agentId;
487
- const taskType =
491
+ const hybridDesignAgent = isImplementationOwningDesignAgent(agent);
492
+ const baseTaskType =
488
493
  agentId === contQaAgentId
489
494
  ? "cont-qa"
490
495
  : agentId === contEvalAgentId
@@ -493,9 +498,11 @@ export function buildTasksFromWaveDefinition(waveDefinition, laneConfig = {}) {
493
498
  ? "integration"
494
499
  : agentId === documentationAgentId
495
500
  ? "documentation"
496
- : isSecurityReviewAgent(agent)
497
- ? "security"
498
- : "implementation";
501
+ : isDesignAgent(agent)
502
+ ? "design"
503
+ : isSecurityReviewAgent(agent)
504
+ ? "security"
505
+ : "implementation";
499
506
  const exitContract =
500
507
  agent.exitContract && typeof agent.exitContract === "object"
501
508
  ? { ...agent.exitContract }
@@ -509,7 +516,7 @@ export function buildTasksFromWaveDefinition(waveDefinition, laneConfig = {}) {
509
516
  const proofLevel = exitContract?.proof || "unit";
510
517
  let proofCentric = false;
511
518
  let maturityTarget = null;
512
- if (taskType === "implementation") {
519
+ if (baseTaskType === "implementation" || hybridDesignAgent) {
513
520
  if (Array.isArray(agent.deliverables) && agent.deliverables.length > 0) {
514
521
  proofCentric = true;
515
522
  }
@@ -539,48 +546,54 @@ export function buildTasksFromWaveDefinition(waveDefinition, laneConfig = {}) {
539
546
  const scope = agent.title
540
547
  ? normalizeText(agent.title).toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")
541
548
  : "primary";
542
- const semanticId = buildSemanticTaskId(waveNumber, agentId, scope);
543
-
544
- tasks.push(
545
- normalizeTask(
546
- {
547
- taskId: semanticId,
548
- taskType,
549
- title: `${agentId}: ${agent.title || ""}`.trim(),
550
- detail: agent.detail || "",
551
- ownerAgentId: agentId,
552
- assigneeAgentId: agentId,
553
- waveNumber,
554
- lane,
555
- artifactContract: {
556
- deliverables,
557
- proofArtifacts,
558
- requiredPaths: normalizeStringArray(agent.ownedPaths || []),
559
- exitContract,
560
- componentTargets,
549
+ const pushAgentTask = (taskType, taskScope, overrides = {}) => {
550
+ tasks.push(
551
+ normalizeTask(
552
+ {
553
+ taskId: buildSemanticTaskId(waveNumber, agentId, `${scope}-${taskScope}`),
554
+ taskType,
555
+ title: `${agentId}: ${agent.title || ""}`.trim(),
556
+ detail: agent.detail || "",
557
+ ownerAgentId: agentId,
558
+ assigneeAgentId: agentId,
559
+ waveNumber,
560
+ lane,
561
+ artifactContract: {
562
+ deliverables,
563
+ proofArtifacts,
564
+ requiredPaths: normalizeStringArray(agent.ownedPaths || []),
565
+ exitContract,
566
+ componentTargets,
567
+ },
568
+ proofRequirements: {
569
+ proofLevel,
570
+ proofCentric,
571
+ maturityTarget,
572
+ },
573
+ dependencyEdges: [],
574
+ components,
575
+ status: "pending",
576
+ closureState: "open",
577
+ priority: taskType === "implementation" ? "normal" : "high",
578
+ ...overrides,
561
579
  },
562
- proofRequirements: {
563
- proofLevel,
564
- proofCentric,
565
- maturityTarget,
580
+ {
581
+ createdAt: seededAt,
582
+ updatedAt: seededAt,
583
+ waveNumber,
584
+ lane,
566
585
  },
567
- dependencyEdges: [],
568
- components,
569
- status: "pending",
570
- closureState: "open",
571
- priority:
572
- taskType === "implementation"
573
- ? "normal"
574
- : "high",
575
- },
576
- {
577
- createdAt: seededAt,
578
- updatedAt: seededAt,
579
- waveNumber,
580
- lane,
581
- },
582
- ),
583
- );
586
+ ),
587
+ );
588
+ };
589
+
590
+ if (hybridDesignAgent && baseTaskType === "design") {
591
+ pushAgentTask("design", "design");
592
+ pushAgentTask("implementation", "implementation");
593
+ continue;
594
+ }
595
+
596
+ pushAgentTask(baseTaskType, baseTaskType);
584
597
  }
585
598
 
586
599
  for (const promotion of waveDefinition.componentPromotions || []) {
@@ -837,6 +850,13 @@ export function evaluateOwnedSliceProven(task, agentResult, proofBundles = []) {
837
850
  return { proven: true, reason: "Exit contract satisfied" };
838
851
  }
839
852
 
853
+ if (task.taskType === "design") {
854
+ const validation = validateDesignSummary(agent, agentResult);
855
+ return validation.ok
856
+ ? { proven: true, reason: "Design packet satisfied" }
857
+ : { proven: false, reason: validation.detail || validation.statusCode };
858
+ }
859
+
840
860
  if (task.taskType === "component") {
841
861
  // Component promotion task: validate that all relevant owners have promoted
842
862
  const componentTargets = task.artifactContract?.componentTargets || {};
@@ -13,6 +13,8 @@ export const WAVE_CONTROL_ENTITY_TYPES = new Set([
13
13
  "fact",
14
14
  "wave_run",
15
15
  "agent_run",
16
+ "wave_signal",
17
+ "agent_signal",
16
18
  "coordination_record",
17
19
  "gate",
18
20
  "artifact",
@@ -7,6 +7,7 @@ import {
7
7
  DEFAULT_CONT_EVAL_ROLE_PROMPT_PATH,
8
8
  DEFAULT_CONT_QA_AGENT_ID,
9
9
  DEFAULT_CONT_QA_ROLE_PROMPT_PATH,
10
+ DEFAULT_DESIGN_ROLE_PROMPT_PATH,
10
11
  DEFAULT_DOCUMENTATION_AGENT_ID,
11
12
  DEFAULT_DOCUMENTATION_ROLE_PROMPT_PATH,
12
13
  DEFAULT_INTEGRATION_AGENT_ID,
@@ -60,6 +61,11 @@ import {
60
61
  isContEvalReportOnlyAgent,
61
62
  isContEvalReportPath,
62
63
  isContQaReportPath,
64
+ isDesignAgent,
65
+ isDocsOnlyDesignAgent,
66
+ isImplementationOwningDesignAgent,
67
+ isDesignRolePromptPath,
68
+ resolveDesignReportPath,
63
69
  isSecurityRolePromptPath,
64
70
  isSecurityReviewAgent,
65
71
  resolveSecurityReviewReportPath,
@@ -77,6 +83,7 @@ export const WAVE_CONT_EVAL_ROLE_PROMPT_PATH = DEFAULT_CONT_EVAL_ROLE_PROMPT_PAT
77
83
  export const WAVE_INTEGRATION_ROLE_PROMPT_PATH = DEFAULT_INTEGRATION_ROLE_PROMPT_PATH;
78
84
  export const WAVE_DOCUMENTATION_ROLE_PROMPT_PATH = DEFAULT_DOCUMENTATION_ROLE_PROMPT_PATH;
79
85
  export const WAVE_SECURITY_ROLE_PROMPT_PATH = DEFAULT_SECURITY_ROLE_PROMPT_PATH;
86
+ export const WAVE_DESIGN_ROLE_PROMPT_PATH = DEFAULT_DESIGN_ROLE_PROMPT_PATH;
80
87
  export const SHARED_PLAN_DOC_PATHS = [
81
88
  "docs/plans/current-state.md",
82
89
  "docs/plans/master-plan.md",
@@ -113,6 +120,10 @@ function resolveSecurityRolePromptPath(laneProfile) {
113
120
  return laneProfile?.roles?.securityRolePromptPath || DEFAULT_SECURITY_ROLE_PROMPT_PATH;
114
121
  }
115
122
 
123
+ function resolveDesignRolePromptPath(laneProfile) {
124
+ return laneProfile?.roles?.designRolePromptPath || DEFAULT_DESIGN_ROLE_PROMPT_PATH;
125
+ }
126
+
116
127
  function normalizeSecurityCapabilities(capabilities, rolePromptPaths, securityRolePromptPath) {
117
128
  const normalized = Array.isArray(capabilities) ? [...capabilities] : [];
118
129
  const hasSecurityRolePrompt = Array.isArray(rolePromptPaths)
@@ -129,6 +140,22 @@ function normalizeSecurityCapabilities(capabilities, rolePromptPaths, securityRo
129
140
  return normalized;
130
141
  }
131
142
 
143
+ function normalizeDesignCapabilities(capabilities, rolePromptPaths, designRolePromptPath) {
144
+ const normalized = Array.isArray(capabilities) ? [...capabilities] : [];
145
+ const hasDesignRolePrompt = Array.isArray(rolePromptPaths)
146
+ ? rolePromptPaths.some((rolePromptPath) =>
147
+ isDesignRolePromptPath(rolePromptPath, designRolePromptPath),
148
+ )
149
+ : false;
150
+ if (
151
+ hasDesignRolePrompt &&
152
+ !normalized.some((capability) => String(capability || "").trim().toLowerCase() === "design")
153
+ ) {
154
+ normalized.push("design");
155
+ }
156
+ return normalized;
157
+ }
158
+
132
159
  export function waveNumberFromFileName(fileName) {
133
160
  const match = fileName.match(/^wave-(\d+)\.md$/);
134
161
  if (!match) {
@@ -1166,12 +1193,14 @@ function isImplementationOwningWaveAgent(
1166
1193
  contEvalAgentId,
1167
1194
  integrationAgentId,
1168
1195
  documentationAgentId,
1196
+ designRolePromptPath,
1169
1197
  securityRolePromptPath,
1170
1198
  },
1171
1199
  ) {
1172
1200
  return (
1173
1201
  ![contQaAgentId, integrationAgentId, documentationAgentId].includes(agent.agentId) &&
1174
1202
  !isContEvalReportOnlyAgent(agent, { contEvalAgentId }) &&
1203
+ !isDocsOnlyDesignAgent(agent, { designRolePromptPath }) &&
1175
1204
  !isSecurityReviewAgent(agent, { securityRolePromptPath })
1176
1205
  );
1177
1206
  }
@@ -1184,6 +1213,12 @@ function resolveAgentSummaryReportPath(wave, agentId, { contQaAgentId, contEvalA
1184
1213
  return path.resolve(REPO_ROOT, wave.contEvalReportPath);
1185
1214
  }
1186
1215
  const agent = wave?.agents?.find((entry) => entry.agentId === agentId);
1216
+ if (isDesignAgent(agent)) {
1217
+ const designReportPath = resolveDesignReportPath(agent);
1218
+ if (designReportPath) {
1219
+ return path.resolve(REPO_ROOT, designReportPath);
1220
+ }
1221
+ }
1187
1222
  if (isSecurityReviewAgent(agent)) {
1188
1223
  const securityReportPath = resolveSecurityReviewReportPath(agent);
1189
1224
  if (securityReportPath) {
@@ -1387,6 +1422,7 @@ export function validateWaveDefinition(wave, options = {}) {
1387
1422
  const documentationAgentId =
1388
1423
  laneProfile.roles.documentationAgentId || DEFAULT_DOCUMENTATION_AGENT_ID;
1389
1424
  const securityRolePromptPath = resolveSecurityRolePromptPath(laneProfile);
1425
+ const designRolePromptPath = resolveDesignRolePromptPath(laneProfile);
1390
1426
  const documentationThreshold = laneProfile.validation.requireDocumentationStewardFromWave;
1391
1427
  const context7Threshold = laneProfile.validation.requireContext7DeclarationsFromWave;
1392
1428
  const exitContractThreshold = laneProfile.validation.requireExitContractsFromWave;
@@ -1437,6 +1473,7 @@ export function validateWaveDefinition(wave, options = {}) {
1437
1473
  contEvalAgentId,
1438
1474
  integrationAgentId,
1439
1475
  documentationAgentId,
1476
+ designRolePromptPath,
1440
1477
  securityRolePromptPath,
1441
1478
  }),
1442
1479
  );
@@ -1497,6 +1534,10 @@ export function validateWaveDefinition(wave, options = {}) {
1497
1534
  }
1498
1535
  }
1499
1536
  for (const agent of wave.agents) {
1537
+ const docsOnlyDesignAgent = isDocsOnlyDesignAgent(agent, { designRolePromptPath });
1538
+ const implementationOwningDesignAgent = isImplementationOwningDesignAgent(agent, {
1539
+ designRolePromptPath,
1540
+ });
1500
1541
  if (!Array.isArray(agent.ownedPaths) || agent.ownedPaths.length === 0) {
1501
1542
  errors.push(`Agent ${agent.agentId} must declare at least one owned path`);
1502
1543
  continue;
@@ -1556,6 +1597,7 @@ export function validateWaveDefinition(wave, options = {}) {
1556
1597
  if (
1557
1598
  [contQaAgentId, integrationAgentId, documentationAgentId].includes(agent.agentId) ||
1558
1599
  isContEvalReportOnlyAgent(agent, { contEvalAgentId }) ||
1600
+ docsOnlyDesignAgent ||
1559
1601
  isSecurityReviewAgent(agent, { securityRolePromptPath })
1560
1602
  ) {
1561
1603
  if (Array.isArray(agent.components) && agent.components.length > 0) {
@@ -1587,6 +1629,7 @@ export function validateWaveDefinition(wave, options = {}) {
1587
1629
  if (
1588
1630
  ![contQaAgentId, integrationAgentId, documentationAgentId].includes(agent.agentId) &&
1589
1631
  !isContEvalReportOnlyAgent(agent, { contEvalAgentId }) &&
1632
+ !docsOnlyDesignAgent &&
1590
1633
  !isSecurityReviewAgent(agent, { securityRolePromptPath })
1591
1634
  ) {
1592
1635
  if (!agent.exitContract) {
@@ -1608,6 +1651,7 @@ export function validateWaveDefinition(wave, options = {}) {
1608
1651
  (!Array.isArray(agent.proofArtifacts) || agent.proofArtifacts.length === 0) &&
1609
1652
  ![contQaAgentId, integrationAgentId, documentationAgentId].includes(agent.agentId) &&
1610
1653
  !isContEvalReportOnlyAgent(agent, { contEvalAgentId }) &&
1654
+ !docsOnlyDesignAgent &&
1611
1655
  !isSecurityReviewAgent(agent, { securityRolePromptPath })
1612
1656
  ) {
1613
1657
  errors.push(
@@ -1675,6 +1719,29 @@ export function validateWaveDefinition(wave, options = {}) {
1675
1719
  errors.push(`Security reviewer ${securityReviewer.agentId} must own a security review report path`);
1676
1720
  }
1677
1721
  }
1722
+ const designAgents = wave.agents.filter((agent) =>
1723
+ isDesignAgent(agent, { designRolePromptPath }),
1724
+ );
1725
+ for (const designAgent of designAgents) {
1726
+ const hybridDesignAgent = isImplementationOwningDesignAgent(designAgent, {
1727
+ designRolePromptPath,
1728
+ });
1729
+ if (!designAgent.rolePromptPaths?.includes(designRolePromptPath)) {
1730
+ errors.push(
1731
+ `Design agent ${designAgent.agentId} must import ${designRolePromptPath}`,
1732
+ );
1733
+ }
1734
+ if (!resolveDesignReportPath(designAgent)) {
1735
+ errors.push(`Design agent ${designAgent.agentId} must own a design packet path`);
1736
+ }
1737
+ if (
1738
+ !hybridDesignAgent &&
1739
+ Array.isArray(designAgent.components) &&
1740
+ designAgent.components.length > 0
1741
+ ) {
1742
+ errors.push(`Design agent ${designAgent.agentId} must stay docs/spec-only unless it explicitly owns implementation files`);
1743
+ }
1744
+ }
1678
1745
  if (integrationRuleActive) {
1679
1746
  const integrationStewards = wave.agents.filter((agent) =>
1680
1747
  agent.rolePromptPaths?.includes(laneProfile.roles.integrationRolePromptPath),
@@ -1749,6 +1816,7 @@ export function validateWaveDefinition(wave, options = {}) {
1749
1816
  export function parseWaveContent(content, filePath, options = {}) {
1750
1817
  const laneProfile = resolveLaneProfileForOptions(options);
1751
1818
  const securityRolePromptPath = resolveSecurityRolePromptPath(laneProfile);
1819
+ const designRolePromptPath = resolveDesignRolePromptPath(laneProfile);
1752
1820
  const fileName = path.basename(filePath);
1753
1821
  const waveNumber = waveNumberFromFileName(fileName);
1754
1822
  const commitMessageMatch = content.match(/\*\*Commit message\*\*:\s*`([^`]+)`/);
@@ -1778,7 +1846,11 @@ export function parseWaveContent(content, filePath, options = {}) {
1778
1846
  const executorConfig = extractExecutorConfigFromSection(sectionText, filePath, current.agentId);
1779
1847
  const components = extractAgentComponentsFromSection(sectionText, filePath, current.agentId);
1780
1848
  const capabilities = normalizeSecurityCapabilities(
1781
- extractAgentCapabilitiesFromSection(sectionText, filePath, current.agentId),
1849
+ normalizeDesignCapabilities(
1850
+ extractAgentCapabilitiesFromSection(sectionText, filePath, current.agentId),
1851
+ rolePromptPaths,
1852
+ designRolePromptPath,
1853
+ ),
1782
1854
  rolePromptPaths,
1783
1855
  securityRolePromptPath,
1784
1856
  );
@@ -1923,6 +1995,13 @@ function inferAgentRuntimeRole(agent, laneProfile) {
1923
1995
  if (agent?.agentId === laneProfile.roles.documentationAgentId) {
1924
1996
  return "documentation";
1925
1997
  }
1998
+ if (
1999
+ isDesignAgent(agent, {
2000
+ designRolePromptPath: laneProfile?.roles?.designRolePromptPath,
2001
+ })
2002
+ ) {
2003
+ return "design";
2004
+ }
1926
2005
  if (isSecurityReviewAgent(agent)) {
1927
2006
  return "security";
1928
2007
  }
@@ -2274,12 +2353,14 @@ export function validateWaveComponentPromotions(wave, summariesByAgentId = {}, o
2274
2353
  const documentationAgentId =
2275
2354
  roles.documentationAgentId || DEFAULT_DOCUMENTATION_AGENT_ID;
2276
2355
  const securityRolePromptPath = resolveSecurityRolePromptPath(laneProfile);
2356
+ const designRolePromptPath = resolveDesignRolePromptPath(laneProfile);
2277
2357
  const implementationOwningAgents = (wave.agents || []).filter((agent) =>
2278
2358
  isImplementationOwningWaveAgent(agent, {
2279
2359
  contQaAgentId,
2280
2360
  contEvalAgentId,
2281
2361
  integrationAgentId,
2282
2362
  documentationAgentId,
2363
+ designRolePromptPath,
2283
2364
  securityRolePromptPath,
2284
2365
  }),
2285
2366
  );
@@ -2302,6 +2383,7 @@ export function validateWaveComponentPromotions(wave, summariesByAgentId = {}, o
2302
2383
  contEvalAgentId,
2303
2384
  integrationAgentId,
2304
2385
  documentationAgentId,
2386
+ designRolePromptPath,
2305
2387
  securityRolePromptPath,
2306
2388
  })) {
2307
2389
  continue;
@@ -2348,12 +2430,14 @@ export function validateWaveComponentMatrixCurrentLevels(wave, options = {}) {
2348
2430
  const integrationAgentId = roles.integrationAgentId || DEFAULT_INTEGRATION_AGENT_ID;
2349
2431
  const documentationAgentId = roles.documentationAgentId || DEFAULT_DOCUMENTATION_AGENT_ID;
2350
2432
  const securityRolePromptPath = resolveSecurityRolePromptPath(laneProfile);
2433
+ const designRolePromptPath = resolveDesignRolePromptPath(laneProfile);
2351
2434
  const implementationOwningAgents = (wave.agents || []).filter((agent) =>
2352
2435
  isImplementationOwningWaveAgent(agent, {
2353
2436
  contQaAgentId,
2354
2437
  contEvalAgentId,
2355
2438
  integrationAgentId,
2356
2439
  documentationAgentId,
2440
+ designRolePromptPath,
2357
2441
  securityRolePromptPath,
2358
2442
  }),
2359
2443
  );
@@ -181,6 +181,18 @@ function derivePhase({
181
181
  const implementationTasks = (tasks || []).filter(
182
182
  (task) => task.taskType === "implementation",
183
183
  );
184
+ const designTasks = (tasks || []).filter(
185
+ (task) => task.taskType === "design",
186
+ );
187
+ const allDesignReady = designTasks.every(
188
+ (task) =>
189
+ task.closureState === "owned_slice_proven" ||
190
+ task.closureState === "wave_closure_ready" ||
191
+ task.closureState === "closed",
192
+ );
193
+ if (!allDesignReady && designTasks.length > 0) {
194
+ return "design";
195
+ }
184
196
  const allImplementationProven = implementationTasks.every(
185
197
  (task) =>
186
198
  task.closureState === "owned_slice_proven" ||
@@ -191,6 +203,9 @@ function derivePhase({
191
203
  return "running";
192
204
  }
193
205
 
206
+ if (gateSnapshot?.designGate && !gateSnapshot.designGate.ok) {
207
+ return "design";
208
+ }
194
209
  if (gateSnapshot?.contEvalGate && !gateSnapshot.contEvalGate.ok) {
195
210
  return "cont-eval";
196
211
  }
@@ -228,6 +243,7 @@ function deriveWaveState(phase) {
228
243
  */
229
244
  function buildProofAvailability(tasks, agentResults, controlPlaneState) {
230
245
  const byAgentId = {};
246
+ const byTaskId = {};
231
247
  const agentTasks = new Map();
232
248
 
233
249
  for (const task of tasks || []) {
@@ -267,6 +283,10 @@ function buildProofAvailability(tasks, agentResults, controlPlaneState) {
267
283
 
268
284
  for (const task of agentTaskList) {
269
285
  const evaluation = evaluateOwnedSliceProven(task, result);
286
+ byTaskId[task.taskId] = {
287
+ proven: evaluation.proven,
288
+ reason: evaluation.reason || null,
289
+ };
270
290
  if (!evaluation.proven) {
271
291
  ownedSliceProven = false;
272
292
  }
@@ -302,6 +322,7 @@ function buildProofAvailability(tasks, agentResults, controlPlaneState) {
302
322
 
303
323
  return {
304
324
  byAgentId,
325
+ byTaskId,
305
326
  allOwnedSlicesProven,
306
327
  activeProofBundles,
307
328
  };
@@ -518,20 +539,16 @@ function deriveClosureEligibility(gateSnapshot, tasks, proofAvailability) {
518
539
  */
519
540
  function applyProofAvailabilityToTasks(tasks, proofAvailability) {
520
541
  return (tasks || []).map((task) => {
521
- const agentId = task.assigneeAgentId || task.ownerAgentId;
522
- if (!agentId) {
523
- return task;
524
- }
525
- const entry = proofAvailability.byAgentId?.[agentId];
542
+ const entry = proofAvailability.byTaskId?.[task.taskId];
526
543
  if (!entry) {
527
544
  return task;
528
545
  }
529
546
  // Forward: open -> owned_slice_proven when proven
530
- if (task.closureState === "open" && entry.ownedSliceProven) {
547
+ if (task.closureState === "open" && entry.proven) {
531
548
  return { ...task, closureState: "owned_slice_proven", status: "proven" };
532
549
  }
533
550
  // Bidirectional: owned_slice_proven -> open when proof is invalidated
534
- if (task.closureState === "owned_slice_proven" && !entry.ownedSliceProven) {
551
+ if (task.closureState === "owned_slice_proven" && !entry.proven) {
535
552
  return { ...task, closureState: "open", status: "in_progress" };
536
553
  }
537
554
  return task;
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ lane="main"
5
+ wave=""
6
+ agent=""
7
+ run_id=""
8
+ dry_run="0"
9
+ json_output="0"
10
+
11
+ usage() {
12
+ cat <<'EOF'
13
+ Usage:
14
+ scripts/wave-status.sh [--lane <lane>] [--wave <n>] [--agent <id>] [--run <id>] [--dry-run] [--json]
15
+
16
+ Exit codes:
17
+ 0 completed
18
+ 10 waiting or running
19
+ 20 input required
20
+ 40 failed
21
+ EOF
22
+ }
23
+
24
+ while [ "$#" -gt 0 ]; do
25
+ case "$1" in
26
+ --lane)
27
+ lane="${2:-}"
28
+ shift 2
29
+ ;;
30
+ --wave)
31
+ wave="${2:-}"
32
+ shift 2
33
+ ;;
34
+ --agent)
35
+ agent="${2:-}"
36
+ shift 2
37
+ ;;
38
+ --run)
39
+ run_id="${2:-}"
40
+ shift 2
41
+ ;;
42
+ --dry-run)
43
+ dry_run="1"
44
+ shift
45
+ ;;
46
+ --json)
47
+ json_output="1"
48
+ shift
49
+ ;;
50
+ --help|-h)
51
+ usage
52
+ exit 0
53
+ ;;
54
+ *)
55
+ echo "Unknown argument: $1" >&2
56
+ usage >&2
57
+ exit 2
58
+ ;;
59
+ esac
60
+ done
61
+
62
+ run_wave_cli() {
63
+ if [ -n "${WAVE_WRAPPER_ENTRY:-}" ]; then
64
+ node "$WAVE_WRAPPER_ENTRY" "$@"
65
+ return
66
+ fi
67
+ if [ -f "scripts/wave.mjs" ]; then
68
+ node "scripts/wave.mjs" "$@"
69
+ return
70
+ fi
71
+ if [ -f "node_modules/@chllming/wave-orchestration/scripts/wave.mjs" ]; then
72
+ node "node_modules/@chllming/wave-orchestration/scripts/wave.mjs" "$@"
73
+ return
74
+ fi
75
+ if command -v pnpm >/dev/null 2>&1; then
76
+ pnpm exec wave "$@"
77
+ return
78
+ fi
79
+ echo "Unable to locate Wave CLI. Set WAVE_WRAPPER_ENTRY or install the package locally." >&2
80
+ exit 2
81
+ }
82
+
83
+ infer_wave() {
84
+ node - "$lane" "$run_id" "$dry_run" <<'NODE'
85
+ const fs = require("node:fs");
86
+ const path = require("node:path");
87
+
88
+ const lane = process.argv[2] || "main";
89
+ const runId = process.argv[3] || "";
90
+ const dryRun = process.argv[4] === "1";
91
+ const stateDir = runId
92
+ ? path.join(process.cwd(), ".tmp", `${lane}-wave-launcher`, "adhoc", runId, dryRun ? "dry-run" : "")
93
+ : path.join(process.cwd(), ".tmp", `${lane}-wave-launcher`, dryRun ? "dry-run" : "");
94
+ const runStatePath = path.join(stateDir, "run-state.json");
95
+ let payload = null;
96
+ try {
97
+ payload = JSON.parse(fs.readFileSync(runStatePath, "utf8"));
98
+ } catch {
99
+ payload = null;
100
+ }
101
+ const waves = Object.values(payload?.waves || {})
102
+ .filter((entry) => entry && typeof entry === "object")
103
+ .map((entry) => ({
104
+ wave: Number.parseInt(String(entry.wave ?? ""), 10),
105
+ state: String(entry.currentState || "").trim().toLowerCase(),
106
+ }))
107
+ .filter((entry) => Number.isFinite(entry.wave))
108
+ .sort((left, right) => left.wave - right.wave);
109
+ const active = waves.findLast(
110
+ (entry) => !["completed", "failed", "timed_out", "timed-out"].includes(entry.state),
111
+ );
112
+ if (active) {
113
+ process.stdout.write(String(active.wave));
114
+ process.exit(0);
115
+ }
116
+ const completed = Array.isArray(payload?.completedWaves)
117
+ ? payload.completedWaves
118
+ .map((value) => Number.parseInt(String(value), 10))
119
+ .filter((value) => Number.isFinite(value))
120
+ .sort((left, right) => left - right)
121
+ : [];
122
+ process.stdout.write(String(completed.at(-1) ?? 0));
123
+ NODE
124
+ }
125
+
126
+ if [ -z "$wave" ]; then
127
+ wave="$(infer_wave)"
128
+ fi
129
+
130
+ status_args=(control status --lane "$lane" --wave "$wave" --json)
131
+ if [ -n "$agent" ]; then
132
+ status_args+=(--agent "$agent")
133
+ fi
134
+ if [ -n "$run_id" ]; then
135
+ status_args+=(--run "$run_id")
136
+ fi
137
+ if [ "$dry_run" = "1" ]; then
138
+ status_args+=(--dry-run)
139
+ fi
140
+
141
+ payload="$(run_wave_cli "${status_args[@]}")"
142
+
143
+ if [ "$json_output" = "1" ]; then
144
+ printf '%s\n' "$payload"
145
+ exit 0
146
+ fi
147
+
148
+ PAYLOAD="$payload" node - "$lane" "$wave" "$agent" <<'NODE'
149
+ const fs = require("node:fs");
150
+
151
+ const lane = process.argv[2] || "main";
152
+ const wave = Number.parseInt(String(process.argv[3] || "0"), 10) || 0;
153
+ const agentId = String(process.argv[4] || "").trim();
154
+ const payload = JSON.parse(process.env.PAYLOAD || "{}");
155
+ const signals = payload?.signals || {};
156
+ const snapshot = agentId
157
+ ? (Array.isArray(signals.agents) ? signals.agents.find((entry) => entry.agentId === agentId) : null)
158
+ : signals.wave;
159
+ const effective = snapshot || {
160
+ signal: payload?.blockingEdge?.kind === "human-input" ? "feedback-requested" : "waiting",
161
+ lane,
162
+ wave,
163
+ phase: payload?.phase || "unknown",
164
+ status: payload?.blockingEdge ? "blocked" : "running",
165
+ blocking: payload?.blockingEdge || null,
166
+ attempt: payload?.activeAttempt?.attemptNumber || 0,
167
+ targetAgentIds: agentId ? [agentId] : [],
168
+ shouldWake: agentId ? true : null,
169
+ version: 0,
170
+ };
171
+ const targetKey = agentId ? "agent" : "agents";
172
+ const targetValue = agentId || (effective.targetAgentIds || []).join(",") || "none";
173
+ const blocking = effective?.blocking?.kind || "none";
174
+ const shouldWake =
175
+ typeof effective.shouldWake === "boolean" ? (effective.shouldWake ? "yes" : "no") : "n/a";
176
+ console.log(
177
+ [
178
+ `signal=${effective.signal || "waiting"}`,
179
+ `lane=${lane}`,
180
+ `wave=${wave}`,
181
+ `phase=${effective.phase || "unknown"}`,
182
+ `status=${effective.status || "running"}`,
183
+ `blocking=${blocking}`,
184
+ `attempt=${effective.attempt || 0}`,
185
+ `${targetKey}=${targetValue}`,
186
+ `version=${effective.version || 0}`,
187
+ `should_wake=${shouldWake}`,
188
+ ].join(" "),
189
+ );
190
+ if (String(effective.signal || "").trim().toLowerCase() === "completed") {
191
+ process.exit(0);
192
+ }
193
+ if (String(effective.signal || "").trim().toLowerCase() === "failed") {
194
+ process.exit(40);
195
+ }
196
+ if (String(effective.signal || "").trim().toLowerCase() === "feedback-requested") {
197
+ process.exit(20);
198
+ }
199
+ process.exit(10);
200
+ NODE