@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.
- package/CHANGELOG.md +41 -1
- package/README.md +31 -13
- package/docs/README.md +4 -0
- package/docs/agents/wave-design-role.md +47 -0
- package/docs/concepts/what-is-a-wave.md +11 -7
- package/docs/context7/bundles.json +19 -20
- package/docs/context7/planner-agent/README.md +4 -1
- package/docs/guides/author-and-run-waves.md +27 -0
- package/docs/guides/planner.md +46 -0
- package/docs/guides/signal-wrappers.md +165 -0
- package/docs/guides/terminal-surfaces.md +13 -0
- package/docs/plans/context7-wave-orchestrator.md +24 -7
- package/docs/plans/current-state.md +10 -2
- package/docs/plans/end-state-architecture.md +22 -5
- package/docs/plans/examples/wave-example-design-handoff.md +262 -0
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +301 -75
- package/docs/plans/wave-orchestrator.md +16 -3
- package/docs/reference/cli-reference.md +22 -1
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/sample-waves.md +14 -7
- package/docs/reference/skills.md +18 -0
- package/docs/reference/wave-control.md +2 -0
- package/package.json +1 -1
- package/releases/manifest.json +38 -0
- package/scripts/context7-api-check.sh +57 -13
- package/scripts/wave-orchestrator/agent-state.mjs +64 -0
- package/scripts/wave-orchestrator/config.mjs +5 -0
- package/scripts/wave-orchestrator/control-cli.mjs +19 -0
- package/scripts/wave-orchestrator/coordination.mjs +77 -1
- package/scripts/wave-orchestrator/gate-engine.mjs +106 -2
- package/scripts/wave-orchestrator/install.mjs +5 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +18 -1
- package/scripts/wave-orchestrator/launcher.mjs +75 -1
- package/scripts/wave-orchestrator/ledger.mjs +56 -27
- package/scripts/wave-orchestrator/local-executor.mjs +37 -0
- package/scripts/wave-orchestrator/planner.mjs +24 -4
- package/scripts/wave-orchestrator/result-envelope.mjs +32 -1
- package/scripts/wave-orchestrator/retry-control.mjs +17 -2
- package/scripts/wave-orchestrator/retry-engine.mjs +85 -0
- package/scripts/wave-orchestrator/role-helpers.mjs +73 -1
- package/scripts/wave-orchestrator/session-supervisor.mjs +113 -0
- package/scripts/wave-orchestrator/shared.mjs +2 -0
- package/scripts/wave-orchestrator/signals.mjs +681 -0
- package/scripts/wave-orchestrator/skills.mjs +1 -0
- package/scripts/wave-orchestrator/task-entity.mjs +65 -45
- package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
- package/scripts/wave-orchestrator/wave-files.mjs +85 -1
- package/scripts/wave-orchestrator/wave-state-reducer.mjs +24 -7
- package/scripts/wave-status.sh +200 -0
- package/scripts/wave-watch.sh +200 -0
- package/skills/README.md +10 -0
- package/skills/role-design/SKILL.md +50 -0
- package/skills/role-design/skill.json +36 -0
- package/skills/signal-hygiene/SKILL.md +51 -0
- package/skills/signal-hygiene/skill.json +20 -0
- package/skills/tui-design/SKILL.md +77 -0
- package/skills/tui-design/references/tui-design.md +259 -0
- package/skills/tui-design/skill.json +36 -0
- 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
|
|
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
|
-
:
|
|
497
|
-
? "
|
|
498
|
-
:
|
|
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 (
|
|
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
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
580
|
+
{
|
|
581
|
+
createdAt: seededAt,
|
|
582
|
+
updatedAt: seededAt,
|
|
583
|
+
waveNumber,
|
|
584
|
+
lane,
|
|
566
585
|
},
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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 || {};
|
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|