@chllming/wave-orchestration 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +549 -0
  3. package/docs/agents/wave-deploy-verifier-role.md +34 -0
  4. package/docs/agents/wave-documentation-role.md +30 -0
  5. package/docs/agents/wave-evaluator-role.md +43 -0
  6. package/docs/agents/wave-infra-role.md +34 -0
  7. package/docs/agents/wave-integration-role.md +32 -0
  8. package/docs/agents/wave-launcher-role.md +37 -0
  9. package/docs/context7/bundles.json +91 -0
  10. package/docs/plans/component-cutover-matrix.json +112 -0
  11. package/docs/plans/component-cutover-matrix.md +49 -0
  12. package/docs/plans/context7-wave-orchestrator.md +130 -0
  13. package/docs/plans/current-state.md +44 -0
  14. package/docs/plans/master-plan.md +16 -0
  15. package/docs/plans/migration.md +23 -0
  16. package/docs/plans/wave-orchestrator.md +254 -0
  17. package/docs/plans/waves/wave-0.md +165 -0
  18. package/docs/reference/github-packages-setup.md +52 -0
  19. package/docs/reference/migration-0.2-to-0.5.md +622 -0
  20. package/docs/reference/npmjs-trusted-publishing.md +55 -0
  21. package/docs/reference/repository-guidance.md +18 -0
  22. package/docs/reference/runtime-config/README.md +85 -0
  23. package/docs/reference/runtime-config/claude.md +105 -0
  24. package/docs/reference/runtime-config/codex.md +81 -0
  25. package/docs/reference/runtime-config/opencode.md +93 -0
  26. package/docs/research/agent-context-sources.md +57 -0
  27. package/docs/roadmap.md +626 -0
  28. package/package.json +53 -0
  29. package/releases/manifest.json +101 -0
  30. package/scripts/context7-api-check.sh +21 -0
  31. package/scripts/context7-export-env.sh +52 -0
  32. package/scripts/research/agent-context-archive.mjs +472 -0
  33. package/scripts/research/generate-agent-context-indexes.mjs +85 -0
  34. package/scripts/research/import-agent-context-archive.mjs +793 -0
  35. package/scripts/research/manifests/harness-and-blackboard-2026-03-21.mjs +201 -0
  36. package/scripts/wave-autonomous.mjs +13 -0
  37. package/scripts/wave-cli-bootstrap.mjs +27 -0
  38. package/scripts/wave-dashboard.mjs +11 -0
  39. package/scripts/wave-human-feedback.mjs +11 -0
  40. package/scripts/wave-launcher.mjs +11 -0
  41. package/scripts/wave-local-executor.mjs +13 -0
  42. package/scripts/wave-orchestrator/agent-state.mjs +416 -0
  43. package/scripts/wave-orchestrator/autonomous.mjs +367 -0
  44. package/scripts/wave-orchestrator/clarification-triage.mjs +605 -0
  45. package/scripts/wave-orchestrator/config.mjs +848 -0
  46. package/scripts/wave-orchestrator/context7.mjs +464 -0
  47. package/scripts/wave-orchestrator/coord-cli.mjs +286 -0
  48. package/scripts/wave-orchestrator/coordination-store.mjs +987 -0
  49. package/scripts/wave-orchestrator/coordination.mjs +768 -0
  50. package/scripts/wave-orchestrator/dashboard-renderer.mjs +254 -0
  51. package/scripts/wave-orchestrator/dashboard-state.mjs +473 -0
  52. package/scripts/wave-orchestrator/dep-cli.mjs +219 -0
  53. package/scripts/wave-orchestrator/docs-queue.mjs +75 -0
  54. package/scripts/wave-orchestrator/executors.mjs +385 -0
  55. package/scripts/wave-orchestrator/feedback.mjs +372 -0
  56. package/scripts/wave-orchestrator/install.mjs +540 -0
  57. package/scripts/wave-orchestrator/launcher.mjs +3879 -0
  58. package/scripts/wave-orchestrator/ledger.mjs +332 -0
  59. package/scripts/wave-orchestrator/local-executor.mjs +263 -0
  60. package/scripts/wave-orchestrator/replay.mjs +246 -0
  61. package/scripts/wave-orchestrator/roots.mjs +10 -0
  62. package/scripts/wave-orchestrator/routing-state.mjs +542 -0
  63. package/scripts/wave-orchestrator/shared.mjs +405 -0
  64. package/scripts/wave-orchestrator/terminals.mjs +209 -0
  65. package/scripts/wave-orchestrator/traces.mjs +1094 -0
  66. package/scripts/wave-orchestrator/wave-files.mjs +1923 -0
  67. package/scripts/wave.mjs +103 -0
  68. package/wave.config.json +115 -0
@@ -0,0 +1,473 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ DASHBOARD_MAX_EVENTS,
5
+ DASHBOARD_MESSAGEBOARD_TAIL_CHARS,
6
+ DASHBOARD_MESSAGEBOARD_TAIL_LINES,
7
+ DEPLOY_SIGNAL_REGEX,
8
+ INFRA_SIGNAL_REGEX,
9
+ PHASE_SIGNAL_REGEX,
10
+ REPO_ROOT,
11
+ WAVE_TERMINAL_STATES,
12
+ ensureDirectory,
13
+ formatAgeFromTimestamp,
14
+ readFileTail,
15
+ readStatusRecordIfPresent,
16
+ toIsoTimestamp,
17
+ truncate,
18
+ writeJsonAtomic,
19
+ } from "./shared.mjs";
20
+
21
+ export function readStatusCodeIfPresent(statusPath) {
22
+ return readStatusRecordIfPresent(statusPath)?.code ?? null;
23
+ }
24
+
25
+ export function readRollingMessageBoardLines(
26
+ messageBoardPath,
27
+ maxLines = DASHBOARD_MESSAGEBOARD_TAIL_LINES,
28
+ ) {
29
+ if (!fs.existsSync(messageBoardPath)) {
30
+ return ["(message board missing)"];
31
+ }
32
+ const tail = readFileTail(messageBoardPath, DASHBOARD_MESSAGEBOARD_TAIL_CHARS).trim();
33
+ if (!tail) {
34
+ return ["(message board currently empty)"];
35
+ }
36
+ return tail.split(/\r?\n/).slice(-maxLines);
37
+ }
38
+
39
+ export function inferAgentPhaseFromLog(logPath) {
40
+ const text = readFileTail(logPath, 14000).toLowerCase();
41
+ if (!text) {
42
+ return "running";
43
+ }
44
+ if (
45
+ /(deployment_list|deployment_logs|deployment_status|\brailway\b|\bdeploy(ing|ment)?\b|\brollover\b)/.test(
46
+ text,
47
+ )
48
+ ) {
49
+ return "deploying";
50
+ }
51
+ if (/\b(vitest|pytest|pnpm\b.*\b(test|build|check|lint)\b|typecheck|tsc|go test)\b/.test(text)) {
52
+ return "validating";
53
+ }
54
+ if (/\b(git commit|conventional commit|git push|pushed)\b/.test(text)) {
55
+ return "finalizing";
56
+ }
57
+ if (
58
+ /\b(apply_patch|update file|add file|editing|implementing|starting file edits)\b/.test(text)
59
+ ) {
60
+ return "coding";
61
+ }
62
+ return "running";
63
+ }
64
+
65
+ export function parseStructuredSignalsFromLog(logPath) {
66
+ const text = readFileTail(logPath, 30000);
67
+ if (!text) {
68
+ return { phase: null, deployment: null, infra: null };
69
+ }
70
+ let phase = null;
71
+ PHASE_SIGNAL_REGEX.lastIndex = 0;
72
+ let phaseMatch = PHASE_SIGNAL_REGEX.exec(text);
73
+ while (phaseMatch !== null) {
74
+ phase = String(phaseMatch[1] || "").toLowerCase();
75
+ phaseMatch = PHASE_SIGNAL_REGEX.exec(text);
76
+ }
77
+ let deployment = null;
78
+ DEPLOY_SIGNAL_REGEX.lastIndex = 0;
79
+ let deploymentMatch = DEPLOY_SIGNAL_REGEX.exec(text);
80
+ while (deploymentMatch !== null) {
81
+ deployment = {
82
+ service: String(deploymentMatch[1] || "").trim(),
83
+ state: String(deploymentMatch[2] || "").trim(),
84
+ detail: String(deploymentMatch[3] || "").trim(),
85
+ };
86
+ deploymentMatch = DEPLOY_SIGNAL_REGEX.exec(text);
87
+ }
88
+ let infra = null;
89
+ INFRA_SIGNAL_REGEX.lastIndex = 0;
90
+ let infraMatch = INFRA_SIGNAL_REGEX.exec(text);
91
+ while (infraMatch !== null) {
92
+ infra = {
93
+ kind: String(infraMatch[1] || "").trim(),
94
+ target: String(infraMatch[2] || "").trim(),
95
+ state: String(infraMatch[3] || "").trim(),
96
+ detail: String(infraMatch[4] || "").trim(),
97
+ };
98
+ infraMatch = INFRA_SIGNAL_REGEX.exec(text);
99
+ }
100
+ return { phase, deployment, infra };
101
+ }
102
+
103
+ export function normalizePhaseState(value) {
104
+ const state = String(value || "")
105
+ .trim()
106
+ .toLowerCase();
107
+ return new Set([
108
+ "pending",
109
+ "launching",
110
+ "running",
111
+ "coding",
112
+ "validating",
113
+ "deploying",
114
+ "finalizing",
115
+ "completed",
116
+ "failed",
117
+ "timed_out",
118
+ ]).has(state)
119
+ ? state
120
+ : null;
121
+ }
122
+
123
+ export function buildWaveDashboardState({
124
+ lane,
125
+ wave,
126
+ waveFile,
127
+ runTag,
128
+ maxAttempts,
129
+ messageBoardPath,
130
+ agentRuns,
131
+ }) {
132
+ const now = toIsoTimestamp();
133
+ return {
134
+ lane,
135
+ wave,
136
+ waveFile,
137
+ runTag,
138
+ status: "running",
139
+ attempt: 0,
140
+ maxAttempts,
141
+ startedAt: now,
142
+ updatedAt: now,
143
+ messageBoardPath: path.relative(REPO_ROOT, messageBoardPath),
144
+ messageBoardTail: readRollingMessageBoardLines(messageBoardPath),
145
+ helperAssignmentsOpen: 0,
146
+ inboundDependenciesOpen: 0,
147
+ outboundDependenciesOpen: 0,
148
+ agents: agentRuns.map((run) => ({
149
+ agentId: run.agent.agentId,
150
+ title: run.agent.title,
151
+ slug: run.agent.slug,
152
+ state: "pending",
153
+ attempts: 0,
154
+ sessionName: run.sessionName,
155
+ promptPath: path.relative(REPO_ROOT, run.promptPath),
156
+ logPath: path.relative(REPO_ROOT, run.logPath),
157
+ statusPath: path.relative(REPO_ROOT, run.statusPath),
158
+ startedAt: null,
159
+ completedAt: null,
160
+ lastUpdateAt: now,
161
+ exitCode: null,
162
+ deploymentService: null,
163
+ deploymentState: null,
164
+ deploymentDetail: "",
165
+ infraKind: null,
166
+ infraTarget: null,
167
+ infraState: null,
168
+ infraDetail: "",
169
+ detail: "",
170
+ })),
171
+ events: [],
172
+ };
173
+ }
174
+
175
+ export function buildGlobalDashboardState({
176
+ lane,
177
+ selectedWaves,
178
+ options,
179
+ runStatePath,
180
+ manifestOut,
181
+ feedbackRequestsDir,
182
+ }) {
183
+ const now = toIsoTimestamp();
184
+ return {
185
+ lane,
186
+ runId: Math.random().toString(16).slice(2, 14),
187
+ status: "running",
188
+ startedAt: now,
189
+ updatedAt: now,
190
+ options: {
191
+ lane: options.lane,
192
+ startWave: options.startWave,
193
+ endWave: options.endWave,
194
+ autoNext: options.autoNext,
195
+ timeoutMinutes: options.timeoutMinutes,
196
+ maxRetriesPerWave: options.maxRetriesPerWave,
197
+ dashboard: options.dashboard,
198
+ cleanupSessions: options.cleanupSessions,
199
+ orchestratorId: options.orchestratorId,
200
+ orchestratorBoardPath: options.orchestratorBoardPath
201
+ ? path.relative(REPO_ROOT, options.orchestratorBoardPath)
202
+ : null,
203
+ coordinationNote: options.coordinationNote || "",
204
+ },
205
+ paths: {
206
+ runState: path.relative(REPO_ROOT, runStatePath),
207
+ manifest: path.relative(REPO_ROOT, manifestOut),
208
+ feedbackRequests: path.relative(REPO_ROOT, feedbackRequestsDir),
209
+ },
210
+ waves: selectedWaves.map((wave) => ({
211
+ wave: wave.wave,
212
+ waveFile: wave.file,
213
+ status: "pending",
214
+ attempt: 0,
215
+ maxAttempts: options.maxRetriesPerWave + 1,
216
+ dashboardPath: path.relative(
217
+ REPO_ROOT,
218
+ path.join(path.dirname(runStatePath), "dashboards", `wave-${wave.wave}.json`),
219
+ ),
220
+ messageBoardPath: path.relative(
221
+ REPO_ROOT,
222
+ path.join(path.dirname(runStatePath), "messageboards", `wave-${wave.wave}.md`),
223
+ ),
224
+ startedAt: null,
225
+ completedAt: null,
226
+ agentsTotal: wave.agents.length,
227
+ agentsCompleted: 0,
228
+ agentsFailed: 0,
229
+ helperAssignmentsOpen: 0,
230
+ inboundDependenciesOpen: 0,
231
+ outboundDependenciesOpen: 0,
232
+ lastMessage: "",
233
+ deployments: [],
234
+ infraFindings: [],
235
+ })),
236
+ events: [],
237
+ };
238
+ }
239
+
240
+ export function writeWaveDashboard(dashboardPath, state) {
241
+ ensureDirectory(path.dirname(dashboardPath));
242
+ state.updatedAt = toIsoTimestamp();
243
+ writeJsonAtomic(dashboardPath, state);
244
+ }
245
+
246
+ export function writeGlobalDashboard(globalDashboardPath, state) {
247
+ ensureDirectory(path.dirname(globalDashboardPath));
248
+ state.updatedAt = toIsoTimestamp();
249
+ writeJsonAtomic(globalDashboardPath, state);
250
+ }
251
+
252
+ export function recordWaveDashboardEvent(state, { level = "info", agentId = null, message }) {
253
+ state.events.push({
254
+ at: toIsoTimestamp(),
255
+ level,
256
+ agentId,
257
+ message,
258
+ });
259
+ if (state.events.length > DASHBOARD_MAX_EVENTS) {
260
+ state.events = state.events.slice(state.events.length - DASHBOARD_MAX_EVENTS);
261
+ }
262
+ }
263
+
264
+ export function recordGlobalDashboardEvent(globalState, { level = "info", wave = null, message }) {
265
+ globalState.events.push({
266
+ at: toIsoTimestamp(),
267
+ level,
268
+ wave,
269
+ message,
270
+ });
271
+ if (globalState.events.length > DASHBOARD_MAX_EVENTS) {
272
+ globalState.events = globalState.events.slice(globalState.events.length - DASHBOARD_MAX_EVENTS);
273
+ }
274
+ }
275
+
276
+ export function updateWaveDashboardMessageBoard(state, messageBoardPath) {
277
+ state.messageBoardTail = readRollingMessageBoardLines(messageBoardPath);
278
+ }
279
+
280
+ export function getGlobalWaveEntry(globalState, waveNumber) {
281
+ return globalState.waves.find((entry) => entry.wave === waveNumber) || null;
282
+ }
283
+
284
+ export function syncGlobalWaveFromWaveDashboard(globalState, waveDashboard) {
285
+ const entry = getGlobalWaveEntry(globalState, waveDashboard.wave);
286
+ if (!entry) {
287
+ return;
288
+ }
289
+ entry.status = waveDashboard.status;
290
+ entry.attempt = waveDashboard.attempt;
291
+ entry.startedAt = waveDashboard.startedAt;
292
+ if (WAVE_TERMINAL_STATES.has(waveDashboard.status)) {
293
+ entry.completedAt = entry.completedAt || toIsoTimestamp();
294
+ }
295
+ const agents = Array.isArray(waveDashboard.agents) ? waveDashboard.agents : [];
296
+ entry.agentsTotal = agents.length;
297
+ entry.agentsCompleted = agents.filter((agent) => agent.state === "completed").length;
298
+ entry.agentsFailed = agents.filter(
299
+ (agent) => agent.state === "failed" || agent.state === "timed_out",
300
+ ).length;
301
+ const latestEvent = waveDashboard.events.at(-1);
302
+ entry.lastMessage = latestEvent?.message || entry.lastMessage || "";
303
+ entry.helperAssignmentsOpen = waveDashboard.helperAssignmentsOpen || 0;
304
+ entry.inboundDependenciesOpen = waveDashboard.inboundDependenciesOpen || 0;
305
+ entry.outboundDependenciesOpen = waveDashboard.outboundDependenciesOpen || 0;
306
+ entry.deployments = agents
307
+ .filter((agent) => agent.deploymentState)
308
+ .map((agent) => ({
309
+ agentId: agent.agentId,
310
+ service: agent.deploymentService || "",
311
+ state: agent.deploymentState,
312
+ detail: agent.deploymentDetail || "",
313
+ updatedAt: agent.lastUpdateAt || "",
314
+ }));
315
+ entry.infraFindings = agents
316
+ .filter((agent) => agent.infraState)
317
+ .map((agent) => ({
318
+ agentId: agent.agentId,
319
+ kind: agent.infraKind || "",
320
+ target: agent.infraTarget || "",
321
+ state: agent.infraState,
322
+ detail: agent.infraDetail || "",
323
+ updatedAt: agent.lastUpdateAt || "",
324
+ }));
325
+ }
326
+
327
+ export function setWaveDashboardAgent(state, agentId, updates) {
328
+ const agent = state.agents.find((entry) => entry.agentId === agentId);
329
+ if (!agent) {
330
+ return;
331
+ }
332
+ Object.assign(agent, updates);
333
+ agent.lastUpdateAt = toIsoTimestamp();
334
+ }
335
+
336
+ export function refreshWaveDashboardAgentStates(
337
+ state,
338
+ agentRuns,
339
+ pendingAgentIds,
340
+ eventSink = null,
341
+ ) {
342
+ for (const run of agentRuns) {
343
+ const code = readStatusCodeIfPresent(run.statusPath);
344
+ const current = state.agents.find((entry) => entry.agentId === run.agent.agentId);
345
+ if (!current) {
346
+ continue;
347
+ }
348
+ if (code === 0) {
349
+ setWaveDashboardAgent(state, run.agent.agentId, {
350
+ state: "completed",
351
+ exitCode: 0,
352
+ completedAt: toIsoTimestamp(),
353
+ detail: "Exit 0",
354
+ });
355
+ continue;
356
+ }
357
+ if (code !== null) {
358
+ setWaveDashboardAgent(state, run.agent.agentId, {
359
+ state: code === 124 ? "timed_out" : "failed",
360
+ exitCode: code,
361
+ completedAt: toIsoTimestamp(),
362
+ detail: `Exit ${code}`,
363
+ });
364
+ continue;
365
+ }
366
+ if (!pendingAgentIds.has(run.agent.agentId)) {
367
+ continue;
368
+ }
369
+
370
+ const signals = parseStructuredSignalsFromLog(run.logPath);
371
+ const signaledState = normalizePhaseState(signals.phase);
372
+ if (signaledState && signaledState !== current.state) {
373
+ setWaveDashboardAgent(state, run.agent.agentId, {
374
+ state: signaledState,
375
+ detail: `Signaled from log (${signaledState})`,
376
+ });
377
+ eventSink?.({
378
+ level: "info",
379
+ agentId: run.agent.agentId,
380
+ message: `Phase signaled: ${signaledState}`,
381
+ });
382
+ } else {
383
+ const inferred = inferAgentPhaseFromLog(run.logPath);
384
+ if (inferred !== current.state) {
385
+ setWaveDashboardAgent(state, run.agent.agentId, {
386
+ state: inferred,
387
+ detail: `Inferred from log (${inferred})`,
388
+ });
389
+ }
390
+ }
391
+
392
+ if (signals.deployment) {
393
+ const changed =
394
+ current.deploymentService !== signals.deployment.service ||
395
+ current.deploymentState !== signals.deployment.state ||
396
+ current.deploymentDetail !== signals.deployment.detail;
397
+ if (changed) {
398
+ setWaveDashboardAgent(state, run.agent.agentId, {
399
+ deploymentService: signals.deployment.service,
400
+ deploymentState: signals.deployment.state,
401
+ deploymentDetail: signals.deployment.detail,
402
+ detail: `Deploy ${signals.deployment.service}: ${signals.deployment.state}`,
403
+ });
404
+ eventSink?.({
405
+ level: signals.deployment.state === "failed" ? "error" : "info",
406
+ agentId: run.agent.agentId,
407
+ message: `Deployment ${signals.deployment.service} => ${signals.deployment.state}${
408
+ signals.deployment.detail ? ` (${signals.deployment.detail})` : ""
409
+ }`,
410
+ });
411
+ }
412
+ }
413
+ if (signals.infra) {
414
+ const changed =
415
+ current.infraKind !== signals.infra.kind ||
416
+ current.infraTarget !== signals.infra.target ||
417
+ current.infraState !== signals.infra.state ||
418
+ current.infraDetail !== signals.infra.detail;
419
+ if (changed) {
420
+ setWaveDashboardAgent(state, run.agent.agentId, {
421
+ infraKind: signals.infra.kind,
422
+ infraTarget: signals.infra.target,
423
+ infraState: signals.infra.state,
424
+ infraDetail: signals.infra.detail,
425
+ detail: `Infra ${signals.infra.kind}:${signals.infra.state}`,
426
+ });
427
+ const infraState = String(signals.infra.state || "").trim().toLowerCase();
428
+ eventSink?.({
429
+ level:
430
+ infraState === "drift" || infraState === "blocked" || infraState === "failed"
431
+ ? "error"
432
+ : infraState === "setup-required" ||
433
+ infraState === "setup-in-progress" ||
434
+ infraState === "action-required" ||
435
+ infraState === "action-approved"
436
+ ? "warn"
437
+ : "info",
438
+ agentId: run.agent.agentId,
439
+ message: `Infra ${signals.infra.kind} ${signals.infra.target} => ${signals.infra.state}${
440
+ signals.infra.detail ? ` (${signals.infra.detail})` : ""
441
+ }`,
442
+ });
443
+ }
444
+ }
445
+ }
446
+ }
447
+
448
+ export function renderCountsByState(agents) {
449
+ const counts = new Map();
450
+ for (const agent of agents || []) {
451
+ const key = agent?.state || "unknown";
452
+ counts.set(key, (counts.get(key) || 0) + 1);
453
+ }
454
+ return Array.from(counts.entries())
455
+ .toSorted((a, b) => a[0].localeCompare(b[0]))
456
+ .map(([state, count]) => `${state}:${count}`)
457
+ .join(" ");
458
+ }
459
+
460
+ export function resolveWaveMessageBoardPathForLane(lanePaths, waveNumber) {
461
+ return path.join(lanePaths.messageboardsDir, `wave-${waveNumber}.md`);
462
+ }
463
+
464
+ export function deploymentSummary(deployment) {
465
+ if (!deployment?.state) {
466
+ return "-";
467
+ }
468
+ return truncate(`${deployment.service || "service"}:${deployment.state}`, 24);
469
+ }
470
+
471
+ export function commsAgeSummary(value) {
472
+ return formatAgeFromTimestamp(value);
473
+ }
@@ -0,0 +1,219 @@
1
+ import path from "node:path";
2
+ import {
3
+ appendDependencyTicket,
4
+ materializeCoordinationState,
5
+ readDependencyTickets,
6
+ writeJsonArtifact,
7
+ } from "./coordination-store.mjs";
8
+ import {
9
+ buildDependencySnapshot,
10
+ readAllDependencyTickets,
11
+ renderDependencySnapshotMarkdown,
12
+ } from "./routing-state.mjs";
13
+ import { buildLanePaths, ensureDirectory, parseNonNegativeInt, writeTextAtomic } from "./shared.mjs";
14
+ import { parseWaveFiles } from "./wave-files.mjs";
15
+
16
+ function printUsage() {
17
+ console.log(`Usage:
18
+ wave dep post --owner-lane <lane> --requester-lane <lane> --owner-wave <n> --requester-wave <n> --agent <id> --summary <text> [options]
19
+ wave dep show --lane <lane> [--wave <n>] [--json]
20
+ wave dep resolve --lane <lane> --id <id> --agent <id> [--detail <text>] [--status resolved|closed]
21
+ wave dep render --lane <lane> [--wave <n>] [--json]
22
+ `);
23
+ }
24
+
25
+ function parseArgs(argv) {
26
+ const args = argv[0] === "--" ? argv.slice(1) : argv;
27
+ const subcommand = String(args[0] || "").trim().toLowerCase();
28
+ const options = {
29
+ lane: "",
30
+ ownerLane: "",
31
+ requesterLane: "",
32
+ wave: null,
33
+ ownerWave: null,
34
+ requesterWave: null,
35
+ agent: "",
36
+ id: "",
37
+ summary: "",
38
+ detail: "",
39
+ targets: [],
40
+ artifacts: [],
41
+ closureCondition: "",
42
+ priority: "high",
43
+ status: "",
44
+ required: false,
45
+ json: false,
46
+ };
47
+ for (let index = 1; index < args.length; index += 1) {
48
+ const arg = args[index];
49
+ if (arg === "--lane") {
50
+ options.lane = String(args[++index] || "").trim();
51
+ } else if (arg === "--owner-lane") {
52
+ options.ownerLane = String(args[++index] || "").trim();
53
+ } else if (arg === "--requester-lane") {
54
+ options.requesterLane = String(args[++index] || "").trim();
55
+ } else if (arg === "--wave") {
56
+ options.wave = parseNonNegativeInt(args[++index], "--wave");
57
+ } else if (arg === "--owner-wave") {
58
+ options.ownerWave = parseNonNegativeInt(args[++index], "--owner-wave");
59
+ } else if (arg === "--requester-wave") {
60
+ options.requesterWave = parseNonNegativeInt(args[++index], "--requester-wave");
61
+ } else if (arg === "--agent") {
62
+ options.agent = String(args[++index] || "").trim();
63
+ } else if (arg === "--id") {
64
+ options.id = String(args[++index] || "").trim();
65
+ } else if (arg === "--summary") {
66
+ options.summary = String(args[++index] || "").trim();
67
+ } else if (arg === "--detail") {
68
+ options.detail = String(args[++index] || "").trim();
69
+ } else if (arg === "--target") {
70
+ options.targets.push(String(args[++index] || "").trim());
71
+ } else if (arg === "--artifact") {
72
+ options.artifacts.push(String(args[++index] || "").trim());
73
+ } else if (arg === "--closure-condition") {
74
+ options.closureCondition = String(args[++index] || "").trim();
75
+ } else if (arg === "--priority") {
76
+ options.priority = String(args[++index] || "").trim();
77
+ } else if (arg === "--status") {
78
+ options.status = String(args[++index] || "").trim();
79
+ } else if (arg === "--required") {
80
+ options.required = true;
81
+ } else if (arg === "--json") {
82
+ options.json = true;
83
+ } else if (arg && arg !== "--") {
84
+ throw new Error(`Unknown argument: ${arg}`);
85
+ }
86
+ }
87
+ if (!subcommand) {
88
+ throw new Error("Expected subcommand");
89
+ }
90
+ return { subcommand, options };
91
+ }
92
+
93
+ function dependencyFilePath(lanePaths, lane) {
94
+ return path.join(lanePaths.crossLaneDependenciesDir, `${lane}.jsonl`);
95
+ }
96
+
97
+ function dependencyMarkdownPath(lanePaths, lane) {
98
+ return path.join(lanePaths.crossLaneDependenciesDir, `${lane}.md`);
99
+ }
100
+
101
+ function loadWaveAgents(lanePaths, waveNumber) {
102
+ if (waveNumber === null || waveNumber === undefined) {
103
+ return [];
104
+ }
105
+ const waves = parseWaveFiles(lanePaths.wavesDir, { laneProfile: lanePaths.laneProfile });
106
+ return waves.find((wave) => wave.wave === waveNumber)?.agents || [];
107
+ }
108
+
109
+ export async function runDependencyCli(argv) {
110
+ if (argv.includes("--help") || argv.includes("-h")) {
111
+ printUsage();
112
+ return;
113
+ }
114
+ const { subcommand, options } = parseArgs(argv);
115
+ const baseLane = options.lane || options.ownerLane || options.requesterLane || "main";
116
+ const lanePaths = buildLanePaths(baseLane);
117
+ ensureDirectory(lanePaths.crossLaneDependenciesDir);
118
+
119
+ if (subcommand === "post") {
120
+ const ownerLane = options.ownerLane || options.lane;
121
+ const requesterLane = options.requesterLane || lanePaths.lane;
122
+ if (!ownerLane || !requesterLane || options.ownerWave === null || options.requesterWave === null) {
123
+ throw new Error("--owner-lane, --requester-lane, --owner-wave, and --requester-wave are required");
124
+ }
125
+ if (!options.agent || !options.summary) {
126
+ throw new Error("--agent and --summary are required");
127
+ }
128
+ const record = appendDependencyTicket(lanePaths.crossLaneDependenciesDir, ownerLane, {
129
+ id: options.id || `dep-${Date.now().toString(36)}`,
130
+ kind: "request",
131
+ lane: ownerLane,
132
+ wave: options.ownerWave,
133
+ ownerLane,
134
+ ownerWave: options.ownerWave,
135
+ requesterLane,
136
+ requesterWave: options.requesterWave,
137
+ agentId: options.agent,
138
+ targets: options.targets,
139
+ priority: options.priority,
140
+ summary: options.summary,
141
+ detail: options.detail,
142
+ artifactRefs: options.artifacts,
143
+ status: options.status || "open",
144
+ closureCondition:
145
+ options.closureCondition || (options.required ? "required=true" : ""),
146
+ source: "launcher",
147
+ required: options.required,
148
+ });
149
+ console.log(JSON.stringify(record, null, 2));
150
+ return;
151
+ }
152
+
153
+ if (subcommand === "resolve") {
154
+ const lane = options.lane || options.ownerLane;
155
+ if (!lane || !options.id || !options.agent) {
156
+ throw new Error("--lane, --id, and --agent are required for resolve");
157
+ }
158
+ const filePath = dependencyFilePath(lanePaths, lane);
159
+ const latest = materializeCoordinationState(readDependencyTickets(lanePaths.crossLaneDependenciesDir, lane)).byId.get(
160
+ options.id,
161
+ );
162
+ if (!latest) {
163
+ throw new Error(`Dependency ${options.id} not found for lane ${lane}`);
164
+ }
165
+ const record = appendDependencyTicket(lanePaths.crossLaneDependenciesDir, lane, {
166
+ ...latest,
167
+ agentId: options.agent,
168
+ status: options.status || "resolved",
169
+ detail: options.detail || latest.detail,
170
+ updatedAt: undefined,
171
+ });
172
+ console.log(JSON.stringify(record, null, 2));
173
+ return;
174
+ }
175
+
176
+ if (subcommand === "show") {
177
+ const lane = options.lane || options.ownerLane || lanePaths.lane;
178
+ const records =
179
+ options.wave === null
180
+ ? readAllDependencyTickets(lanePaths.crossLaneDependenciesDir).filter(
181
+ (record) => record.ownerLane === lane || record.requesterLane === lane || record.lane === lane,
182
+ )
183
+ : buildDependencySnapshot({
184
+ dirPath: lanePaths.crossLaneDependenciesDir,
185
+ lane,
186
+ waveNumber: options.wave,
187
+ agents: loadWaveAgents(lanePaths, options.wave),
188
+ capabilityRouting: lanePaths.capabilityRouting,
189
+ });
190
+ if (options.json || options.wave !== null) {
191
+ console.log(JSON.stringify(records, null, 2));
192
+ } else {
193
+ for (const record of records) {
194
+ console.log(
195
+ `${record.updatedAt} ${record.id} ${record.status} ${record.summary || record.detail || ""}`,
196
+ );
197
+ }
198
+ }
199
+ return;
200
+ }
201
+
202
+ if (subcommand === "render") {
203
+ const lane = options.lane || options.ownerLane || lanePaths.lane;
204
+ const snapshot = buildDependencySnapshot({
205
+ dirPath: lanePaths.crossLaneDependenciesDir,
206
+ lane,
207
+ waveNumber: options.wave ?? 0,
208
+ agents: loadWaveAgents(lanePaths, options.wave ?? 0),
209
+ capabilityRouting: lanePaths.capabilityRouting,
210
+ });
211
+ const markdownPath = dependencyMarkdownPath(lanePaths, lane);
212
+ writeJsonArtifact(path.join(lanePaths.crossLaneDependenciesDir, `${lane}.json`), snapshot);
213
+ writeTextAtomic(markdownPath, `${renderDependencySnapshotMarkdown(snapshot)}\n`);
214
+ console.log(JSON.stringify({ markdownPath, jsonPath: path.join(lanePaths.crossLaneDependenciesDir, `${lane}.json`) }, null, 2));
215
+ return;
216
+ }
217
+
218
+ throw new Error(`Unknown dep subcommand: ${subcommand}`);
219
+ }