@chllming/wave-orchestration 0.8.9 → 0.9.0

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 (47) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +23 -7
  3. package/docs/README.md +5 -3
  4. package/docs/concepts/context7-vs-skills.md +1 -1
  5. package/docs/concepts/operating-modes.md +1 -1
  6. package/docs/concepts/what-is-a-wave.md +1 -1
  7. package/docs/guides/author-and-run-waves.md +14 -1
  8. package/docs/guides/monorepo-projects.md +226 -0
  9. package/docs/guides/planner.md +9 -2
  10. package/docs/guides/{recommendations-0.8.9.md → recommendations-0.9.0.md} +7 -7
  11. package/docs/plans/current-state.md +8 -6
  12. package/docs/plans/end-state-architecture.md +1 -1
  13. package/docs/plans/examples/wave-example-design-handoff.md +3 -1
  14. package/docs/plans/examples/wave-example-live-proof.md +6 -1
  15. package/docs/plans/examples/wave-example-rollout-fidelity.md +2 -0
  16. package/docs/plans/migration.md +21 -18
  17. package/docs/plans/wave-orchestrator.md +4 -4
  18. package/docs/reference/cli-reference.md +55 -51
  19. package/docs/reference/coordination-and-closure.md +1 -1
  20. package/docs/reference/npmjs-trusted-publishing.md +2 -2
  21. package/docs/reference/runtime-config/README.md +140 -12
  22. package/docs/reference/sample-waves.md +100 -5
  23. package/docs/reference/skills.md +1 -1
  24. package/docs/reference/wave-control.md +23 -5
  25. package/docs/roadmap.md +2 -2
  26. package/package.json +1 -1
  27. package/releases/manifest.json +19 -0
  28. package/scripts/wave-orchestrator/adhoc.mjs +49 -17
  29. package/scripts/wave-orchestrator/autonomous.mjs +49 -15
  30. package/scripts/wave-orchestrator/benchmark-external.mjs +23 -7
  31. package/scripts/wave-orchestrator/benchmark.mjs +33 -10
  32. package/scripts/wave-orchestrator/config.mjs +239 -24
  33. package/scripts/wave-orchestrator/control-cli.mjs +29 -23
  34. package/scripts/wave-orchestrator/coord-cli.mjs +22 -14
  35. package/scripts/wave-orchestrator/coordination-store.mjs +8 -0
  36. package/scripts/wave-orchestrator/dashboard-renderer.mjs +10 -3
  37. package/scripts/wave-orchestrator/dep-cli.mjs +47 -21
  38. package/scripts/wave-orchestrator/feedback.mjs +28 -11
  39. package/scripts/wave-orchestrator/human-input-resolution.mjs +5 -1
  40. package/scripts/wave-orchestrator/launcher.mjs +24 -3
  41. package/scripts/wave-orchestrator/planner.mjs +48 -27
  42. package/scripts/wave-orchestrator/project-profile.mjs +31 -8
  43. package/scripts/wave-orchestrator/proof-cli.mjs +18 -12
  44. package/scripts/wave-orchestrator/retry-cli.mjs +19 -13
  45. package/scripts/wave-orchestrator/shared.mjs +77 -14
  46. package/scripts/wave-orchestrator/wave-control-client.mjs +84 -16
  47. package/scripts/wave-orchestrator/wave-files.mjs +5 -1
@@ -15,10 +15,10 @@ import { parseWaveFiles } from "./wave-files.mjs";
15
15
 
16
16
  function printUsage() {
17
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]
18
+ wave dep post --owner-project <id> --owner-lane <lane> --requester-project <id> --requester-lane <lane> --owner-wave <n> --requester-wave <n> --agent <id> --summary <text> [options]
19
+ wave dep show --project <id> --lane <lane> [--wave <n>] [--json]
20
+ wave dep resolve --project <id> --lane <lane> --id <id> --agent <id> [--detail <text>] [--status resolved|closed]
21
+ wave dep render --project <id> --lane <lane> [--wave <n>] [--json]
22
22
  `);
23
23
  }
24
24
 
@@ -26,6 +26,9 @@ function parseArgs(argv) {
26
26
  const args = argv[0] === "--" ? argv.slice(1) : argv;
27
27
  const subcommand = String(args[0] || "").trim().toLowerCase();
28
28
  const options = {
29
+ project: "",
30
+ ownerProject: "",
31
+ requesterProject: "",
29
32
  lane: "",
30
33
  ownerLane: "",
31
34
  requesterLane: "",
@@ -46,7 +49,13 @@ function parseArgs(argv) {
46
49
  };
47
50
  for (let index = 1; index < args.length; index += 1) {
48
51
  const arg = args[index];
49
- if (arg === "--lane") {
52
+ if (arg === "--project") {
53
+ options.project = String(args[++index] || "").trim();
54
+ } else if (arg === "--owner-project") {
55
+ options.ownerProject = String(args[++index] || "").trim();
56
+ } else if (arg === "--requester-project") {
57
+ options.requesterProject = String(args[++index] || "").trim();
58
+ } else if (arg === "--lane") {
50
59
  options.lane = String(args[++index] || "").trim();
51
60
  } else if (arg === "--owner-lane") {
52
61
  options.ownerLane = String(args[++index] || "").trim();
@@ -112,11 +121,15 @@ export async function runDependencyCli(argv) {
112
121
  return;
113
122
  }
114
123
  const { subcommand, options } = parseArgs(argv);
124
+ const baseProject =
125
+ options.project || options.ownerProject || options.requesterProject || undefined;
115
126
  const baseLane = options.lane || options.ownerLane || options.requesterLane || "main";
116
- const lanePaths = buildLanePaths(baseLane);
127
+ const lanePaths = buildLanePaths(baseLane, { project: baseProject });
117
128
  ensureDirectory(lanePaths.crossLaneDependenciesDir);
118
129
 
119
130
  if (subcommand === "post") {
131
+ const ownerProject = options.ownerProject || options.project || lanePaths.project;
132
+ const requesterProject = options.requesterProject || options.project || lanePaths.project;
120
133
  const ownerLane = options.ownerLane || options.lane;
121
134
  const requesterLane = options.requesterLane || lanePaths.lane;
122
135
  if (!ownerLane || !requesterLane || options.ownerWave === null || options.requesterWave === null) {
@@ -125,13 +138,17 @@ export async function runDependencyCli(argv) {
125
138
  if (!options.agent || !options.summary) {
126
139
  throw new Error("--agent and --summary are required");
127
140
  }
128
- const record = appendDependencyTicket(lanePaths.crossLaneDependenciesDir, ownerLane, {
141
+ const ownerLanePaths = buildLanePaths(ownerLane, { project: ownerProject });
142
+ ensureDirectory(ownerLanePaths.crossLaneDependenciesDir);
143
+ const record = appendDependencyTicket(ownerLanePaths.crossLaneDependenciesDir, ownerLane, {
129
144
  id: options.id || `dep-${Date.now().toString(36)}`,
130
145
  kind: "request",
131
146
  lane: ownerLane,
132
147
  wave: options.ownerWave,
148
+ ownerProject,
133
149
  ownerLane,
134
150
  ownerWave: options.ownerWave,
151
+ requesterProject,
135
152
  requesterLane,
136
153
  requesterWave: options.requesterWave,
137
154
  agentId: options.agent,
@@ -155,14 +172,15 @@ export async function runDependencyCli(argv) {
155
172
  if (!lane || !options.id || !options.agent) {
156
173
  throw new Error("--lane, --id, and --agent are required for resolve");
157
174
  }
158
- const filePath = dependencyFilePath(lanePaths, lane);
159
- const latest = materializeCoordinationState(readDependencyTickets(lanePaths.crossLaneDependenciesDir, lane)).byId.get(
175
+ const targetProject = options.project || options.ownerProject || lanePaths.project;
176
+ const targetLanePaths = buildLanePaths(lane, { project: targetProject });
177
+ const latest = materializeCoordinationState(readDependencyTickets(targetLanePaths.crossLaneDependenciesDir, lane)).byId.get(
160
178
  options.id,
161
179
  );
162
180
  if (!latest) {
163
181
  throw new Error(`Dependency ${options.id} not found for lane ${lane}`);
164
182
  }
165
- const record = appendDependencyTicket(lanePaths.crossLaneDependenciesDir, lane, {
183
+ const record = appendDependencyTicket(targetLanePaths.crossLaneDependenciesDir, lane, {
166
184
  ...latest,
167
185
  agentId: options.agent,
168
186
  status: options.status || "resolved",
@@ -175,17 +193,23 @@ export async function runDependencyCli(argv) {
175
193
 
176
194
  if (subcommand === "show") {
177
195
  const lane = options.lane || options.ownerLane || lanePaths.lane;
196
+ const targetProject = options.project || options.ownerProject || lanePaths.project;
197
+ const targetLanePaths = buildLanePaths(lane, { project: targetProject });
178
198
  const records =
179
199
  options.wave === null
180
- ? readAllDependencyTickets(lanePaths.crossLaneDependenciesDir).filter(
181
- (record) => record.ownerLane === lane || record.requesterLane === lane || record.lane === lane,
200
+ ? readAllDependencyTickets(targetLanePaths.crossLaneDependenciesDir).filter(
201
+ (record) =>
202
+ (record.ownerLane === lane || record.requesterLane === lane || record.lane === lane) &&
203
+ (!options.project ||
204
+ record.ownerProject === targetProject ||
205
+ record.requesterProject === targetProject),
182
206
  )
183
207
  : buildDependencySnapshot({
184
- dirPath: lanePaths.crossLaneDependenciesDir,
208
+ dirPath: targetLanePaths.crossLaneDependenciesDir,
185
209
  lane,
186
210
  waveNumber: options.wave,
187
- agents: loadWaveAgents(lanePaths, options.wave),
188
- capabilityRouting: lanePaths.capabilityRouting,
211
+ agents: loadWaveAgents(targetLanePaths, options.wave),
212
+ capabilityRouting: targetLanePaths.capabilityRouting,
189
213
  });
190
214
  if (options.json || options.wave !== null) {
191
215
  console.log(JSON.stringify(records, null, 2));
@@ -201,20 +225,22 @@ export async function runDependencyCli(argv) {
201
225
 
202
226
  if (subcommand === "render") {
203
227
  const lane = options.lane || options.ownerLane || lanePaths.lane;
228
+ const targetProject = options.project || options.ownerProject || lanePaths.project;
229
+ const targetLanePaths = buildLanePaths(lane, { project: targetProject });
204
230
  const snapshot = buildDependencySnapshot({
205
- dirPath: lanePaths.crossLaneDependenciesDir,
231
+ dirPath: targetLanePaths.crossLaneDependenciesDir,
206
232
  lane,
207
233
  waveNumber: options.wave ?? 0,
208
- agents: loadWaveAgents(lanePaths, options.wave ?? 0),
209
- capabilityRouting: lanePaths.capabilityRouting,
234
+ agents: loadWaveAgents(targetLanePaths, options.wave ?? 0),
235
+ capabilityRouting: targetLanePaths.capabilityRouting,
210
236
  });
211
- const markdownPath = dependencyMarkdownPath(lanePaths, lane);
212
- writeDependencySnapshot(path.join(lanePaths.crossLaneDependenciesDir, `${lane}.json`), snapshot, {
237
+ const markdownPath = dependencyMarkdownPath(targetLanePaths, lane);
238
+ writeDependencySnapshot(path.join(targetLanePaths.crossLaneDependenciesDir, `${lane}.json`), snapshot, {
213
239
  lane,
214
240
  wave: options.wave ?? 0,
215
241
  });
216
242
  writeTextAtomic(markdownPath, `${renderDependencySnapshotMarkdown(snapshot)}\n`);
217
- console.log(JSON.stringify({ markdownPath, jsonPath: path.join(lanePaths.crossLaneDependenciesDir, `${lane}.json`) }, null, 2));
243
+ console.log(JSON.stringify({ markdownPath, jsonPath: path.join(targetLanePaths.crossLaneDependenciesDir, `${lane}.json`) }, null, 2));
218
244
  return;
219
245
  }
220
246
 
@@ -6,10 +6,10 @@ import {
6
6
  DEFAULT_WAIT_TIMEOUT_SECONDS,
7
7
  DEFAULT_WATCH_REFRESH_MS,
8
8
  DEFAULT_WAVE_LANE,
9
- REPO_ROOT,
10
9
  buildLanePaths,
11
10
  compactSingleLine,
12
11
  ensureDirectory,
12
+ findAdhocRunRecord,
13
13
  formatAgeFromTimestamp,
14
14
  parseNonNegativeInt,
15
15
  parsePositiveInt,
@@ -41,9 +41,12 @@ function requestFilePath(feedbackRequestsDir, requestId) {
41
41
  return path.join(feedbackRequestsDir, `${requestId}.json`);
42
42
  }
43
43
 
44
- function resolveLaneForRun(runId, fallbackLane) {
45
- const resultPath = path.join(REPO_ROOT, ".wave", "adhoc", "runs", runId, "result.json");
46
- return readJsonOrNull(resultPath)?.lane || fallbackLane;
44
+ function resolveRunContext(runId, fallbackProject, fallbackLane) {
45
+ const record = findAdhocRunRecord(runId);
46
+ return {
47
+ project: record?.project || fallbackProject,
48
+ lane: record?.result?.lane || fallbackLane,
49
+ };
47
50
  }
48
51
 
49
52
  function buildRequestId({ lane, wave, agentId }) {
@@ -55,6 +58,7 @@ function buildRequestId({ lane, wave, agentId }) {
55
58
  export function createFeedbackRequest({
56
59
  feedbackStateDir,
57
60
  feedbackRequestsDir,
61
+ project = null,
58
62
  lane,
59
63
  wave,
60
64
  agentId,
@@ -69,6 +73,7 @@ export function createFeedbackRequest({
69
73
  const now = toIsoTimestamp();
70
74
  const payload = {
71
75
  id: requestId,
76
+ project: project || null,
72
77
  createdAt: now,
73
78
  updatedAt: now,
74
79
  lane,
@@ -88,7 +93,9 @@ export function createFeedbackRequest({
88
93
  });
89
94
  if (recordTelemetry) {
90
95
  try {
91
- const lanePaths = buildLanePaths(lane);
96
+ const lanePaths = buildLanePaths(lane, {
97
+ project: project || undefined,
98
+ });
92
99
  safeQueueWaveControlEvent(lanePaths, {
93
100
  category: "feedback",
94
101
  entityType: "human_input",
@@ -148,7 +155,9 @@ export function answerFeedbackRequest({
148
155
  });
149
156
  if (recordTelemetry) {
150
157
  try {
151
- const lanePaths = buildLanePaths(answeredPayload?.lane || DEFAULT_WAVE_LANE);
158
+ const lanePaths = buildLanePaths(answeredPayload?.lane || DEFAULT_WAVE_LANE, {
159
+ project: answeredPayload?.project || undefined,
160
+ });
152
161
  safeQueueWaveControlEvent(lanePaths, {
153
162
  category: "feedback",
154
163
  entityType: "human_input",
@@ -245,6 +254,7 @@ function parseFeedbackArgs(argv) {
245
254
  id: "",
246
255
  response: "",
247
256
  operator: "human-operator",
257
+ project: "",
248
258
  force: false,
249
259
  pending: false,
250
260
  json: false,
@@ -255,7 +265,9 @@ function parseFeedbackArgs(argv) {
255
265
  if (arg === "--") {
256
266
  continue;
257
267
  }
258
- if (arg === "--lane") {
268
+ if (arg === "--project") {
269
+ out.project = String(args[++i] || "").trim();
270
+ } else if (arg === "--lane") {
259
271
  out.lane = sanitizeLaneName(args[++i]);
260
272
  } else if (arg === "--run") {
261
273
  out.runId = sanitizeAdhocRunId(args[++i]);
@@ -310,10 +322,10 @@ async function waitForAnswer(filePath, timeoutSeconds) {
310
322
 
311
323
  function printHelp() {
312
324
  console.log(`Usage:
313
- pnpm exec wave-feedback ask --lane <lane> --wave <n> --agent <id> --question "<text>" [options]
325
+ pnpm exec wave-feedback ask --project <id> --lane <lane> --wave <n> --agent <id> --question "<text>" [options]
314
326
  pnpm exec wave-feedback respond --id <request-id> --response "<text>" [options]
315
- pnpm exec wave-feedback list [--pending] [--lane <lane>] [--wave <n>] [--agent <id>] [--json]
316
- pnpm exec wave-feedback watch [--pending] [--lane <lane>] [--wave <n>] [--agent <id>] [--refresh-ms <n>]
327
+ pnpm exec wave-feedback list [--pending] [--project <id>] [--lane <lane>] [--wave <n>] [--agent <id>] [--json]
328
+ pnpm exec wave-feedback watch [--pending] [--project <id>] [--lane <lane>] [--wave <n>] [--agent <id>] [--refresh-ms <n>]
317
329
  pnpm exec wave-feedback show --id <request-id>
318
330
  `);
319
331
  }
@@ -325,9 +337,12 @@ export async function runFeedbackCli(argv) {
325
337
  return;
326
338
  }
327
339
  if (options.runId) {
328
- options.lane = resolveLaneForRun(options.runId, options.lane);
340
+ const context = resolveRunContext(options.runId, options.project, options.lane);
341
+ options.project = context.project;
342
+ options.lane = context.lane;
329
343
  }
330
344
  const lanePaths = buildLanePaths(options.lane, {
345
+ project: options.project || undefined,
331
346
  adhocRunId: options.runId || null,
332
347
  });
333
348
  const requestsDir = lanePaths.feedbackRequestsDir;
@@ -340,6 +355,7 @@ export async function runFeedbackCli(argv) {
340
355
  const result = createFeedbackRequest({
341
356
  feedbackStateDir: stateDir,
342
357
  feedbackRequestsDir: requestsDir,
358
+ project: lanePaths.project,
343
359
  lane: options.lane,
344
360
  wave: options.wave,
345
361
  agentId: options.agent,
@@ -375,6 +391,7 @@ export async function runFeedbackCli(argv) {
375
391
  });
376
392
  if (answered?.lane && Number.isFinite(Number(answered.wave))) {
377
393
  answerHumanInputByRequest({
394
+ project: answered.project || options.project || null,
378
395
  lane: answered.lane,
379
396
  waveNumber: Number(answered.wave),
380
397
  requestId: options.id,
@@ -331,13 +331,17 @@ export function answerHumanInputAndReconcile({
331
331
  }
332
332
 
333
333
  export function answerHumanInputByRequest({
334
+ project = null,
334
335
  lane,
335
336
  waveNumber,
336
337
  requestId,
337
338
  operator = "human-operator",
338
339
  runId = null,
339
340
  }) {
340
- const lanePaths = buildLanePaths(lane, { adhocRunId: runId || null });
341
+ const lanePaths = buildLanePaths(lane, {
342
+ project: project || undefined,
343
+ adhocRunId: runId || null,
344
+ });
341
345
  const wave = loadWave(lanePaths, waveNumber);
342
346
  return answerHumanInputAndReconcile({
343
347
  lanePaths,
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import {
6
6
  CODEX_SANDBOX_MODES,
7
7
  DEFAULT_CODEX_SANDBOX_MODE,
8
+ loadWaveConfig,
8
9
  normalizeCodexSandboxMode,
9
10
  normalizeExecutorMode,
10
11
  SUPPORTED_EXECUTOR_MODES,
@@ -234,6 +235,7 @@ function printUsage(lanePaths, terminalSurface) {
234
235
  console.log(`Usage: pnpm exec wave launch [options]
235
236
 
236
237
  Options:
238
+ --project <id> Project id (default: ${lanePaths.project})
237
239
  --lane <name> Wave lane name (default: ${DEFAULT_WAVE_LANE})
238
240
  --start-wave <n> Start from wave number (default: 0)
239
241
  --end-wave <n> End at wave number (default: last available)
@@ -278,9 +280,10 @@ Options:
278
280
  }
279
281
 
280
282
  function parseArgs(argv) {
281
- let lanePaths = buildLanePaths(DEFAULT_WAVE_LANE);
282
- const projectProfile = readProjectProfile();
283
+ const config = loadWaveConfig();
284
+ let lanePaths = buildLanePaths(DEFAULT_WAVE_LANE, { config, project: config.defaultProject });
283
285
  const options = {
286
+ project: config.defaultProject,
284
287
  lane: DEFAULT_WAVE_LANE,
285
288
  startWave: 0,
286
289
  endWave: null,
@@ -298,7 +301,9 @@ function parseArgs(argv) {
298
301
  codexSandboxMode: DEFAULT_CODEX_SANDBOX_MODE,
299
302
  manifestOut: lanePaths.defaultManifestPath,
300
303
  dryRun: false,
301
- terminalSurface: resolveDefaultTerminalSurface(projectProfile),
304
+ terminalSurface: resolveDefaultTerminalSurface(
305
+ readProjectProfile({ config, project: config.defaultProject }),
306
+ ),
302
307
  dashboard: true,
303
308
  cleanupSessions: true,
304
309
  keepTerminals: false,
@@ -348,14 +353,28 @@ function parseArgs(argv) {
348
353
  } else if (arg === "--no-orchestrator-board") {
349
354
  options.orchestratorBoardPath = null;
350
355
  orchestratorBoardProvided = true;
356
+ } else if (arg === "--project") {
357
+ options.project = String(argv[++i] || "").trim();
358
+ lanePaths = buildLanePaths(options.lane, {
359
+ config,
360
+ project: options.project,
361
+ adhocRunId: options.adhocRunId,
362
+ });
363
+ options.terminalSurface = resolveDefaultTerminalSurface(
364
+ readProjectProfile({ config, project: options.project }),
365
+ );
351
366
  } else if (arg === "--lane") {
352
367
  options.lane = String(argv[++i] || "").trim();
353
368
  lanePaths = buildLanePaths(options.lane, {
369
+ config,
370
+ project: options.project,
354
371
  adhocRunId: options.adhocRunId,
355
372
  });
356
373
  } else if (arg === "--adhoc-run") {
357
374
  options.adhocRunId = sanitizeAdhocRunId(argv[++i]);
358
375
  lanePaths = buildLanePaths(options.lane, {
376
+ config,
377
+ project: options.project,
359
378
  adhocRunId: options.adhocRunId,
360
379
  });
361
380
  } else if (arg === "--orchestrator-id") {
@@ -406,6 +425,8 @@ function parseArgs(argv) {
406
425
  }
407
426
 
408
427
  lanePaths = buildLanePaths(options.lane, {
428
+ config,
429
+ project: options.project,
409
430
  runVariant: options.dryRun ? "dry-run" : undefined,
410
431
  adhocRunId: options.adhocRunId,
411
432
  });
@@ -36,8 +36,8 @@ import {
36
36
  buildDefaultProjectProfile,
37
37
  normalizeDraftTemplate,
38
38
  normalizeOversightMode,
39
+ projectProfilePath,
39
40
  PROJECT_OVERSIGHT_MODES,
40
- PROJECT_PROFILE_PATH,
41
41
  PROJECT_PROFILE_TERMINAL_SURFACES,
42
42
  readProjectProfile,
43
43
  updateProjectProfile,
@@ -1336,8 +1336,9 @@ function collectPlannerSources({ config, lanePaths, task, fromWave }) {
1336
1336
  )) {
1337
1337
  sources.add(sourcePath, "core-context", "planner core context");
1338
1338
  }
1339
- if (fileExists(PROJECT_PROFILE_PATH)) {
1340
- sources.add(repoRelativePath(PROJECT_PROFILE_PATH), "project-profile", "saved project profile");
1339
+ const plannerProfilePath = projectProfilePath(lanePaths.project || config.defaultProject);
1340
+ if (fileExists(plannerProfilePath)) {
1341
+ sources.add(repoRelativePath(plannerProfilePath), "project-profile", "saved project profile");
1341
1342
  }
1342
1343
  for (const sourcePath of ensureRepoRelativePathList(
1343
1344
  plannerConfig.lessonsPaths || DEFAULT_PLANNER_AGENTIC_LESSONS_PATHS,
@@ -2587,9 +2588,10 @@ function parsePlannerWaveSelection(rawValue, waveOrder) {
2587
2588
 
2588
2589
  async function runAgenticDraftFlow(options = {}) {
2589
2590
  const config = options.config || loadWaveConfig();
2590
- const profile = await ensureProjectProfile({ config });
2591
+ const projectId = options.project || config.defaultProject;
2592
+ const profile = await ensureProjectProfile({ config, project: projectId });
2591
2593
  const lane = options.lane || profile.plannerDefaults.lane || config.defaultLane;
2592
- const lanePaths = buildLanePaths(lane, { config });
2594
+ const lanePaths = buildLanePaths(lane, { config, project: projectId });
2593
2595
  const matrix = loadComponentCutoverMatrix({ laneProfile: lanePaths.laneProfile });
2594
2596
  const bundleIndex = loadContext7BundleIndex(lanePaths.context7BundleIndexPath);
2595
2597
  const plannerExecutorProfile =
@@ -2794,7 +2796,7 @@ async function runAgenticDraftFlow(options = {}) {
2794
2796
  lane: lanePaths.lane,
2795
2797
  },
2796
2798
  }),
2797
- { config },
2799
+ { config, project: projectId },
2798
2800
  );
2799
2801
  return resultPayload;
2800
2802
  }
@@ -2810,7 +2812,12 @@ async function runApplyPlannerRun(options = {}) {
2810
2812
  );
2811
2813
  }
2812
2814
  const config = options.config || loadWaveConfig();
2813
- const lanePaths = buildLanePaths(bundle.request.lane, { config });
2815
+ const projectId =
2816
+ cleanText(options.project) ||
2817
+ cleanText(bundle.request.project) ||
2818
+ cleanText(bundle.result?.project) ||
2819
+ config.defaultProject;
2820
+ const lanePaths = buildLanePaths(bundle.request.lane, { config, project: projectId });
2814
2821
  const selectedWaves = parsePlannerWaveSelection(options.waves, bundle.plan?.waveOrder || []);
2815
2822
  if (selectedWaves.length === 0) {
2816
2823
  throw new Error("No waves selected for apply");
@@ -2894,12 +2901,13 @@ function showPlannerRun(options = {}) {
2894
2901
 
2895
2902
  async function ensureProjectProfile(options = {}) {
2896
2903
  const config = options.config || loadWaveConfig();
2897
- const existing = readProjectProfile({ config });
2904
+ const existing = readProjectProfile({ config, project: options.project });
2898
2905
  if (existing) {
2899
2906
  return existing;
2900
2907
  }
2901
2908
  return runProjectSetupFlow({
2902
2909
  config,
2910
+ project: options.project,
2903
2911
  json: false,
2904
2912
  fromDraft: true,
2905
2913
  });
@@ -2907,12 +2915,14 @@ async function ensureProjectProfile(options = {}) {
2907
2915
 
2908
2916
  async function runProjectSetupFlow(options = {}) {
2909
2917
  const config = options.config || loadWaveConfig();
2910
- const existing = readProjectProfile({ config });
2918
+ const projectId = cleanText(options.project || config.defaultProject);
2919
+ const existing = readProjectProfile({ config, project: projectId });
2911
2920
  const base = existing || buildDefaultProjectProfile(config);
2912
2921
  const prompt = new PromptSession();
2913
2922
  try {
2923
+ const projectLanes = Object.keys(config.projects?.[projectId]?.lanes || {});
2914
2924
  const laneChoices = Array.from(
2915
- new Set([config.defaultLane, ...Object.keys(config.lanes || {})].filter(Boolean)),
2925
+ new Set([config.defaultLane, ...projectLanes, ...Object.keys(config.lanes || {})].filter(Boolean)),
2916
2926
  );
2917
2927
  const newProject = await prompt.askBoolean("Treat this repository as a new project?", base.newProject);
2918
2928
  const defaultOversightMode = normalizeOversightMode(
@@ -2994,7 +3004,7 @@ async function runProjectSetupFlow(options = {}) {
2994
3004
  lane,
2995
3005
  },
2996
3006
  },
2997
- { config },
3007
+ { config, project: projectId },
2998
3008
  );
2999
3009
  return profile;
3000
3010
  } finally {
@@ -3284,10 +3294,11 @@ async function collectEvalTargets({ prompt }) {
3284
3294
 
3285
3295
  async function runDraftFlow(options = {}) {
3286
3296
  const config = options.config || loadWaveConfig();
3287
- const profile = await ensureProjectProfile({ config });
3297
+ const projectId = options.project || config.defaultProject;
3298
+ const profile = await ensureProjectProfile({ config, project: projectId });
3288
3299
  const waveNumber = options.wave;
3289
3300
  const lane = options.lane || profile.plannerDefaults.lane || config.defaultLane;
3290
- const lanePaths = buildLanePaths(lane, { config });
3301
+ const lanePaths = buildLanePaths(lane, { config, project: projectId });
3291
3302
  const matrix = loadComponentCutoverMatrix({ laneProfile: lanePaths.laneProfile });
3292
3303
  const bundleIndex = loadContext7BundleIndex(lanePaths.context7BundleIndexPath);
3293
3304
  const context7BundleChoices = Object.keys(bundleIndex.bundles || {}).sort((left, right) =>
@@ -3409,9 +3420,10 @@ async function runDraftFlow(options = {}) {
3409
3420
  lane: lanePaths.lane,
3410
3421
  },
3411
3422
  }),
3412
- { config },
3423
+ { config, project: projectId },
3413
3424
  );
3414
3425
  return {
3426
+ project: projectId,
3415
3427
  wave: waveNumber,
3416
3428
  lane: lanePaths.lane,
3417
3429
  template,
@@ -3419,7 +3431,7 @@ async function runDraftFlow(options = {}) {
3419
3431
  specPath: path.relative(REPO_ROOT, specPath),
3420
3432
  matrixJsonPath: path.relative(REPO_ROOT, path.resolve(REPO_ROOT, lanePaths.componentCutoverMatrixJsonPath)),
3421
3433
  matrixDocPath: path.relative(REPO_ROOT, path.resolve(REPO_ROOT, lanePaths.componentCutoverMatrixDocPath)),
3422
- profilePath: path.relative(REPO_ROOT, PROJECT_PROFILE_PATH),
3434
+ profilePath: path.relative(REPO_ROOT, projectProfilePath(projectId)),
3423
3435
  };
3424
3436
  } finally {
3425
3437
  await prompt.close();
@@ -3428,12 +3440,12 @@ async function runDraftFlow(options = {}) {
3428
3440
 
3429
3441
  function printPlannerHelp() {
3430
3442
  console.log(`Usage:
3431
- wave project setup [--json]
3432
- wave project show [--json]
3433
- wave draft --wave <n> [--lane <lane>] [--template implementation|qa|infra|release] [--force] [--json]
3434
- wave draft --agentic --task "<text>" --from-wave <n> [--lane <lane>] [--max-waves <n>] [--planner-executor <profile>] [--json]
3443
+ wave project setup [--project <id>] [--json]
3444
+ wave project show [--project <id>] [--json]
3445
+ wave draft --wave <n> [--project <id>] [--lane <lane>] [--template implementation|qa|infra|release] [--force] [--json]
3446
+ wave draft --agentic --task "<text>" --from-wave <n> [--project <id>] [--lane <lane>] [--max-waves <n>] [--planner-executor <profile>] [--json]
3435
3447
  wave draft --show-run <run-id> [--json]
3436
- wave draft --apply-run <run-id> [--waves <list>|all] [--force] [--json]
3448
+ wave draft --apply-run <run-id> [--project <id>] [--waves <list>|all] [--force] [--json]
3437
3449
  `);
3438
3450
  }
3439
3451
 
@@ -3444,6 +3456,7 @@ export async function runPlannerCli(argv) {
3444
3456
  json: false,
3445
3457
  force: false,
3446
3458
  wave: null,
3459
+ project: null,
3447
3460
  lane: null,
3448
3461
  template: null,
3449
3462
  agentic: false,
@@ -3464,6 +3477,8 @@ export async function runPlannerCli(argv) {
3464
3477
  for (const arg of args) {
3465
3478
  if (arg === "--json") {
3466
3479
  options.json = true;
3480
+ } else if (arg === "--project") {
3481
+ options.project = cleanText(args.shift());
3467
3482
  } else if (arg === "--help" || arg === "-h") {
3468
3483
  printPlannerHelp();
3469
3484
  return;
@@ -3472,15 +3487,17 @@ export async function runPlannerCli(argv) {
3472
3487
  }
3473
3488
  }
3474
3489
  if (action === "setup") {
3475
- const profile = await runProjectSetupFlow({ json: options.json });
3490
+ const projectId = options.project || loadWaveConfig().defaultProject;
3491
+ const profile = await runProjectSetupFlow({ json: options.json, project: projectId });
3476
3492
  if (options.json) {
3477
3493
  printJson({
3478
- profilePath: path.relative(REPO_ROOT, PROJECT_PROFILE_PATH),
3494
+ profilePath: path.relative(REPO_ROOT, projectProfilePath(projectId)),
3479
3495
  profile,
3480
3496
  });
3481
3497
  return;
3482
3498
  }
3483
- console.log(`[wave:project] profile: ${path.relative(REPO_ROOT, PROJECT_PROFILE_PATH)}`);
3499
+ console.log(`[wave:project] profile: ${path.relative(REPO_ROOT, projectProfilePath(projectId))}`);
3500
+ console.log(`[wave:project] project_id=${projectId}`);
3484
3501
  console.log(`[wave:project] lane=${profile.plannerDefaults.lane}`);
3485
3502
  console.log(`[wave:project] template=${profile.plannerDefaults.template}`);
3486
3503
  console.log(`[wave:project] oversight=${profile.defaultOversightMode}`);
@@ -3488,19 +3505,21 @@ export async function runPlannerCli(argv) {
3488
3505
  return;
3489
3506
  }
3490
3507
  if (action === "show") {
3491
- const profile = readProjectProfile();
3508
+ const projectId = options.project || loadWaveConfig().defaultProject;
3509
+ const profile = readProjectProfile({ project: projectId });
3492
3510
  if (options.json) {
3493
3511
  printJson({
3494
- profilePath: path.relative(REPO_ROOT, PROJECT_PROFILE_PATH),
3512
+ profilePath: path.relative(REPO_ROOT, projectProfilePath(projectId)),
3495
3513
  profile,
3496
3514
  });
3497
3515
  return;
3498
3516
  }
3499
3517
  if (!profile) {
3500
- console.log(`[wave:project] no saved profile at ${path.relative(REPO_ROOT, PROJECT_PROFILE_PATH)}`);
3518
+ console.log(`[wave:project] no saved profile at ${path.relative(REPO_ROOT, projectProfilePath(projectId))}`);
3501
3519
  return;
3502
3520
  }
3503
- console.log(`[wave:project] profile: ${path.relative(REPO_ROOT, PROJECT_PROFILE_PATH)}`);
3521
+ console.log(`[wave:project] profile: ${path.relative(REPO_ROOT, projectProfilePath(projectId))}`);
3522
+ console.log(`[wave:project] project_id=${projectId}`);
3504
3523
  console.log(`[wave:project] project=${profile.source.projectName}`);
3505
3524
  console.log(`[wave:project] lane=${profile.plannerDefaults.lane}`);
3506
3525
  console.log(`[wave:project] template=${profile.plannerDefaults.template}`);
@@ -3537,6 +3556,8 @@ export async function runPlannerCli(argv) {
3537
3556
  options.waves = cleanText(args[++index]);
3538
3557
  } else if (arg === "--wave") {
3539
3558
  options.wave = Number.parseInt(String(args[++index] || ""), 10);
3559
+ } else if (arg === "--project") {
3560
+ options.project = cleanText(args[++index]);
3540
3561
  } else if (arg === "--lane") {
3541
3562
  options.lane = cleanText(args[++index]);
3542
3563
  } else if (arg === "--template") {