@chllming/wave-orchestration 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/README.md +60 -11
  3. package/docs/README.md +8 -2
  4. package/docs/agents/wave-design-role.md +47 -0
  5. package/docs/concepts/what-is-a-wave.md +11 -7
  6. package/docs/guides/author-and-run-waves.md +24 -0
  7. package/docs/guides/planner.md +44 -0
  8. package/docs/plans/architecture-hardening-migration.md +8 -1
  9. package/docs/plans/current-state.md +19 -7
  10. package/docs/plans/end-state-architecture.md +88 -70
  11. package/docs/plans/examples/wave-example-design-handoff.md +262 -0
  12. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  13. package/docs/plans/migration.md +370 -64
  14. package/docs/plans/wave-orchestrator.md +49 -13
  15. package/docs/reference/cli-reference.md +46 -14
  16. package/docs/reference/coordination-and-closure.md +19 -6
  17. package/docs/reference/npmjs-trusted-publishing.md +5 -4
  18. package/docs/reference/sample-waves.md +14 -7
  19. package/docs/reference/skills.md +10 -0
  20. package/package.json +1 -1
  21. package/releases/manifest.json +39 -0
  22. package/scripts/wave-orchestrator/agent-state.mjs +64 -491
  23. package/scripts/wave-orchestrator/autonomous.mjs +10 -6
  24. package/scripts/wave-orchestrator/{launcher-closure.mjs → closure-engine.mjs} +190 -74
  25. package/scripts/wave-orchestrator/config.mjs +5 -0
  26. package/scripts/wave-orchestrator/coordination.mjs +42 -1
  27. package/scripts/wave-orchestrator/{launcher-derived-state.mjs → derived-state-engine.mjs} +34 -146
  28. package/scripts/wave-orchestrator/{launcher-gates.mjs → gate-engine.mjs} +501 -141
  29. package/scripts/wave-orchestrator/human-input-resolution.mjs +14 -10
  30. package/scripts/wave-orchestrator/human-input-workflow.mjs +104 -0
  31. package/scripts/wave-orchestrator/implementation-engine.mjs +120 -0
  32. package/scripts/wave-orchestrator/install.mjs +3 -0
  33. package/scripts/wave-orchestrator/launcher-runtime.mjs +11 -6
  34. package/scripts/wave-orchestrator/launcher.mjs +324 -723
  35. package/scripts/wave-orchestrator/ledger.mjs +56 -27
  36. package/scripts/wave-orchestrator/local-executor.mjs +37 -0
  37. package/scripts/wave-orchestrator/planner.mjs +24 -4
  38. package/scripts/wave-orchestrator/projection-writer.mjs +256 -0
  39. package/scripts/wave-orchestrator/reconcile-format.mjs +32 -0
  40. package/scripts/wave-orchestrator/reducer-snapshot.mjs +297 -0
  41. package/scripts/wave-orchestrator/replay.mjs +3 -1
  42. package/scripts/wave-orchestrator/result-envelope.mjs +620 -0
  43. package/scripts/wave-orchestrator/retry-control.mjs +22 -2
  44. package/scripts/wave-orchestrator/{launcher-retry.mjs → retry-engine.mjs} +352 -18
  45. package/scripts/wave-orchestrator/role-helpers.mjs +124 -1
  46. package/scripts/wave-orchestrator/{launcher-supervisor.mjs → session-supervisor.mjs} +178 -103
  47. package/scripts/wave-orchestrator/shared.mjs +2 -0
  48. package/scripts/wave-orchestrator/skills.mjs +1 -0
  49. package/scripts/wave-orchestrator/task-entity.mjs +65 -45
  50. package/scripts/wave-orchestrator/traces.mjs +10 -1
  51. package/scripts/wave-orchestrator/wave-files.mjs +96 -10
  52. package/scripts/wave-orchestrator/wave-state-reducer.mjs +76 -12
  53. package/skills/README.md +7 -0
  54. package/skills/role-design/SKILL.md +50 -0
  55. package/skills/role-design/skill.json +36 -0
  56. package/skills/tui-design/SKILL.md +77 -0
  57. package/skills/tui-design/references/tui-design.md +259 -0
  58. package/skills/tui-design/skill.json +36 -0
  59. package/wave.config.json +15 -1
@@ -8,7 +8,7 @@ import {
8
8
  readClarificationBarrier,
9
9
  readWaveAssignmentBarrier,
10
10
  readWaveDependencyBarrier,
11
- } from "./launcher-gates.mjs";
11
+ } from "./gate-engine.mjs";
12
12
  import {
13
13
  isOpenCoordinationStatus,
14
14
  openClarificationLinkedRequests,
@@ -20,6 +20,7 @@ import {
20
20
  } from "./shared.mjs";
21
21
  import {
22
22
  readAgentExecutionSummary,
23
+ validateDesignSummary,
23
24
  validateImplementationSummary,
24
25
  } from "./agent-state.mjs";
25
26
  import {
@@ -32,7 +33,11 @@ import {
32
33
  } from "./proof-registry.mjs";
33
34
  import { hashAgentPromptFingerprint } from "./context7.mjs";
34
35
  import {
36
+ isDocsOnlyDesignAgent,
37
+ isDesignAgent,
38
+ isImplementationOwningDesignAgent,
35
39
  isSecurityReviewAgent,
40
+ resolveWaveRoleBindings,
36
41
  } from "./role-helpers.mjs";
37
42
  import {
38
43
  commandForExecutor,
@@ -121,6 +126,7 @@ export function persistedRelaunchPlanMatchesCurrentState(
121
126
  }
122
127
  const componentGate = readWaveComponentGate(waveDefinition, agentRuns, {
123
128
  laneProfile: lanePaths?.laneProfile,
129
+ mode: "live",
124
130
  });
125
131
  if (componentGate?.statusCode !== "shared-component-sibling-pending") {
126
132
  return true;
@@ -271,7 +277,10 @@ export function reconcileFailuresAgainstSharedComponentState(wave, agentRuns, fa
271
277
  return failures;
272
278
  }
273
279
  const summariesByAgentId = Object.fromEntries(
274
- (agentRuns || []).map((runInfo) => [runInfo.agent.agentId, readRunExecutionSummary(runInfo, wave)]),
280
+ (agentRuns || []).map((runInfo) => [
281
+ runInfo.agent.agentId,
282
+ readRunExecutionSummary(runInfo, wave, { mode: "live" }),
283
+ ]),
275
284
  );
276
285
  const failureAgentIds = new Set(failures.map((failure) => failure.agentId).filter(Boolean));
277
286
  const consumedSatisfiedAgentIds = new Set();
@@ -310,6 +319,28 @@ export function hasReusableSuccessStatus(agent, statusPath, options = {}) {
310
319
  if (!basicReuseOk) {
311
320
  return false;
312
321
  }
322
+ if (isDocsOnlyDesignAgent(agent) || isImplementationOwningDesignAgent(agent)) {
323
+ const summary = readAgentExecutionSummary(statusPath, {
324
+ agent,
325
+ statusPath,
326
+ statusRecord,
327
+ logPath: options.logPath || null,
328
+ reportPath: options.reportPath || null,
329
+ });
330
+ if (!summary) {
331
+ return false;
332
+ }
333
+ const effectiveSummary = options.proofRegistry
334
+ ? augmentSummaryWithProofRegistry(agent, summary, options.proofRegistry)
335
+ : summary;
336
+ if (isDocsOnlyDesignAgent(agent)) {
337
+ return validateDesignSummary(agent, effectiveSummary).ok;
338
+ }
339
+ return (
340
+ validateDesignSummary(agent, effectiveSummary).ok &&
341
+ validateImplementationSummary(agent, effectiveSummary).ok
342
+ );
343
+ }
313
344
  const proofCentric =
314
345
  agentRequiresProofCentricValidation(agent) || waveRequiresProofCentricValidation(options.wave);
315
346
  if (!proofCentric) {
@@ -337,13 +368,43 @@ export function hasReusableSuccessStatus(agent, statusPath, options = {}) {
337
368
  return true;
338
369
  }
339
370
 
340
- function isClosureAgentId(agent, lanePaths) {
341
- return [
342
- lanePaths.contEvalAgentId || "E0",
343
- lanePaths.integrationAgentId || "A8",
344
- lanePaths.documentationAgentId || "A9",
345
- lanePaths.contQaAgentId || "A0",
346
- ].includes(agent?.agentId) || isSecurityReviewAgent(agent);
371
+ function hasReusableDesignPassStatus(agent, statusPath, options = {}) {
372
+ if (!isDesignAgent(agent)) {
373
+ return hasReusableSuccessStatus(agent, statusPath, options);
374
+ }
375
+ const statusRecord = readStatusRecordIfPresent(statusPath);
376
+ const basicReuseOk = Boolean(
377
+ statusRecord && statusRecord.code === 0 && statusRecord.promptHash === hashAgentPromptFingerprint(agent),
378
+ );
379
+ if (!basicReuseOk) {
380
+ return false;
381
+ }
382
+ const summary = readAgentExecutionSummary(statusPath, {
383
+ agent: {
384
+ ...agent,
385
+ exitContract: null,
386
+ components: [],
387
+ componentTargets: {},
388
+ deliverables: [],
389
+ proofArtifacts: [],
390
+ },
391
+ statusPath,
392
+ statusRecord,
393
+ logPath: options.logPath || null,
394
+ reportPath: options.reportPath || null,
395
+ });
396
+ if (!summary) {
397
+ return false;
398
+ }
399
+ return validateDesignSummary(agent, summary).ok;
400
+ }
401
+
402
+ function isClosureAgentId(agent, lanePaths, waveDefinition = null) {
403
+ return (
404
+ resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents).closureAgentIds.includes(
405
+ agent?.agentId,
406
+ ) || isSecurityReviewAgent(agent)
407
+ );
347
408
  }
348
409
 
349
410
  export function selectReusablePreCompletedAgentIds(
@@ -357,7 +418,7 @@ export function selectReusablePreCompletedAgentIds(
357
418
  .filter(
358
419
  (run) =>
359
420
  !retryOverrideClearedAgentIds.has(run.agent.agentId) &&
360
- !isClosureAgentId(run.agent, lanePaths) &&
421
+ !isClosureAgentId(run.agent, lanePaths, wave) &&
361
422
  hasReusableSuccessStatus(run.agent, run.statusPath, {
362
423
  wave,
363
424
  derivedState,
@@ -369,10 +430,27 @@ export function selectReusablePreCompletedAgentIds(
369
430
  );
370
431
  }
371
432
 
372
- export function selectInitialWaveRuns(agentRuns, lanePaths) {
433
+ export function selectInitialWaveRuns(agentRuns, lanePaths, waveDefinition = null) {
373
434
  const implementationRuns = (agentRuns || []).filter(
374
- (run) => !isClosureAgentId(run?.agent, lanePaths),
435
+ (run) => !isClosureAgentId(run?.agent, lanePaths, waveDefinition),
436
+ );
437
+ const pendingDesignRuns = implementationRuns.filter(
438
+ (run) =>
439
+ isDesignAgent(run.agent) &&
440
+ !hasReusableDesignPassStatus(run.agent, run.statusPath, {
441
+ wave: waveDefinition,
442
+ logPath: run.logPath,
443
+ }),
375
444
  );
445
+ if (pendingDesignRuns.length > 0) {
446
+ return pendingDesignRuns;
447
+ }
448
+ const implementationFanoutRuns = implementationRuns.filter(
449
+ (run) => !isDocsOnlyDesignAgent(run.agent),
450
+ );
451
+ if (implementationFanoutRuns.length > 0) {
452
+ return implementationFanoutRuns;
453
+ }
376
454
  return implementationRuns.length > 0 ? implementationRuns : agentRuns;
377
455
  }
378
456
 
@@ -560,7 +638,38 @@ function retryBarrierFromOutcomes(outcomes, failures) {
560
638
  };
561
639
  }
562
640
 
563
- export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths, waveDefinition = null) {
641
+ function runsFromAgentIds(agentRuns, agentIds) {
642
+ const runsByAgentId = new Map((agentRuns || []).map((run) => [run.agent.agentId, run]));
643
+ return Array.from(new Set((agentIds || []).filter(Boolean)))
644
+ .map((agentId) => runsByAgentId.get(agentId))
645
+ .filter(Boolean);
646
+ }
647
+
648
+ function resolveRunsForResumePhase(agentRuns, lanePaths, resumePhase, waveDefinition = null) {
649
+ const roleBindings = resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents);
650
+ if (resumePhase === "design") {
651
+ return (agentRuns || []).filter((run) => isDesignAgent(run.agent));
652
+ }
653
+ if (resumePhase === "integrating") {
654
+ return runsFromAgentIds(agentRuns, [roleBindings.integrationAgentId]);
655
+ }
656
+ if (resumePhase === "security-review") {
657
+ return (agentRuns || []).filter((run) => isSecurityReviewAgent(run.agent));
658
+ }
659
+ if (resumePhase === "docs-closure") {
660
+ return runsFromAgentIds(agentRuns, [roleBindings.documentationAgentId]);
661
+ }
662
+ if (resumePhase === "cont-qa-closure") {
663
+ return runsFromAgentIds(agentRuns, [roleBindings.contQaAgentId]);
664
+ }
665
+ if (resumePhase === "cont-eval") {
666
+ return runsFromAgentIds(agentRuns, [roleBindings.contEvalAgentId]);
667
+ }
668
+ return [];
669
+ }
670
+
671
+ function resolveRelaunchRunsLegacy(agentRuns, failures, derivedState, lanePaths, waveDefinition = null) {
672
+ const roleBindings = resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents);
564
673
  const runsByAgentId = new Map(agentRuns.map((run) => [run.agent.agentId, run]));
565
674
  const pendingFeedback = (derivedState?.coordinationState?.humanFeedback || []).filter((record) =>
566
675
  isOpenCoordinationStatus(record.status),
@@ -693,7 +802,13 @@ export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths
693
802
  }
694
803
  if (derivedState?.ledger?.phase === "docs-closure") {
695
804
  return {
696
- runs: [runsByAgentId.get(lanePaths.documentationAgentId)].filter(Boolean),
805
+ runs: [runsByAgentId.get(roleBindings.documentationAgentId)].filter(Boolean),
806
+ barrier: null,
807
+ };
808
+ }
809
+ if (derivedState?.ledger?.phase === "design") {
810
+ return {
811
+ runs: agentRuns.filter((run) => isDesignAgent(run.agent)),
697
812
  barrier: null,
698
813
  };
699
814
  }
@@ -705,19 +820,19 @@ export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths
705
820
  }
706
821
  if (derivedState?.ledger?.phase === "cont-eval") {
707
822
  return {
708
- runs: [runsByAgentId.get(lanePaths.contEvalAgentId)].filter(Boolean),
823
+ runs: [runsByAgentId.get(roleBindings.contEvalAgentId)].filter(Boolean),
709
824
  barrier: null,
710
825
  };
711
826
  }
712
827
  if (derivedState?.ledger?.phase === "cont-qa-closure") {
713
828
  return {
714
- runs: [runsByAgentId.get(lanePaths.contQaAgentId)].filter(Boolean),
829
+ runs: [runsByAgentId.get(roleBindings.contQaAgentId)].filter(Boolean),
715
830
  barrier: null,
716
831
  };
717
832
  }
718
833
  if (derivedState?.ledger?.phase === "integrating") {
719
834
  return {
720
- runs: [runsByAgentId.get(lanePaths.integrationAgentId)].filter(Boolean),
835
+ runs: [runsByAgentId.get(roleBindings.integrationAgentId)].filter(Boolean),
721
836
  barrier: null,
722
837
  };
723
838
  }
@@ -742,6 +857,216 @@ export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths
742
857
  };
743
858
  }
744
859
 
860
+ function resolveRelaunchRunsFromWaveState(
861
+ agentRuns,
862
+ failures,
863
+ derivedState,
864
+ lanePaths,
865
+ waveDefinition,
866
+ waveState,
867
+ ) {
868
+ const roleBindings = resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents);
869
+ const pendingFeedback = (waveState?.coordinationState?.humanFeedback || []).filter((record) =>
870
+ isOpenCoordinationStatus(record.status),
871
+ );
872
+ const pendingHumanEscalations = (waveState?.coordinationState?.humanEscalations || []).filter(
873
+ (record) => isOpenCoordinationStatus(record.status),
874
+ );
875
+ if (pendingFeedback.length > 0 || pendingHumanEscalations.length > 0) {
876
+ return { runs: [], barrier: null };
877
+ }
878
+
879
+ const nextAttemptNumber = Number(derivedState?.ledger?.attempt || 0) + 1;
880
+ const fallbackResolution = applyRetryFallbacks(
881
+ agentRuns,
882
+ failures,
883
+ lanePaths,
884
+ nextAttemptNumber,
885
+ waveDefinition,
886
+ );
887
+ const retryBarrier = retryBarrierFromOutcomes(fallbackResolution.outcomes, failures);
888
+ if (retryBarrier) {
889
+ return { runs: [], barrier: retryBarrier };
890
+ }
891
+
892
+ const clarificationTargets = new Set();
893
+ for (const record of openClarificationLinkedRequests(waveState?.coordinationState)) {
894
+ for (const target of record.targets || []) {
895
+ if (String(target).startsWith("agent:")) {
896
+ clarificationTargets.add(String(target).slice("agent:".length));
897
+ } else {
898
+ clarificationTargets.add(target);
899
+ }
900
+ }
901
+ }
902
+ if (clarificationTargets.size > 0) {
903
+ return {
904
+ runs: runsFromAgentIds(agentRuns, Array.from(clarificationTargets)),
905
+ barrier: null,
906
+ };
907
+ }
908
+
909
+ const blockingAssignments = (waveState?.capabilityAssignments || []).filter(
910
+ (assignment) => assignment.blocking,
911
+ );
912
+ if (blockingAssignments.length > 0) {
913
+ const unresolvedAssignments = blockingAssignments.filter((assignment) => !assignment.assignedAgentId);
914
+ if (unresolvedAssignments.length > 0) {
915
+ return {
916
+ runs: [],
917
+ barrier: {
918
+ statusCode: "helper-assignment-unresolved",
919
+ detail: `No matching assignee exists for helper requests (${unresolvedAssignments.map((assignment) => assignment.requestId).join(", ")}).`,
920
+ failures: unresolvedAssignments.map((assignment) => ({
921
+ agentId: null,
922
+ statusCode: "helper-assignment-unresolved",
923
+ logPath: null,
924
+ detail: assignment.assignmentDetail || assignment.summary || assignment.requestId,
925
+ })),
926
+ },
927
+ };
928
+ }
929
+ return {
930
+ runs: runsFromAgentIds(
931
+ agentRuns,
932
+ blockingAssignments.map((assignment) => assignment.assignedAgentId),
933
+ ),
934
+ barrier: null,
935
+ };
936
+ }
937
+
938
+ const unresolvedInboundAssignments =
939
+ waveState?.dependencySnapshot?.unresolvedInboundAssignments || [];
940
+ if (unresolvedInboundAssignments.length > 0) {
941
+ return {
942
+ runs: [],
943
+ barrier: {
944
+ statusCode: "dependency-assignment-unresolved",
945
+ detail: `Required inbound dependencies are not assigned (${unresolvedInboundAssignments.map((record) => record.id).join(", ")}).`,
946
+ failures: unresolvedInboundAssignments.map((record) => ({
947
+ agentId: null,
948
+ statusCode: "dependency-assignment-unresolved",
949
+ logPath: null,
950
+ detail: record.assignmentDetail || record.summary || record.id,
951
+ })),
952
+ },
953
+ };
954
+ }
955
+
956
+ const inboundDependencyAgentIds = new Set(
957
+ (waveState?.dependencySnapshot?.openInbound || [])
958
+ .map((record) => record.assignedAgentId)
959
+ .filter(Boolean),
960
+ );
961
+ if (inboundDependencyAgentIds.size > 0) {
962
+ return {
963
+ runs: runsFromAgentIds(agentRuns, Array.from(inboundDependencyAgentIds)),
964
+ barrier: null,
965
+ };
966
+ }
967
+
968
+ const blockerAgentIds = new Set();
969
+ for (const record of waveState?.coordinationState?.blockers || []) {
970
+ if (!isOpenCoordinationStatus(record.status)) {
971
+ continue;
972
+ }
973
+ blockerAgentIds.add(record.agentId);
974
+ for (const target of record.targets || []) {
975
+ if (String(target).startsWith("agent:")) {
976
+ blockerAgentIds.add(String(target).slice("agent:".length));
977
+ }
978
+ }
979
+ }
980
+ if (blockerAgentIds.size > 0) {
981
+ return {
982
+ runs: runsFromAgentIds(agentRuns, Array.from(blockerAgentIds)),
983
+ barrier: null,
984
+ };
985
+ }
986
+
987
+ const sharedComponentWaitingAgentIds = new Set(
988
+ (failures || [])
989
+ .filter((failure) => failure.statusCode === "shared-component-sibling-pending")
990
+ .flatMap((failure) => failure.waitingOnAgentIds || [])
991
+ .filter(Boolean),
992
+ );
993
+ if (sharedComponentWaitingAgentIds.size > 0) {
994
+ return {
995
+ runs: runsFromAgentIds(agentRuns, Array.from(sharedComponentWaitingAgentIds)),
996
+ barrier: null,
997
+ };
998
+ }
999
+
1000
+ const resumePlan = buildResumePlan(waveState, {
1001
+ waveDefinition,
1002
+ lanePaths,
1003
+ });
1004
+ if (!resumePlan.canResume || resumePlan.reason === "human-request") {
1005
+ return { runs: [], barrier: null };
1006
+ }
1007
+
1008
+ const phaseRuns = resolveRunsForResumePhase(
1009
+ agentRuns,
1010
+ lanePaths,
1011
+ resumePlan.resumeFromPhase,
1012
+ waveDefinition,
1013
+ );
1014
+ if (phaseRuns.length > 0 && resumePlan.resumeFromPhase !== "implementation") {
1015
+ return {
1016
+ runs: phaseRuns,
1017
+ barrier: null,
1018
+ };
1019
+ }
1020
+
1021
+ const retryTargetAgentIds = normalizeRetryTargets(waveState?.retryTargetSet).map(
1022
+ (target) => target.agentId,
1023
+ );
1024
+ const implementationAgentIds =
1025
+ resumePlan.invalidatedAgentIds.length > 0
1026
+ ? resumePlan.invalidatedAgentIds
1027
+ : retryTargetAgentIds;
1028
+ if (implementationAgentIds.length > 0) {
1029
+ return {
1030
+ runs: runsFromAgentIds(agentRuns, implementationAgentIds),
1031
+ barrier: null,
1032
+ };
1033
+ }
1034
+
1035
+ const failedAgentIds = new Set(failures.map((failure) => failure.agentId));
1036
+ return {
1037
+ runs: agentRuns.filter((run) => failedAgentIds.has(run.agent.agentId)),
1038
+ barrier: null,
1039
+ };
1040
+ }
1041
+
1042
+ export function resolveRelaunchRuns(
1043
+ agentRuns,
1044
+ failures,
1045
+ derivedState,
1046
+ lanePaths,
1047
+ waveDefinition = null,
1048
+ options = {},
1049
+ ) {
1050
+ const waveState = options?.waveState || null;
1051
+ if (!waveState) {
1052
+ return resolveRelaunchRunsLegacy(
1053
+ agentRuns,
1054
+ failures,
1055
+ derivedState,
1056
+ lanePaths,
1057
+ waveDefinition,
1058
+ );
1059
+ }
1060
+ return resolveRelaunchRunsFromWaveState(
1061
+ agentRuns,
1062
+ failures,
1063
+ derivedState,
1064
+ lanePaths,
1065
+ waveDefinition,
1066
+ waveState,
1067
+ );
1068
+ }
1069
+
745
1070
  export function preflightWavesForExecutorAvailability(waves, lanePaths) {
746
1071
  for (const wave of waves) {
747
1072
  const mixValidation = validateWaveRuntimeMixAssignments(wave, {
@@ -776,12 +1101,21 @@ export function preflightWavesForExecutorAvailability(waves, lanePaths) {
776
1101
 
777
1102
  function phaseFromGate(gateName) {
778
1103
  switch (gateName) {
1104
+ case "designGate":
1105
+ return "design";
779
1106
  case "implementationGate":
780
1107
  case "componentGate":
781
- case "contEvalGate":
1108
+ case "helperAssignmentBarrier":
1109
+ case "dependencyBarrier":
1110
+ case "clarificationBarrier":
782
1111
  return "implementation";
1112
+ case "contEvalGate":
1113
+ return "cont-eval";
1114
+ case "securityGate":
1115
+ return "security-review";
783
1116
  case "integrationBarrier":
784
1117
  return "integrating";
1118
+ case "componentMatrixGate":
785
1119
  case "documentationGate":
786
1120
  return "docs-closure";
787
1121
  case "contQaGate":
@@ -1,5 +1,9 @@
1
1
  import {
2
+ DEFAULT_CONT_QA_AGENT_ID,
2
3
  DEFAULT_CONT_EVAL_AGENT_ID,
4
+ DEFAULT_DESIGN_ROLE_PROMPT_PATH,
5
+ DEFAULT_DOCUMENTATION_AGENT_ID,
6
+ DEFAULT_INTEGRATION_AGENT_ID,
3
7
  DEFAULT_SECURITY_ROLE_PROMPT_PATH,
4
8
  } from "./config.mjs";
5
9
 
@@ -34,6 +38,29 @@ export function isSecurityReportPath(relPath) {
34
38
  return /(?:^|\/).*security.*\.(?:md|txt)$/i.test(cleanPath(relPath));
35
39
  }
36
40
 
41
+ export function isDesignRolePromptPath(
42
+ relPath,
43
+ designRolePromptPath = DEFAULT_DESIGN_ROLE_PROMPT_PATH,
44
+ ) {
45
+ const normalized = cleanPath(relPath);
46
+ const configured = cleanPath(designRolePromptPath);
47
+ return (
48
+ normalized === configured ||
49
+ normalized === DEFAULT_DESIGN_ROLE_PROMPT_PATH ||
50
+ normalized.endsWith("/wave-design-role.md")
51
+ );
52
+ }
53
+
54
+ export function isDesignReportPath(relPath) {
55
+ return /(?:^|\/).*(?:design|handoff|decision-lineage).*\.(?:md|txt)$/i.test(cleanPath(relPath));
56
+ }
57
+
58
+ function normalizedOwnedPaths(agent) {
59
+ return Array.isArray(agent?.ownedPaths)
60
+ ? agent.ownedPaths.map(cleanPath).filter(Boolean)
61
+ : [];
62
+ }
63
+
37
64
  export function isContEvalImplementationOwningAgent(
38
65
  agent,
39
66
  { contEvalAgentId = DEFAULT_CONT_EVAL_AGENT_ID } = {},
@@ -78,7 +105,103 @@ export function isSecurityReviewAgent(
78
105
  return capabilities.includes("security-review");
79
106
  }
80
107
 
108
+ export function isDesignAgent(
109
+ agent,
110
+ { designRolePromptPath = DEFAULT_DESIGN_ROLE_PROMPT_PATH } = {},
111
+ ) {
112
+ if (!agent || typeof agent !== "object") {
113
+ return false;
114
+ }
115
+ const rolePromptPaths = Array.isArray(agent.rolePromptPaths) ? agent.rolePromptPaths : [];
116
+ if (
117
+ rolePromptPaths.some((rolePromptPath) =>
118
+ isDesignRolePromptPath(rolePromptPath, designRolePromptPath),
119
+ )
120
+ ) {
121
+ return true;
122
+ }
123
+ const capabilities = Array.isArray(agent.capabilities)
124
+ ? agent.capabilities.map((entry) => String(entry || "").trim().toLowerCase())
125
+ : [];
126
+ return capabilities.includes("design");
127
+ }
128
+
129
+ export function isImplementationOwningDesignAgent(
130
+ agent,
131
+ { designRolePromptPath = DEFAULT_DESIGN_ROLE_PROMPT_PATH } = {},
132
+ ) {
133
+ if (!isDesignAgent(agent, { designRolePromptPath })) {
134
+ return false;
135
+ }
136
+ const ownedPaths = normalizedOwnedPaths(agent);
137
+ if (ownedPaths.length === 0) {
138
+ return false;
139
+ }
140
+ return ownedPaths.some((ownedPath) => !isDesignReportPath(ownedPath));
141
+ }
142
+
143
+ export function isDocsOnlyDesignAgent(
144
+ agent,
145
+ { designRolePromptPath = DEFAULT_DESIGN_ROLE_PROMPT_PATH } = {},
146
+ ) {
147
+ return isDesignAgent(agent, { designRolePromptPath }) &&
148
+ !isImplementationOwningDesignAgent(agent, { designRolePromptPath });
149
+ }
150
+
81
151
  export function resolveSecurityReviewReportPath(agent) {
82
- const ownedPaths = Array.isArray(agent?.ownedPaths) ? agent.ownedPaths.map(cleanPath).filter(Boolean) : [];
152
+ const ownedPaths = normalizedOwnedPaths(agent);
83
153
  return ownedPaths.find((ownedPath) => isSecurityReportPath(ownedPath)) || null;
84
154
  }
155
+
156
+ export function resolveDesignReportPath(agent) {
157
+ const ownedPaths = normalizedOwnedPaths(agent);
158
+ return ownedPaths.find((ownedPath) => isDesignReportPath(ownedPath)) || null;
159
+ }
160
+
161
+ export function resolveWaveRoleBindings(wave = {}, lanePaths = {}, agents = wave?.agents || []) {
162
+ const contQaAgentId =
163
+ wave?.contQaAgentId || lanePaths?.contQaAgentId || DEFAULT_CONT_QA_AGENT_ID;
164
+ const contEvalAgentId =
165
+ wave?.contEvalAgentId || lanePaths?.contEvalAgentId || DEFAULT_CONT_EVAL_AGENT_ID;
166
+ const integrationAgentId =
167
+ wave?.integrationAgentId || lanePaths?.integrationAgentId || DEFAULT_INTEGRATION_AGENT_ID;
168
+ const documentationAgentId =
169
+ wave?.documentationAgentId ||
170
+ lanePaths?.documentationAgentId ||
171
+ DEFAULT_DOCUMENTATION_AGENT_ID;
172
+ const securityReviewerAgentIds = Array.from(
173
+ new Set(
174
+ (Array.isArray(agents) ? agents : [])
175
+ .filter((agent) =>
176
+ isSecurityReviewAgent(agent, {
177
+ securityRolePromptPath: lanePaths?.securityRolePromptPath,
178
+ }),
179
+ )
180
+ .map((agent) => agent.agentId)
181
+ .filter(Boolean),
182
+ ),
183
+ ).sort();
184
+ const closureAgentIds = Array.from(
185
+ new Set(
186
+ [
187
+ contEvalAgentId,
188
+ integrationAgentId,
189
+ documentationAgentId,
190
+ contQaAgentId,
191
+ ...securityReviewerAgentIds,
192
+ ].filter(Boolean),
193
+ ),
194
+ ).sort();
195
+ return {
196
+ contQaAgentId,
197
+ contEvalAgentId,
198
+ integrationAgentId,
199
+ documentationAgentId,
200
+ securityReviewerAgentIds,
201
+ closureAgentIds,
202
+ };
203
+ }
204
+
205
+ export function isClosureRoleAgentId(agentId, roleBindings) {
206
+ return (roleBindings?.closureAgentIds || []).includes(agentId);
207
+ }