@chllming/wave-orchestration 0.8.9 → 0.9.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 (75) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +135 -18
  3. package/docs/README.md +9 -3
  4. package/docs/architecture/README.md +1498 -0
  5. package/docs/concepts/context7-vs-skills.md +1 -1
  6. package/docs/concepts/operating-modes.md +3 -3
  7. package/docs/concepts/what-is-a-wave.md +1 -1
  8. package/docs/guides/author-and-run-waves.md +27 -4
  9. package/docs/guides/monorepo-projects.md +226 -0
  10. package/docs/guides/planner.md +10 -3
  11. package/docs/guides/{recommendations-0.8.9.md → recommendations-0.9.1.md} +8 -7
  12. package/docs/guides/sandboxed-environments.md +158 -0
  13. package/docs/guides/terminal-surfaces.md +14 -12
  14. package/docs/plans/current-state.md +11 -7
  15. package/docs/plans/end-state-architecture.md +3 -1
  16. package/docs/plans/examples/wave-example-design-handoff.md +3 -1
  17. package/docs/plans/examples/wave-example-live-proof.md +6 -1
  18. package/docs/plans/examples/wave-example-rollout-fidelity.md +2 -0
  19. package/docs/plans/migration.md +48 -18
  20. package/docs/plans/sandbox-end-state-architecture.md +153 -0
  21. package/docs/plans/wave-orchestrator.md +4 -4
  22. package/docs/reference/cli-reference.md +125 -57
  23. package/docs/reference/coordination-and-closure.md +1 -1
  24. package/docs/reference/github-packages-setup.md +1 -1
  25. package/docs/reference/migration-0.2-to-0.5.md +9 -7
  26. package/docs/reference/npmjs-token-publishing.md +53 -0
  27. package/docs/reference/npmjs-trusted-publishing.md +4 -50
  28. package/docs/reference/package-publishing-flow.md +272 -0
  29. package/docs/reference/runtime-config/README.md +140 -12
  30. package/docs/reference/sample-waves.md +100 -5
  31. package/docs/reference/skills.md +1 -1
  32. package/docs/reference/wave-control.md +23 -5
  33. package/docs/roadmap.md +43 -201
  34. package/package.json +1 -1
  35. package/releases/manifest.json +38 -0
  36. package/scripts/wave-orchestrator/adhoc.mjs +49 -17
  37. package/scripts/wave-orchestrator/agent-process-runner.mjs +344 -0
  38. package/scripts/wave-orchestrator/agent-state.mjs +0 -1
  39. package/scripts/wave-orchestrator/artifact-schemas.mjs +7 -0
  40. package/scripts/wave-orchestrator/autonomous.mjs +96 -29
  41. package/scripts/wave-orchestrator/benchmark-external.mjs +23 -7
  42. package/scripts/wave-orchestrator/benchmark.mjs +33 -10
  43. package/scripts/wave-orchestrator/closure-engine.mjs +138 -17
  44. package/scripts/wave-orchestrator/config.mjs +239 -24
  45. package/scripts/wave-orchestrator/control-cli.mjs +71 -28
  46. package/scripts/wave-orchestrator/coord-cli.mjs +22 -14
  47. package/scripts/wave-orchestrator/coordination-store.mjs +8 -0
  48. package/scripts/wave-orchestrator/dashboard-renderer.mjs +123 -44
  49. package/scripts/wave-orchestrator/dep-cli.mjs +47 -21
  50. package/scripts/wave-orchestrator/derived-state-engine.mjs +6 -3
  51. package/scripts/wave-orchestrator/feedback.mjs +28 -11
  52. package/scripts/wave-orchestrator/gate-engine.mjs +106 -38
  53. package/scripts/wave-orchestrator/human-input-resolution.mjs +5 -1
  54. package/scripts/wave-orchestrator/install.mjs +13 -0
  55. package/scripts/wave-orchestrator/launcher-progress.mjs +91 -0
  56. package/scripts/wave-orchestrator/launcher-runtime.mjs +179 -68
  57. package/scripts/wave-orchestrator/launcher.mjs +222 -53
  58. package/scripts/wave-orchestrator/ledger.mjs +7 -2
  59. package/scripts/wave-orchestrator/planner.mjs +48 -27
  60. package/scripts/wave-orchestrator/project-profile.mjs +31 -8
  61. package/scripts/wave-orchestrator/projection-writer.mjs +13 -1
  62. package/scripts/wave-orchestrator/proof-cli.mjs +18 -12
  63. package/scripts/wave-orchestrator/reducer-snapshot.mjs +6 -0
  64. package/scripts/wave-orchestrator/retry-cli.mjs +19 -13
  65. package/scripts/wave-orchestrator/retry-control.mjs +3 -3
  66. package/scripts/wave-orchestrator/retry-engine.mjs +93 -6
  67. package/scripts/wave-orchestrator/role-helpers.mjs +30 -0
  68. package/scripts/wave-orchestrator/session-supervisor.mjs +94 -85
  69. package/scripts/wave-orchestrator/shared.mjs +77 -14
  70. package/scripts/wave-orchestrator/supervisor-cli.mjs +1306 -0
  71. package/scripts/wave-orchestrator/terminals.mjs +12 -32
  72. package/scripts/wave-orchestrator/tmux-adapter.mjs +300 -0
  73. package/scripts/wave-orchestrator/wave-control-client.mjs +84 -16
  74. package/scripts/wave-orchestrator/wave-files.mjs +43 -6
  75. package/scripts/wave.mjs +13 -0
@@ -17,6 +17,7 @@ const REPO_ROOT = WORKSPACE_ROOT;
17
17
 
18
18
  export const DEFAULT_WAVE_CONFIG_PATH = path.join(REPO_ROOT, "wave.config.json");
19
19
  export const DEFAULT_WAVE_LANE = "main";
20
+ export const DEFAULT_PROJECT_ID = "default";
20
21
  export const DEFAULT_CONT_QA_AGENT_ID = "A0";
21
22
  export const DEFAULT_CONT_EVAL_AGENT_ID = "E0";
22
23
  export const DEFAULT_INTEGRATION_AGENT_ID = "A8";
@@ -73,7 +74,8 @@ export const CODEX_SANDBOX_MODES = ["read-only", "workspace-write", "danger-full
73
74
  export const DEFAULT_CLAUDE_COMMAND = "claude";
74
75
  export const DEFAULT_OPENCODE_COMMAND = "opencode";
75
76
  export const DEFAULT_WAVE_CONTROL_AUTH_TOKEN_ENV_VAR = "WAVE_CONTROL_AUTH_TOKEN";
76
- export const DEFAULT_WAVE_CONTROL_REPORT_MODE = "metadata-plus-selected";
77
+ export const DEFAULT_WAVE_CONTROL_ENDPOINT = "https://wave-control.up.railway.app/api/v1";
78
+ export const DEFAULT_WAVE_CONTROL_REPORT_MODE = "metadata-only";
77
79
  export const DEFAULT_WAVE_CONTROL_REQUEST_TIMEOUT_MS = 5000;
78
80
  export const DEFAULT_WAVE_CONTROL_FLUSH_BATCH_SIZE = 25;
79
81
  export const DEFAULT_WAVE_CONTROL_MAX_PENDING_EVENTS = 1000;
@@ -137,6 +139,22 @@ function sanitizeLaneName(value) {
137
139
  return lane;
138
140
  }
139
141
 
142
+ export function sanitizeProjectId(value) {
143
+ const projectId = String(value || "")
144
+ .trim()
145
+ .toLowerCase()
146
+ .replace(/[^a-z0-9_-]+/g, "-")
147
+ .replace(/-+/g, "-")
148
+ .replace(/^-+|-+$/g, "");
149
+ if (!projectId) {
150
+ throw new Error("Project id is required");
151
+ }
152
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(projectId)) {
153
+ throw new Error(`Invalid project id: ${value}`);
154
+ }
155
+ return projectId;
156
+ }
157
+
140
158
  function readJsonOrNull(filePath) {
141
159
  try {
142
160
  return JSON.parse(fs.readFileSync(filePath, "utf8"));
@@ -185,6 +203,26 @@ function normalizeOptionalString(value, fallback = null) {
185
203
  return normalized || fallback;
186
204
  }
187
205
 
206
+ function joinRepoPath(basePath, childPath) {
207
+ const base = String(basePath || "")
208
+ .replaceAll("\\", "/")
209
+ .replace(/^\.\/+/, "")
210
+ .replace(/\/+/g, "/")
211
+ .replace(/\/$/, "");
212
+ const child = String(childPath || "")
213
+ .replaceAll("\\", "/")
214
+ .replace(/^\.\/+/, "")
215
+ .replace(/\/+/g, "/")
216
+ .replace(/\/$/, "");
217
+ if (!base || base === ".") {
218
+ return child;
219
+ }
220
+ if (!child) {
221
+ return base;
222
+ }
223
+ return `${base}/${child}`.replace(/\/+/g, "/");
224
+ }
225
+
188
226
  function normalizeOptionalPositiveInt(value, label, fallback = null) {
189
227
  if (value === null || value === undefined || value === "") {
190
228
  return fallback;
@@ -282,11 +320,11 @@ function normalizeThreshold(value, fallback) {
282
320
  return parsed;
283
321
  }
284
322
 
285
- function defaultDocsDirForLane(lane, defaultLane, repoMode) {
323
+ function defaultDocsDirForLane(lane, defaultLane, repoMode, projectDocsDir = DEFAULT_DOCS_DIR) {
286
324
  if (repoMode === "single-repo" && lane === defaultLane) {
287
- return DEFAULT_DOCS_DIR;
325
+ return projectDocsDir;
288
326
  }
289
- return `${DEFAULT_DOCS_DIR}/${lane}`;
327
+ return joinRepoPath(projectDocsDir, lane);
290
328
  }
291
329
 
292
330
  function defaultPlansDir(docsDir) {
@@ -549,7 +587,7 @@ function normalizeWaveControl(rawWaveControl = {}, label = "waveControl") {
549
587
  reportMode !== "disabled" && normalizeOptionalBoolean(waveControl.enabled, true);
550
588
  return {
551
589
  enabled,
552
- endpoint: normalizeOptionalString(waveControl.endpoint, null),
590
+ endpoint: normalizeOptionalString(waveControl.endpoint, DEFAULT_WAVE_CONTROL_ENDPOINT),
553
591
  workspaceId: normalizeOptionalString(waveControl.workspaceId, null),
554
592
  projectId: normalizeOptionalString(waveControl.projectId, null),
555
593
  authTokenEnvVar:
@@ -931,6 +969,7 @@ export function loadWaveConfig(configPath = DEFAULT_WAVE_CONFIG_PATH) {
931
969
  throw new Error(`Unsupported repoMode in ${path.relative(REPO_ROOT, configPath)}: ${repoMode}`);
932
970
  }
933
971
  const defaultLane = sanitizeLaneName(rawConfig.defaultLane || DEFAULT_WAVE_LANE);
972
+ const defaultProject = sanitizeProjectId(rawConfig.defaultProject || DEFAULT_PROJECT_ID);
934
973
  const paths = {
935
974
  docsDir: normalizeRepoRelativePath(rawConfig.paths?.docsDir || DEFAULT_DOCS_DIR, "paths.docsDir"),
936
975
  stateRoot: normalizeRepoRelativePath(
@@ -966,17 +1005,122 @@ export function loadWaveConfig(configPath = DEFAULT_WAVE_CONFIG_PATH) {
966
1005
  };
967
1006
  const sharedPlanDocs =
968
1007
  normalizeOptionalPathArray(rawConfig.sharedPlanDocs, "sharedPlanDocs") || null;
969
- const lanes = Object.fromEntries(
1008
+ const legacyLanes = Object.fromEntries(
970
1009
  Object.entries(rawConfig.lanes || {}).map(([laneName, laneConfig]) => [
971
1010
  sanitizeLaneName(laneName),
972
1011
  laneConfig || {},
973
1012
  ]),
974
1013
  );
1014
+ const rawProjects =
1015
+ rawConfig.projects && typeof rawConfig.projects === "object" && !Array.isArray(rawConfig.projects)
1016
+ ? rawConfig.projects
1017
+ : null;
1018
+ const projects = Object.fromEntries(
1019
+ Object.entries(
1020
+ rawProjects || {
1021
+ [defaultProject]: {
1022
+ projectName: rawConfig.projectName,
1023
+ rootDir: ".",
1024
+ sharedPlanDocs: rawConfig.sharedPlanDocs,
1025
+ lanes: legacyLanes,
1026
+ },
1027
+ },
1028
+ ).map(([projectId, projectConfig]) => {
1029
+ const normalizedProjectId = sanitizeProjectId(projectId);
1030
+ const rawProject =
1031
+ projectConfig && typeof projectConfig === "object" && !Array.isArray(projectConfig)
1032
+ ? projectConfig
1033
+ : {};
1034
+ const rootDir = normalizeRepoRelativePath(
1035
+ rawProject.rootDir || ".",
1036
+ `projects.${projectId}.rootDir`,
1037
+ );
1038
+ const projectSharedPlanDocs =
1039
+ normalizeOptionalPathArray(
1040
+ rawProject.sharedPlanDocs,
1041
+ `projects.${projectId}.sharedPlanDocs`,
1042
+ ) || null;
1043
+ const projectDocsDir = normalizeRepoRelativePath(
1044
+ rawProject.paths?.docsDir || joinRepoPath(rootDir, rawConfig.paths?.docsDir || DEFAULT_DOCS_DIR),
1045
+ `projects.${projectId}.paths.docsDir`,
1046
+ );
1047
+ const projectPaths = {
1048
+ docsDir: projectDocsDir,
1049
+ stateRoot: normalizeRepoRelativePath(
1050
+ rawProject.paths?.stateRoot || rawConfig.paths?.stateRoot || DEFAULT_STATE_ROOT,
1051
+ `projects.${projectId}.paths.stateRoot`,
1052
+ ),
1053
+ orchestratorStateDir: normalizeRepoRelativePath(
1054
+ rawProject.paths?.orchestratorStateDir ||
1055
+ rawConfig.paths?.orchestratorStateDir ||
1056
+ DEFAULT_ORCHESTRATOR_STATE_DIR,
1057
+ `projects.${projectId}.paths.orchestratorStateDir`,
1058
+ ),
1059
+ terminalsPath: normalizeRepoRelativePath(
1060
+ rawProject.paths?.terminalsPath || rawConfig.paths?.terminalsPath || DEFAULT_TERMINALS_PATH,
1061
+ `projects.${projectId}.paths.terminalsPath`,
1062
+ ),
1063
+ context7BundleIndexPath: normalizeRepoRelativePath(
1064
+ rawProject.paths?.context7BundleIndexPath ||
1065
+ rawConfig.paths?.context7BundleIndexPath ||
1066
+ DEFAULT_CONTEXT7_BUNDLE_INDEX_PATH,
1067
+ `projects.${projectId}.paths.context7BundleIndexPath`,
1068
+ ),
1069
+ benchmarkCatalogPath: normalizeRepoRelativePath(
1070
+ rawProject.paths?.benchmarkCatalogPath ||
1071
+ rawConfig.paths?.benchmarkCatalogPath ||
1072
+ DEFAULT_BENCHMARK_CATALOG_PATH,
1073
+ `projects.${projectId}.paths.benchmarkCatalogPath`,
1074
+ ),
1075
+ componentCutoverMatrixDocPath: normalizeRepoRelativePath(
1076
+ rawProject.paths?.componentCutoverMatrixDocPath ||
1077
+ rawConfig.paths?.componentCutoverMatrixDocPath ||
1078
+ defaultComponentCutoverMatrixDocPath(defaultPlansDir(projectDocsDir)),
1079
+ `projects.${projectId}.paths.componentCutoverMatrixDocPath`,
1080
+ ),
1081
+ componentCutoverMatrixJsonPath: normalizeRepoRelativePath(
1082
+ rawProject.paths?.componentCutoverMatrixJsonPath ||
1083
+ rawConfig.paths?.componentCutoverMatrixJsonPath ||
1084
+ defaultComponentCutoverMatrixJsonPath(defaultPlansDir(projectDocsDir)),
1085
+ `projects.${projectId}.paths.componentCutoverMatrixJsonPath`,
1086
+ ),
1087
+ };
1088
+ const projectLanes = Object.fromEntries(
1089
+ Object.entries(rawProject.lanes || {}).map(([laneName, laneConfig]) => [
1090
+ sanitizeLaneName(laneName),
1091
+ laneConfig || {},
1092
+ ]),
1093
+ );
1094
+ return [
1095
+ normalizedProjectId,
1096
+ {
1097
+ projectId: normalizedProjectId,
1098
+ projectName: String(
1099
+ rawProject.projectName || rawConfig.projectName || normalizedProjectId,
1100
+ ).trim(),
1101
+ rootDir,
1102
+ sharedPlanDocs: projectSharedPlanDocs,
1103
+ paths: projectPaths,
1104
+ roles: rawProject.roles || {},
1105
+ validation: rawProject.validation || {},
1106
+ executors: rawProject.executors || {},
1107
+ planner: rawProject.planner || {},
1108
+ skills: rawProject.skills || {},
1109
+ capabilityRouting: rawProject.capabilityRouting || {},
1110
+ runtimePolicy: rawProject.runtimePolicy || {},
1111
+ waveControl: rawProject.waveControl || {},
1112
+ lanes: projectLanes,
1113
+ explicit: Boolean(rawProjects),
1114
+ },
1115
+ ];
1116
+ }),
1117
+ );
975
1118
  return {
976
1119
  version: Number.parseInt(String(rawConfig.version ?? "1"), 10) || 1,
977
1120
  projectName: String(rawConfig.projectName || "Wave Orchestrator").trim(),
978
1121
  repoMode,
979
1122
  defaultLane,
1123
+ defaultProject,
980
1124
  paths,
981
1125
  roles: normalizeRoles(rawConfig.roles),
982
1126
  validation: normalizeValidation(rawConfig.validation),
@@ -987,16 +1131,82 @@ export function loadWaveConfig(configPath = DEFAULT_WAVE_CONFIG_PATH) {
987
1131
  runtimePolicy: normalizeRuntimePolicy(rawConfig.runtimePolicy),
988
1132
  waveControl: normalizeWaveControl(rawConfig.waveControl, "waveControl"),
989
1133
  sharedPlanDocs,
990
- lanes,
1134
+ lanes: legacyLanes,
1135
+ projects,
991
1136
  configPath,
992
1137
  };
993
1138
  }
994
1139
 
995
- export function resolveLaneProfile(config, laneInput = config.defaultLane) {
1140
+ export function resolveProjectProfile(config, projectInput = config.defaultProject) {
1141
+ const projectId = sanitizeProjectId(projectInput || config.defaultProject || DEFAULT_PROJECT_ID);
1142
+ const projectConfig = config.projects?.[projectId];
1143
+ if (!projectConfig) {
1144
+ throw new Error(`Unknown project: ${projectInput}`);
1145
+ }
1146
+ const paths = {
1147
+ ...config.paths,
1148
+ ...(projectConfig.paths || {}),
1149
+ };
1150
+ return {
1151
+ projectId,
1152
+ projectName: projectConfig.projectName || config.projectName,
1153
+ rootDir: projectConfig.rootDir || ".",
1154
+ paths,
1155
+ sharedPlanDocs: projectConfig.sharedPlanDocs || null,
1156
+ roles: normalizeRoles({
1157
+ ...config.roles,
1158
+ ...(projectConfig.roles || {}),
1159
+ }),
1160
+ validation: normalizeValidation({
1161
+ ...config.validation,
1162
+ ...(projectConfig.validation || {}),
1163
+ }),
1164
+ executors: normalizeExecutors(
1165
+ mergeExecutors(config.executors, projectConfig.executors || {}),
1166
+ ),
1167
+ planner: normalizePlanner({
1168
+ ...config.planner,
1169
+ ...(projectConfig.planner || {}),
1170
+ }),
1171
+ skills: mergeSkillsConfig(
1172
+ config.skills || emptySkillsConfig(),
1173
+ normalizeLaneSkills(projectConfig.skills || {}, `${projectId}.skills`, {
1174
+ preserveOmittedDir: true,
1175
+ }),
1176
+ ),
1177
+ capabilityRouting: normalizeCapabilityRouting({
1178
+ ...config.capabilityRouting,
1179
+ ...(projectConfig.capabilityRouting || {}),
1180
+ }),
1181
+ runtimePolicy: normalizeRuntimePolicy({
1182
+ ...config.runtimePolicy,
1183
+ ...(projectConfig.runtimePolicy || {}),
1184
+ }),
1185
+ waveControl: normalizeWaveControl(
1186
+ {
1187
+ ...config.waveControl,
1188
+ projectId,
1189
+ ...(projectConfig.waveControl || {}),
1190
+ },
1191
+ `projects.${projectId}.waveControl`,
1192
+ ),
1193
+ lanes: projectConfig.lanes || {},
1194
+ explicit: projectConfig.explicit === true,
1195
+ };
1196
+ }
1197
+
1198
+ export function resolveLaneProfile(config, laneInput = config.defaultLane, projectInput = config.defaultProject) {
1199
+ const projectProfile = resolveProjectProfile(config, projectInput);
996
1200
  const lane = sanitizeLaneName(laneInput || config.defaultLane);
997
- const laneConfig = config.lanes[lane] || {};
1201
+ const laneConfig = projectProfile.lanes[lane] || config.lanes[lane] || {};
998
1202
  const docsDir = normalizeRepoRelativePath(
999
- laneConfig.docsDir || defaultDocsDirForLane(lane, config.defaultLane, config.repoMode),
1203
+ laneConfig.docsDir ||
1204
+ defaultDocsDirForLane(
1205
+ lane,
1206
+ config.defaultLane,
1207
+ config.repoMode,
1208
+ projectProfile.paths.docsDir,
1209
+ ),
1000
1210
  `${lane}.docsDir`,
1001
1211
  );
1002
1212
  const plansDir = normalizeRepoRelativePath(
@@ -1008,28 +1218,28 @@ export function resolveLaneProfile(config, laneInput = config.defaultLane) {
1008
1218
  `${lane}.wavesDir`,
1009
1219
  );
1010
1220
  const roles = normalizeRoles({
1011
- ...config.roles,
1221
+ ...projectProfile.roles,
1012
1222
  ...(laneConfig.roles || {}),
1013
1223
  });
1014
1224
  const validation = normalizeValidation({
1015
- ...config.validation,
1225
+ ...projectProfile.validation,
1016
1226
  ...(laneConfig.validation || {}),
1017
1227
  });
1018
1228
  const executors = normalizeExecutors(
1019
- mergeExecutors(config.executors, laneConfig.executors),
1229
+ mergeExecutors(projectProfile.executors, laneConfig.executors),
1020
1230
  );
1021
1231
  const skills = mergeSkillsConfig(
1022
- config.skills || emptySkillsConfig(),
1232
+ projectProfile.skills || emptySkillsConfig(),
1023
1233
  normalizeLaneSkills(laneConfig.skills, `${lane}.skills`, {
1024
1234
  preserveOmittedDir: true,
1025
1235
  }),
1026
1236
  );
1027
1237
  const capabilityRouting = normalizeCapabilityRouting({
1028
- ...config.capabilityRouting,
1238
+ ...projectProfile.capabilityRouting,
1029
1239
  ...(laneConfig.capabilityRouting || {}),
1030
1240
  });
1031
1241
  const runtimePolicy = normalizeRuntimePolicy({
1032
- ...config.runtimePolicy,
1242
+ ...projectProfile.runtimePolicy,
1033
1243
  ...(laneConfig.runtimePolicy || {}),
1034
1244
  ...(laneConfig.runtimeMixTargets ? { runtimeMixTargets: laneConfig.runtimeMixTargets } : {}),
1035
1245
  ...(laneConfig.defaultExecutorByRole
@@ -1041,18 +1251,23 @@ export function resolveLaneProfile(config, laneInput = config.defaultLane) {
1041
1251
  });
1042
1252
  const waveControl = normalizeWaveControl(
1043
1253
  {
1044
- ...config.waveControl,
1254
+ ...projectProfile.waveControl,
1045
1255
  ...(laneConfig.waveControl || {}),
1046
1256
  },
1047
1257
  `${lane}.waveControl`,
1048
1258
  );
1049
1259
  return {
1260
+ projectId: projectProfile.projectId,
1261
+ projectName: projectProfile.projectName,
1262
+ projectRootDir: projectProfile.rootDir,
1263
+ explicitProject: projectProfile.explicit === true,
1050
1264
  lane,
1051
1265
  docsDir,
1052
1266
  plansDir,
1053
1267
  wavesDir,
1054
1268
  sharedPlanDocs:
1055
1269
  normalizeOptionalPathArray(laneConfig.sharedPlanDocs, `${lane}.sharedPlanDocs`) ||
1270
+ projectProfile.sharedPlanDocs ||
1056
1271
  config.sharedPlanDocs ||
1057
1272
  defaultSharedPlanDocs(plansDir),
1058
1273
  roles,
@@ -1064,34 +1279,34 @@ export function resolveLaneProfile(config, laneInput = config.defaultLane) {
1064
1279
  waveControl,
1065
1280
  paths: {
1066
1281
  terminalsPath: normalizeRepoRelativePath(
1067
- laneConfig.terminalsPath || config.paths.terminalsPath,
1282
+ laneConfig.terminalsPath || projectProfile.paths.terminalsPath,
1068
1283
  `${lane}.terminalsPath`,
1069
1284
  ),
1070
1285
  stateRoot: normalizeRepoRelativePath(
1071
- laneConfig.stateRoot || config.paths.stateRoot,
1286
+ laneConfig.stateRoot || projectProfile.paths.stateRoot,
1072
1287
  `${lane}.stateRoot`,
1073
1288
  ),
1074
1289
  orchestratorStateDir: normalizeRepoRelativePath(
1075
- laneConfig.orchestratorStateDir || config.paths.orchestratorStateDir,
1290
+ laneConfig.orchestratorStateDir || projectProfile.paths.orchestratorStateDir,
1076
1291
  `${lane}.orchestratorStateDir`,
1077
1292
  ),
1078
1293
  context7BundleIndexPath: normalizeRepoRelativePath(
1079
- laneConfig.context7BundleIndexPath || config.paths.context7BundleIndexPath,
1294
+ laneConfig.context7BundleIndexPath || projectProfile.paths.context7BundleIndexPath,
1080
1295
  `${lane}.context7BundleIndexPath`,
1081
1296
  ),
1082
1297
  benchmarkCatalogPath: normalizeRepoRelativePath(
1083
- laneConfig.benchmarkCatalogPath || config.paths.benchmarkCatalogPath,
1298
+ laneConfig.benchmarkCatalogPath || projectProfile.paths.benchmarkCatalogPath,
1084
1299
  `${lane}.benchmarkCatalogPath`,
1085
1300
  ),
1086
1301
  componentCutoverMatrixDocPath: normalizeRepoRelativePath(
1087
1302
  laneConfig.componentCutoverMatrixDocPath ||
1088
- config.paths.componentCutoverMatrixDocPath ||
1303
+ projectProfile.paths.componentCutoverMatrixDocPath ||
1089
1304
  defaultComponentCutoverMatrixDocPath(plansDir),
1090
1305
  `${lane}.componentCutoverMatrixDocPath`,
1091
1306
  ),
1092
1307
  componentCutoverMatrixJsonPath: normalizeRepoRelativePath(
1093
1308
  laneConfig.componentCutoverMatrixJsonPath ||
1094
- config.paths.componentCutoverMatrixJsonPath ||
1309
+ projectProfile.paths.componentCutoverMatrixJsonPath ||
1095
1310
  defaultComponentCutoverMatrixJsonPath(plansDir),
1096
1311
  `${lane}.componentCutoverMatrixJsonPath`,
1097
1312
  ),
@@ -19,10 +19,9 @@ import {
19
19
  DEFAULT_COORDINATION_ACK_TIMEOUT_MS,
20
20
  DEFAULT_COORDINATION_RESOLUTION_STALE_MS,
21
21
  ensureDirectory,
22
+ findAdhocRunRecord,
22
23
  parseNonNegativeInt,
23
- readJsonOrNull,
24
24
  readStatusRecordIfPresent,
25
- REPO_ROOT,
26
25
  sanitizeAdhocRunId,
27
26
  sanitizeLaneName,
28
27
  toIsoTimestamp,
@@ -43,31 +42,32 @@ import {
43
42
  import { readWaveRelaunchPlanSnapshot, readWaveRetryOverride, resolveRetryOverrideAgentIds, writeWaveRetryOverride, clearWaveRetryOverride } from "./retry-control.mjs";
44
43
  import { flushWaveControlQueue, readWaveControlQueueState } from "./wave-control-client.mjs";
45
44
  import { readAgentExecutionSummary, validateImplementationSummary } from "./agent-state.mjs";
46
- import { isContEvalReportOnlyAgent, isSecurityReviewAgent } from "./role-helpers.mjs";
45
+ import { isContEvalReportOnlyAgent, isSecurityReviewAgentForLane } from "./role-helpers.mjs";
47
46
  import {
48
47
  buildSignalStatusLine,
49
48
  syncWaveSignalProjections,
50
49
  } from "./signals.mjs";
50
+ import { summarizeSupervisorStateForWave } from "./supervisor-cli.mjs";
51
51
 
52
52
  function printUsage() {
53
53
  console.log(`Usage:
54
- wave control status --lane <lane> --wave <n> [--agent <id>] [--json]
55
- wave control telemetry status --lane <lane> [--run <id>] [--json]
56
- wave control telemetry flush --lane <lane> [--run <id>] [--json]
54
+ wave control status --project <id> --lane <lane> --wave <n> [--agent <id>] [--json]
55
+ wave control telemetry status --project <id> --lane <lane> [--run <id>] [--json]
56
+ wave control telemetry flush --project <id> --lane <lane> [--run <id>] [--json]
57
57
 
58
- wave control task create --lane <lane> --wave <n> --agent <id> --kind <request|blocker|clarification|handoff|evidence|claim|decision|human-input> --summary <text> [options]
59
- wave control task list --lane <lane> --wave <n> [--agent <id>] [--json]
60
- wave control task get --lane <lane> --wave <n> --id <task-id> [--json]
61
- wave control task act <start|resolve|dismiss|cancel|reassign|answer|escalate|defer|mark-advisory|mark-stale|resolve-policy> --lane <lane> --wave <n> --id <task-id> [options]
58
+ wave control task create --project <id> --lane <lane> --wave <n> --agent <id> --kind <request|blocker|clarification|handoff|evidence|claim|decision|human-input> --summary <text> [options]
59
+ wave control task list --project <id> --lane <lane> --wave <n> [--agent <id>] [--json]
60
+ wave control task get --project <id> --lane <lane> --wave <n> --id <task-id> [--json]
61
+ wave control task act <start|resolve|dismiss|cancel|reassign|answer|escalate|defer|mark-advisory|mark-stale|resolve-policy> --project <id> --lane <lane> --wave <n> --id <task-id> [options]
62
62
 
63
- wave control rerun request --lane <lane> --wave <n> [--agent <id> ...] [--resume-cursor <cursor>] [--reuse-attempt <id> ...] [--reuse-proof <id> ...] [--reuse-derived-summaries <true|false>] [--invalidate-component <id> ...] [--clear-reuse <id> ...] [--preserve-reuse <id> ...] [--requested-by <name>] [--reason <text>] [--json]
64
- wave control rerun get --lane <lane> --wave <n> [--json]
65
- wave control rerun clear --lane <lane> --wave <n>
63
+ wave control rerun request --project <id> --lane <lane> --wave <n> [--agent <id> ...] [--resume-cursor <cursor>] [--reuse-attempt <id> ...] [--reuse-proof <id> ...] [--reuse-derived-summaries <true|false>] [--invalidate-component <id> ...] [--clear-reuse <id> ...] [--preserve-reuse <id> ...] [--requested-by <name>] [--reason <text>] [--json]
64
+ wave control rerun get --project <id> --lane <lane> --wave <n> [--json]
65
+ wave control rerun clear --project <id> --lane <lane> --wave <n>
66
66
 
67
- wave control proof register --lane <lane> --wave <n> --agent <id> --artifact <path> [--artifact <path> ...] [--component <id[:level]> ...] [--authoritative] [--satisfy-owned-components] [--completion <level>] [--durability <level>] [--proof-level <level>] [--doc-delta <state>] [--operator <name>] [--detail <text>] [--json]
68
- wave control proof get --lane <lane> --wave <n> [--agent <id>] [--id <bundle-id>] [--json]
69
- wave control proof supersede --lane <lane> --wave <n> --id <bundle-id> --agent <id> --artifact <path> [--artifact <path> ...] [options]
70
- wave control proof revoke --lane <lane> --wave <n> --id <bundle-id> [--operator <name>] [--detail <text>] [--json]
67
+ wave control proof register --project <id> --lane <lane> --wave <n> --agent <id> --artifact <path> [--artifact <path> ...] [--component <id[:level]> ...] [--authoritative] [--satisfy-owned-components] [--completion <level>] [--durability <level>] [--proof-level <level>] [--doc-delta <state>] [--operator <name>] [--detail <text>] [--json]
68
+ wave control proof get --project <id> --lane <lane> --wave <n> [--agent <id>] [--id <bundle-id>] [--json]
69
+ wave control proof supersede --project <id> --lane <lane> --wave <n> --id <bundle-id> --agent <id> --artifact <path> [--artifact <path> ...] [options]
70
+ wave control proof revoke --project <id> --lane <lane> --wave <n> --id <bundle-id> [--operator <name>] [--detail <text>] [--json]
71
71
  `);
72
72
  }
73
73
 
@@ -91,6 +91,7 @@ function parseArgs(argv) {
91
91
  const operation = String(args[1] || "").trim().toLowerCase();
92
92
  const action = String(args[2] || "").trim().toLowerCase();
93
93
  const options = {
94
+ project: "",
94
95
  lane: "main",
95
96
  wave: null,
96
97
  runId: "",
@@ -144,7 +145,9 @@ function parseArgs(argv) {
144
145
  if (i < startIndex) {
145
146
  continue;
146
147
  }
147
- if (arg === "--lane") {
148
+ if (arg === "--project") {
149
+ options.project = String(args[++i] || "").trim();
150
+ } else if (arg === "--lane") {
148
151
  options.lane = sanitizeLaneName(args[++i]);
149
152
  } else if (arg === "--run") {
150
153
  options.runId = sanitizeAdhocRunId(args[++i]);
@@ -228,11 +231,12 @@ function parseArgs(argv) {
228
231
  return { help: false, surface, operation, action, options };
229
232
  }
230
233
 
231
- function resolveLaneForRun(runId, fallbackLane) {
232
- return (
233
- readJsonOrNull(path.join(REPO_ROOT, ".wave", "adhoc", "runs", runId, "result.json"))?.lane ||
234
- fallbackLane
235
- );
234
+ function resolveRunContext(runId, fallbackProject, fallbackLane) {
235
+ const record = findAdhocRunRecord(runId);
236
+ return {
237
+ project: record?.project || fallbackProject,
238
+ lane: record?.result?.lane || fallbackLane,
239
+ };
236
240
  }
237
241
 
238
242
  function loadWave(lanePaths, waveNumber) {
@@ -363,7 +367,8 @@ function buildLogicalAgents({
363
367
  proofRegistry || { entries: [] },
364
368
  );
365
369
  const proofValidation =
366
- !isSecurityReviewAgent(agent) && !isContEvalReportOnlyAgent(agent, { contEvalAgentId: lanePaths.contEvalAgentId })
370
+ !isSecurityReviewAgentForLane(agent, lanePaths) &&
371
+ !isContEvalReportOnlyAgent(agent, { contEvalAgentId: lanePaths.contEvalAgentId })
367
372
  ? validateImplementationSummary(agent, summary ? summary : null)
368
373
  : { ok: statusRecord?.code === 0, statusCode: statusRecord?.code === 0 ? "pass" : "pending" };
369
374
  const targetedTasks = tasks.filter(
@@ -382,7 +387,7 @@ function buildLogicalAgents({
382
387
  const satisfiedByStatus =
383
388
  statusRecord?.code === 0 &&
384
389
  (proofValidation.ok ||
385
- isSecurityReviewAgent(agent) ||
390
+ isSecurityReviewAgentForLane(agent, lanePaths) ||
386
391
  isContEvalReportOnlyAgent(agent, { contEvalAgentId: lanePaths.contEvalAgentId }));
387
392
  let state = "planned";
388
393
  let reason = "";
@@ -401,7 +406,7 @@ function buildLogicalAgents({
401
406
  lanePaths.integrationAgentId || "A8",
402
407
  lanePaths.documentationAgentId || "A9",
403
408
  lanePaths.contQaAgentId || "A0",
404
- ].includes(agent.agentId) || isSecurityReviewAgent(agent)
409
+ ].includes(agent.agentId) || isSecurityReviewAgentForLane(agent, lanePaths)
405
410
  ? "closed"
406
411
  : "satisfied";
407
412
  reason = "Completed wave preserves the latest satisfied agent state.";
@@ -422,7 +427,7 @@ function buildLogicalAgents({
422
427
  lanePaths.integrationAgentId || "A8",
423
428
  lanePaths.documentationAgentId || "A9",
424
429
  lanePaths.contQaAgentId || "A0",
425
- ].includes(agent.agentId) || isSecurityReviewAgent(agent)
430
+ ].includes(agent.agentId) || isSecurityReviewAgentForLane(agent, lanePaths)
426
431
  ? "closed"
427
432
  : "satisfied";
428
433
  reason = "Latest attempt satisfied current control-plane state.";
@@ -654,6 +659,12 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
654
659
  const controlState = readWaveControlPlaneState(lanePaths, wave.wave);
655
660
  const proofRegistry = readWaveProofRegistry(lanePaths, wave.wave) || { entries: [] };
656
661
  const relaunchPlan = readWaveRelaunchPlanSnapshot(lanePaths, wave.wave);
662
+ const supervisor = summarizeSupervisorStateForWave(lanePaths, wave.wave, {
663
+ agentId,
664
+ });
665
+ const forwardedClosureGaps = Array.isArray(relaunchPlan?.forwardedClosureGaps)
666
+ ? relaunchPlan.forwardedClosureGaps
667
+ : [];
657
668
  const rerunRequest = controlState.activeRerunRequest
658
669
  ? {
659
670
  ...controlState.activeRerunRequest,
@@ -715,6 +726,8 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
715
726
  selectionSource: selection.source,
716
727
  rerunRequest,
717
728
  relaunchPlan,
729
+ forwardedClosureGaps,
730
+ supervisor,
718
731
  nextTimer: isCompletedPhase(phase) ? null : nextTaskDeadline(tasks),
719
732
  activeAttempt: controlState.activeAttempt,
720
733
  };
@@ -755,6 +768,33 @@ function printStatus(payload) {
755
768
  console.log(buildSignalStatusLine(payload.signals.wave, payload));
756
769
  }
757
770
  console.log(`blocking=${blocking}`);
771
+ if (payload.supervisor) {
772
+ console.log(
773
+ `supervisor=${payload.supervisor.terminalDisposition || payload.supervisor.status} run_id=${payload.supervisor.runId} launcher_pid=${payload.supervisor.launcherPid || "none"}`,
774
+ );
775
+ if (payload.supervisor.sessionBackend || payload.supervisor.recoveryState || payload.supervisor.resumeAction) {
776
+ console.log(
777
+ `supervisor-backend=${payload.supervisor.sessionBackend || "unknown"} recovery=${payload.supervisor.recoveryState || "unknown"} resume=${payload.supervisor.resumeAction || "none"}`,
778
+ );
779
+ }
780
+ if ((payload.supervisor.agentRuntimeSummary || []).length > 0) {
781
+ console.log("supervisor-runtime:");
782
+ for (const record of payload.supervisor.agentRuntimeSummary) {
783
+ console.log(
784
+ `- ${record.agentId || "unknown"} ${record.terminalDisposition || "unknown"} pid=${record.pid || "none"} backend=${record.sessionBackend || "process"} attach=${record.attachMode || "log-tail"} heartbeat=${record.lastHeartbeatAt || "n/a"}`,
785
+ );
786
+ }
787
+ }
788
+ }
789
+ if ((payload.forwardedClosureGaps || []).length > 0) {
790
+ console.log("forwarded-closure-gaps:");
791
+ for (const gap of payload.forwardedClosureGaps) {
792
+ const targets = Array.isArray(gap.targets) && gap.targets.length > 0 ? gap.targets.join(",") : "none";
793
+ console.log(
794
+ `- ${gap.stageKey} agent=${gap.agentId || "unknown"} attempt=${gap.attempt ?? "n/a"} targets=${targets}${gap.detail ? ` detail=${gap.detail}` : ""}`,
795
+ );
796
+ }
797
+ }
758
798
  if (payload.nextTimer) {
759
799
  console.log(`next-timer=${payload.nextTimer.kind} ${payload.nextTimer.taskId} at ${payload.nextTimer.at}`);
760
800
  }
@@ -955,9 +995,12 @@ export async function runControlCli(argv) {
955
995
  throw new Error("Expected control surface: status | telemetry | task | rerun | proof");
956
996
  }
957
997
  if (options.runId) {
958
- options.lane = resolveLaneForRun(options.runId, options.lane);
998
+ const context = resolveRunContext(options.runId, options.project, options.lane);
999
+ options.project = context.project;
1000
+ options.lane = context.lane;
959
1001
  }
960
1002
  const lanePaths = buildLanePaths(options.lane, {
1003
+ project: options.project || undefined,
961
1004
  runVariant: options.dryRun ? "dry-run" : undefined,
962
1005
  adhocRunId: options.runId || null,
963
1006
  });