@chllming/wave-orchestration 0.5.2 → 0.5.4

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 (79) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +56 -501
  3. package/docs/README.md +39 -0
  4. package/docs/concepts/context7-vs-skills.md +94 -0
  5. package/docs/concepts/operating-modes.md +91 -0
  6. package/docs/concepts/runtime-agnostic-orchestration.md +95 -0
  7. package/docs/concepts/what-is-a-wave.md +133 -0
  8. package/docs/guides/planner.md +113 -0
  9. package/docs/guides/terminal-surfaces.md +80 -0
  10. package/docs/image.png +0 -0
  11. package/docs/plans/context7-wave-orchestrator.md +2 -0
  12. package/docs/plans/current-state.md +10 -0
  13. package/docs/plans/master-plan.md +3 -3
  14. package/docs/plans/migration.md +4 -3
  15. package/docs/plans/wave-orchestrator.md +27 -3
  16. package/docs/reference/runtime-config/README.md +19 -0
  17. package/docs/reference/skills.md +156 -0
  18. package/docs/roadmap.md +160 -564
  19. package/package.json +2 -1
  20. package/releases/manifest.json +32 -0
  21. package/scripts/wave-orchestrator/config.mjs +17 -0
  22. package/scripts/wave-orchestrator/context7.mjs +9 -0
  23. package/scripts/wave-orchestrator/coordination.mjs +16 -0
  24. package/scripts/wave-orchestrator/executors.mjs +24 -11
  25. package/scripts/wave-orchestrator/install.mjs +41 -2
  26. package/scripts/wave-orchestrator/launcher.mjs +131 -25
  27. package/scripts/wave-orchestrator/planner.mjs +1328 -0
  28. package/scripts/wave-orchestrator/project-profile.mjs +190 -0
  29. package/scripts/wave-orchestrator/shared.mjs +2 -0
  30. package/scripts/wave-orchestrator/skills.mjs +448 -0
  31. package/scripts/wave-orchestrator/terminals.mjs +16 -0
  32. package/scripts/wave-orchestrator/traces.mjs +23 -0
  33. package/scripts/wave-orchestrator/wave-files.mjs +299 -84
  34. package/scripts/wave.mjs +11 -0
  35. package/skills/provider-aws/SKILL.md +6 -0
  36. package/skills/provider-aws/skill.json +5 -0
  37. package/skills/provider-custom-deploy/SKILL.md +5 -0
  38. package/skills/provider-custom-deploy/skill.json +5 -0
  39. package/skills/provider-docker-compose/SKILL.md +6 -0
  40. package/skills/provider-docker-compose/skill.json +5 -0
  41. package/skills/provider-github-release/SKILL.md +6 -0
  42. package/skills/provider-github-release/skill.json +5 -0
  43. package/skills/provider-kubernetes/SKILL.md +6 -0
  44. package/skills/provider-kubernetes/skill.json +5 -0
  45. package/skills/provider-railway/SKILL.md +6 -0
  46. package/skills/provider-railway/adapters/claude.md +1 -0
  47. package/skills/provider-railway/adapters/codex.md +1 -0
  48. package/skills/provider-railway/adapters/local.md +1 -0
  49. package/skills/provider-railway/adapters/opencode.md +1 -0
  50. package/skills/provider-railway/skill.json +5 -0
  51. package/skills/provider-ssh-manual/SKILL.md +6 -0
  52. package/skills/provider-ssh-manual/skill.json +5 -0
  53. package/skills/repo-coding-rules/SKILL.md +7 -0
  54. package/skills/repo-coding-rules/skill.json +5 -0
  55. package/skills/role-deploy/SKILL.md +6 -0
  56. package/skills/role-deploy/skill.json +5 -0
  57. package/skills/role-documentation/SKILL.md +6 -0
  58. package/skills/role-documentation/skill.json +5 -0
  59. package/skills/role-evaluator/SKILL.md +6 -0
  60. package/skills/role-evaluator/skill.json +5 -0
  61. package/skills/role-implementation/SKILL.md +6 -0
  62. package/skills/role-implementation/skill.json +5 -0
  63. package/skills/role-infra/SKILL.md +6 -0
  64. package/skills/role-infra/skill.json +5 -0
  65. package/skills/role-integration/SKILL.md +6 -0
  66. package/skills/role-integration/skill.json +5 -0
  67. package/skills/role-research/SKILL.md +6 -0
  68. package/skills/role-research/skill.json +5 -0
  69. package/skills/runtime-claude/SKILL.md +6 -0
  70. package/skills/runtime-claude/skill.json +5 -0
  71. package/skills/runtime-codex/SKILL.md +6 -0
  72. package/skills/runtime-codex/skill.json +5 -0
  73. package/skills/runtime-local/SKILL.md +5 -0
  74. package/skills/runtime-local/skill.json +5 -0
  75. package/skills/runtime-opencode/SKILL.md +6 -0
  76. package/skills/runtime-opencode/skill.json +5 -0
  77. package/skills/wave-core/SKILL.md +7 -0
  78. package/skills/wave-core/skill.json +5 -0
  79. package/wave.config.json +27 -0
@@ -671,6 +671,29 @@ function buildAgentMetadata(dir, run, attempt, artifacts) {
671
671
  run.lastContext7?.snippetHash ||
672
672
  (run.lastContext7?.promptText ? hashText(run.lastContext7.promptText) : ""),
673
673
  },
674
+ skills:
675
+ run.lastSkillProjection ||
676
+ (run.agent?.skillsResolved
677
+ ? {
678
+ ids: run.agent.skillsResolved.ids || [],
679
+ role: run.agent.skillsResolved.role || null,
680
+ runtime: run.agent.skillsResolved.runtime || null,
681
+ deployKind: run.agent.skillsResolved.deployKind || null,
682
+ promptHash: run.agent.skillsResolved.promptHash || null,
683
+ bundles: Array.isArray(run.agent.skillsResolved.bundles)
684
+ ? run.agent.skillsResolved.bundles.map((bundle) => ({
685
+ id: bundle.id,
686
+ bundlePath: bundle.bundlePath,
687
+ manifestPath: bundle.manifestPath,
688
+ skillPath: bundle.skillPath,
689
+ adapterPath: bundle.adapterPath || null,
690
+ bundleHash: bundle.bundleHash || null,
691
+ sourceFiles: Array.isArray(bundle.sourceFiles) ? bundle.sourceFiles.slice() : [],
692
+ }))
693
+ : [],
694
+ artifacts: run.agent.skillsResolved.artifacts || null,
695
+ }
696
+ : null),
674
697
  };
675
698
  }
676
699
 
@@ -30,6 +30,7 @@ import {
30
30
  } from "./shared.mjs";
31
31
  import { normalizeContext7Config, hashAgentPromptFingerprint } from "./context7.mjs";
32
32
  import {
33
+ isOpenCoordinationStatus,
33
34
  openClarificationLinkedRequests,
34
35
  readMaterializedCoordinationState,
35
36
  } from "./coordination-store.mjs";
@@ -42,6 +43,7 @@ import {
42
43
  validateIntegrationSummary,
43
44
  validateImplementationSummary,
44
45
  } from "./agent-state.mjs";
46
+ import { normalizeSkillId, resolveAgentSkills } from "./skills.mjs";
45
47
 
46
48
  export const WAVE_EVALUATOR_ROLE_PROMPT_PATH = DEFAULT_EVALUATOR_ROLE_PROMPT_PATH;
47
49
  export const WAVE_INTEGRATION_ROLE_PROMPT_PATH = DEFAULT_INTEGRATION_ROLE_PROMPT_PATH;
@@ -201,6 +203,76 @@ function parsePathList(blockText, filePath, label) {
201
203
  return paths;
202
204
  }
203
205
 
206
+ function parseSkillsList(blockText, filePath, label) {
207
+ if (!blockText) {
208
+ return [];
209
+ }
210
+ const skills = [];
211
+ const seen = new Set();
212
+ for (const line of String(blockText || "").split(/\r?\n/)) {
213
+ const trimmed = line.trim();
214
+ if (!trimmed) {
215
+ continue;
216
+ }
217
+ const bulletMatch = trimmed.match(/^-\s+(.+?)\s*$/);
218
+ if (!bulletMatch) {
219
+ throw new Error(`Malformed skill entry "${trimmed}" in ${label} (${filePath})`);
220
+ }
221
+ const skillId = normalizeSkillId(
222
+ bulletMatch[1].replace(/[`"']/g, "").trim(),
223
+ `${label} (${filePath})`,
224
+ );
225
+ if (seen.has(skillId)) {
226
+ throw new Error(`Duplicate skill "${skillId}" in ${label} (${filePath})`);
227
+ }
228
+ seen.add(skillId);
229
+ skills.push(skillId);
230
+ }
231
+ return skills;
232
+ }
233
+
234
+ function parseDeployEnvironments(blockText, filePath) {
235
+ if (!blockText) {
236
+ return [];
237
+ }
238
+ const environments = [];
239
+ const seen = new Set();
240
+ for (const line of String(blockText || "").split(/\r?\n/)) {
241
+ const trimmed = line.trim();
242
+ if (!trimmed) {
243
+ continue;
244
+ }
245
+ const match = trimmed.match(
246
+ /^-\s+`?([a-z0-9][a-z0-9._-]*)`?\s*:\s*`?([a-z0-9][a-z0-9._-]*)`?\s*(default)?(?:\s+\((.*)\))?$/i,
247
+ );
248
+ if (!match) {
249
+ throw new Error(`Malformed deploy environment "${trimmed}" in ${filePath}`);
250
+ }
251
+ const id = String(match[1] || "").trim().toLowerCase();
252
+ const kind = String(match[2] || "").trim().toLowerCase();
253
+ if (seen.has(id)) {
254
+ throw new Error(`Duplicate deploy environment "${id}" in ${filePath}`);
255
+ }
256
+ seen.add(id);
257
+ environments.push({
258
+ id,
259
+ kind,
260
+ isDefault: cleanBooleanToken(match[3]),
261
+ notes: String(match[4] || "").trim() || null,
262
+ });
263
+ }
264
+ if (!environments.some((environment) => environment.isDefault) && environments.length > 0) {
265
+ environments[0].isDefault = true;
266
+ }
267
+ return environments;
268
+ }
269
+
270
+ function cleanBooleanToken(value) {
271
+ return String(value || "")
272
+ .trim()
273
+ .toLowerCase() === "default";
274
+ }
275
+
204
276
  function normalizeRepoRelativePath(relPath) {
205
277
  return String(relPath || "")
206
278
  .replaceAll("\\", "/")
@@ -259,6 +331,13 @@ export function extractPromptFromSection(sectionText, filePath, agentId) {
259
331
  return extractFencedBlock(promptBlock, `Agent ${agentId} in ${filePath}`);
260
332
  }
261
333
 
334
+ function extractAgentSkillsFromSection(sectionText, filePath, agentId) {
335
+ const skillsBlock = extractSectionBody(sectionText, "Skills", filePath, agentId, {
336
+ required: false,
337
+ });
338
+ return parseSkillsList(skillsBlock, filePath, `agent ${agentId} skills`);
339
+ }
340
+
262
341
  function parseContext7Settings(blockText, filePath, label) {
263
342
  if (!blockText) {
264
343
  return null;
@@ -1280,6 +1359,7 @@ export function parseWaveContent(content, filePath, options = {}) {
1280
1359
  const executorConfig = extractExecutorConfigFromSection(sectionText, filePath, current.agentId);
1281
1360
  const components = extractAgentComponentsFromSection(sectionText, filePath, current.agentId);
1282
1361
  const capabilities = extractAgentCapabilitiesFromSection(sectionText, filePath, current.agentId);
1362
+ const skills = extractAgentSkillsFromSection(sectionText, filePath, current.agentId);
1283
1363
  const deliverables = extractAgentDeliverablesFromSection(
1284
1364
  sectionText,
1285
1365
  filePath,
@@ -1309,6 +1389,7 @@ export function parseWaveContent(content, filePath, options = {}) {
1309
1389
  executorConfig,
1310
1390
  components,
1311
1391
  capabilities,
1392
+ skills,
1312
1393
  deliverables,
1313
1394
  ownedPaths,
1314
1395
  });
@@ -1329,6 +1410,12 @@ export function parseWaveContent(content, filePath, options = {}) {
1329
1410
  wave: waveNumber,
1330
1411
  file: path.relative(REPO_ROOT, filePath),
1331
1412
  commitMessage: commitMessageMatch ? commitMessageMatch[1] : null,
1413
+ deployEnvironments: parseDeployEnvironments(
1414
+ extractTopLevelSectionBody(content, "Deploy environments", filePath, {
1415
+ required: false,
1416
+ }),
1417
+ filePath,
1418
+ ),
1332
1419
  context7Defaults: extractWaveContext7Defaults(content, filePath),
1333
1420
  componentPromotions,
1334
1421
  agents: agentsWithComponentTargets,
@@ -1608,11 +1695,19 @@ export function resolveAgentExecutor(agent, options = {}) {
1608
1695
  }
1609
1696
 
1610
1697
  export function applyExecutorSelectionsToWave(wave, options = {}) {
1611
- return {
1698
+ const laneProfile = resolveLaneProfileForOptions(options);
1699
+ const withExecutors = {
1612
1700
  ...wave,
1613
1701
  agents: wave.agents.map((agent) => ({
1614
1702
  ...agent,
1615
- executorResolved: resolveAgentExecutor(agent, options),
1703
+ executorResolved: resolveAgentExecutor(agent, { ...options, laneProfile }),
1704
+ })),
1705
+ };
1706
+ return {
1707
+ ...withExecutors,
1708
+ agents: withExecutors.agents.map((agent) => ({
1709
+ ...agent,
1710
+ skillsResolved: resolveAgentSkills(agent, withExecutors, { laneProfile }),
1616
1711
  })),
1617
1712
  };
1618
1713
  }
@@ -1878,7 +1973,21 @@ export function readWaveEvaluatorArtifacts(wave, { logsDir, evaluatorAgentId } =
1878
1973
  };
1879
1974
  }
1880
1975
 
1881
- export function completedWavesFromStatusFiles(allWaves, statusDir, options = {}) {
1976
+ function pushWaveCompletionReason(reasons, code, detail) {
1977
+ const normalizedCode = String(code || "").trim();
1978
+ const normalizedDetail = String(detail || "").trim();
1979
+ if (!normalizedCode || !normalizedDetail) {
1980
+ return;
1981
+ }
1982
+ if (
1983
+ reasons.some((reason) => reason.code === normalizedCode && reason.detail === normalizedDetail)
1984
+ ) {
1985
+ return;
1986
+ }
1987
+ reasons.push({ code: normalizedCode, detail: normalizedDetail });
1988
+ }
1989
+
1990
+ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
1882
1991
  const logsDir = options.logsDir || path.join(path.resolve(statusDir, ".."), "logs");
1883
1992
  const coordinationDir =
1884
1993
  options.coordinationDir || path.join(path.resolve(statusDir, ".."), "coordination");
@@ -1893,96 +2002,195 @@ export function completedWavesFromStatusFiles(allWaves, statusDir, options = {})
1893
2002
  const componentThreshold =
1894
2003
  options.requireComponentPromotionsFromWave ??
1895
2004
  laneProfile.validation.requireComponentPromotionsFromWave;
1896
- const completed = [];
1897
- for (const wave of allWaves) {
1898
- let waveIsComplete = wave.agents.length > 0;
1899
- const summariesByAgentId = {};
1900
- for (const agent of wave.agents) {
1901
- const statusPath = path.join(statusDir, `wave-${wave.wave}-${agent.slug}.status`);
1902
- const statusRecord = readStatusRecordIfPresent(statusPath);
1903
- if (!statusRecord) {
1904
- waveIsComplete = false;
1905
- break;
1906
- }
1907
- const expectedPromptHash = hashAgentPromptFingerprint(agent);
1908
- if (statusRecord.code !== 0 || statusRecord.promptHash !== expectedPromptHash) {
1909
- waveIsComplete = false;
1910
- break;
1911
- }
1912
- const summary = readAgentExecutionSummary(statusPath);
1913
- summariesByAgentId[agent.agentId] = summary;
1914
- if (agent.agentId === evaluatorAgentId && summary) {
1915
- if (!validateEvaluatorSummary(agent, summary).ok) {
1916
- waveIsComplete = false;
1917
- break;
1918
- }
1919
- continue;
1920
- }
1921
- if (
1922
- agent.agentId === integrationAgentId &&
1923
- integrationThreshold !== null &&
1924
- wave.wave >= integrationThreshold
1925
- ) {
1926
- if (!validateIntegrationSummary(agent, summary).ok) {
1927
- waveIsComplete = false;
1928
- break;
1929
- }
1930
- continue;
1931
- }
1932
- if (agent.agentId === documentationAgentId) {
1933
- if (!validateDocumentationClosureSummary(agent, summary).ok) {
1934
- waveIsComplete = false;
1935
- break;
2005
+
2006
+ const reasons = [];
2007
+ const summariesByAgentId = {};
2008
+ const missingStatusAgents = [];
2009
+ let statusesReady = wave.agents.length > 0;
2010
+
2011
+ for (const agent of wave.agents) {
2012
+ const statusPath = path.join(statusDir, `wave-${wave.wave}-${agent.slug}.status`);
2013
+ const statusRecord = readStatusRecordIfPresent(statusPath);
2014
+ if (!statusRecord) {
2015
+ missingStatusAgents.push(agent.agentId);
2016
+ statusesReady = false;
2017
+ continue;
2018
+ }
2019
+ const expectedPromptHash = hashAgentPromptFingerprint(agent);
2020
+ if (statusRecord.code !== 0) {
2021
+ pushWaveCompletionReason(
2022
+ reasons,
2023
+ "nonzero-status",
2024
+ `${agent.agentId} exited ${statusRecord.code} in ${path.relative(REPO_ROOT, statusPath)}.`,
2025
+ );
2026
+ statusesReady = false;
2027
+ continue;
2028
+ }
2029
+ if (statusRecord.promptHash !== expectedPromptHash) {
2030
+ pushWaveCompletionReason(
2031
+ reasons,
2032
+ "prompt-hash-mismatch",
2033
+ `${agent.agentId} status in ${path.relative(REPO_ROOT, statusPath)} does not match the current prompt fingerprint.`,
2034
+ );
2035
+ statusesReady = false;
2036
+ continue;
2037
+ }
2038
+ const summary = readAgentExecutionSummary(statusPath);
2039
+ summariesByAgentId[agent.agentId] = summary;
2040
+ if (agent.agentId === evaluatorAgentId) {
2041
+ if (summary) {
2042
+ const validation = validateEvaluatorSummary(agent, summary);
2043
+ if (!validation.ok) {
2044
+ pushWaveCompletionReason(
2045
+ reasons,
2046
+ "invalid-evaluator-summary",
2047
+ `${agent.agentId}: ${validation.statusCode}: ${validation.detail}`,
2048
+ );
2049
+ statusesReady = false;
1936
2050
  }
1937
- continue;
1938
- }
1939
- if (!validateImplementationSummary(agent, summary).ok) {
1940
- waveIsComplete = false;
1941
- break;
1942
2051
  }
2052
+ continue;
1943
2053
  }
1944
2054
  if (
1945
- waveIsComplete &&
1946
- componentThreshold !== null &&
1947
- wave.wave >= componentThreshold &&
1948
- !validateWaveComponentPromotions(wave, summariesByAgentId, options).ok
2055
+ agent.agentId === integrationAgentId &&
2056
+ integrationThreshold !== null &&
2057
+ wave.wave >= integrationThreshold
1949
2058
  ) {
1950
- waveIsComplete = false;
2059
+ const validation = validateIntegrationSummary(agent, summary);
2060
+ if (!validation.ok) {
2061
+ pushWaveCompletionReason(
2062
+ reasons,
2063
+ "invalid-integration-summary",
2064
+ `${agent.agentId}: ${validation.statusCode}: ${validation.detail}`,
2065
+ );
2066
+ statusesReady = false;
2067
+ }
2068
+ continue;
1951
2069
  }
1952
- if (
1953
- waveIsComplete &&
1954
- componentThreshold !== null &&
1955
- wave.wave >= componentThreshold &&
1956
- !validateWaveComponentMatrixCurrentLevels(wave, { ...options, laneProfile }).ok
1957
- ) {
1958
- waveIsComplete = false;
2070
+ if (agent.agentId === documentationAgentId) {
2071
+ const validation = validateDocumentationClosureSummary(agent, summary);
2072
+ if (!validation.ok) {
2073
+ pushWaveCompletionReason(
2074
+ reasons,
2075
+ "invalid-documentation-summary",
2076
+ `${agent.agentId}: ${validation.statusCode}: ${validation.detail}`,
2077
+ );
2078
+ statusesReady = false;
2079
+ }
2080
+ continue;
1959
2081
  }
1960
- if (
1961
- waveIsComplete &&
1962
- !readWaveEvaluatorArtifacts(wave, { logsDir, evaluatorAgentId }).ok
1963
- ) {
1964
- waveIsComplete = false;
2082
+ const validation = validateImplementationSummary(agent, summary);
2083
+ if (!validation.ok) {
2084
+ pushWaveCompletionReason(
2085
+ reasons,
2086
+ "invalid-implementation-summary",
2087
+ `${agent.agentId}: ${validation.statusCode}: ${validation.detail}`,
2088
+ );
2089
+ statusesReady = false;
2090
+ }
2091
+ }
2092
+
2093
+ if (missingStatusAgents.length > 0) {
2094
+ pushWaveCompletionReason(
2095
+ reasons,
2096
+ "missing-status",
2097
+ `Missing status files for ${missingStatusAgents.join(", ")}.`,
2098
+ );
2099
+ }
2100
+
2101
+ if (
2102
+ statusesReady &&
2103
+ componentThreshold !== null &&
2104
+ wave.wave >= componentThreshold
2105
+ ) {
2106
+ const promotionsValidation = validateWaveComponentPromotions(wave, summariesByAgentId, options);
2107
+ if (!promotionsValidation.ok) {
2108
+ pushWaveCompletionReason(
2109
+ reasons,
2110
+ "component-promotions-invalid",
2111
+ promotionsValidation.detail,
2112
+ );
2113
+ statusesReady = false;
1965
2114
  }
1966
- if (waveIsComplete) {
1967
- const coordinationState = readMaterializedCoordinationState(
1968
- path.join(coordinationDir, `wave-${wave.wave}.jsonl`),
2115
+ const matrixValidation = validateWaveComponentMatrixCurrentLevels(wave, {
2116
+ ...options,
2117
+ laneProfile,
2118
+ });
2119
+ if (!matrixValidation.ok) {
2120
+ pushWaveCompletionReason(
2121
+ reasons,
2122
+ "component-matrix-invalid",
2123
+ matrixValidation.detail,
1969
2124
  );
1970
- if (
1971
- coordinationState.clarifications.some((record) =>
1972
- ["open", "acknowledged", "in_progress"].includes(record.status),
1973
- ) ||
1974
- openClarificationLinkedRequests(coordinationState).length > 0 ||
1975
- coordinationState.humanEscalations.some((record) =>
1976
- ["open", "acknowledged", "in_progress"].includes(record.status),
1977
- ) ||
1978
- coordinationState.humanFeedback.some((record) =>
1979
- ["open", "acknowledged", "in_progress"].includes(record.status),
1980
- )
1981
- ) {
1982
- waveIsComplete = false;
1983
- }
2125
+ statusesReady = false;
1984
2126
  }
1985
- if (waveIsComplete) {
2127
+ }
2128
+
2129
+ if (statusesReady) {
2130
+ const evaluatorArtifacts = readWaveEvaluatorArtifacts(wave, {
2131
+ logsDir,
2132
+ evaluatorAgentId,
2133
+ });
2134
+ if (!evaluatorArtifacts.ok) {
2135
+ pushWaveCompletionReason(reasons, evaluatorArtifacts.statusCode, evaluatorArtifacts.detail);
2136
+ statusesReady = false;
2137
+ }
2138
+ }
2139
+
2140
+ const coordinationState = readMaterializedCoordinationState(
2141
+ path.join(coordinationDir, `wave-${wave.wave}.jsonl`),
2142
+ );
2143
+ const openClarificationIds = coordinationState.clarifications
2144
+ .filter((record) => isOpenCoordinationStatus(record.status))
2145
+ .map((record) => record.id);
2146
+ if (openClarificationIds.length > 0) {
2147
+ pushWaveCompletionReason(
2148
+ reasons,
2149
+ "open-clarification",
2150
+ `Open clarification records: ${openClarificationIds.join(", ")}.`,
2151
+ );
2152
+ }
2153
+ const openClarificationRequestIds = openClarificationLinkedRequests(coordinationState).map(
2154
+ (record) => record.id,
2155
+ );
2156
+ if (openClarificationRequestIds.length > 0) {
2157
+ pushWaveCompletionReason(
2158
+ reasons,
2159
+ "open-clarification-request",
2160
+ `Open clarification-linked requests: ${openClarificationRequestIds.join(", ")}.`,
2161
+ );
2162
+ }
2163
+ const openHumanEscalationIds = coordinationState.humanEscalations
2164
+ .filter((record) => isOpenCoordinationStatus(record.status))
2165
+ .map((record) => record.id);
2166
+ if (openHumanEscalationIds.length > 0) {
2167
+ pushWaveCompletionReason(
2168
+ reasons,
2169
+ "open-human-escalation",
2170
+ `Open human escalation records: ${openHumanEscalationIds.join(", ")}.`,
2171
+ );
2172
+ }
2173
+ const openHumanFeedbackIds = coordinationState.humanFeedback
2174
+ .filter((record) => isOpenCoordinationStatus(record.status))
2175
+ .map((record) => record.id);
2176
+ if (openHumanFeedbackIds.length > 0) {
2177
+ pushWaveCompletionReason(
2178
+ reasons,
2179
+ "open-human-feedback",
2180
+ `Open human feedback records: ${openHumanFeedbackIds.join(", ")}.`,
2181
+ );
2182
+ }
2183
+
2184
+ return {
2185
+ ok: reasons.length === 0,
2186
+ reasons,
2187
+ };
2188
+ }
2189
+
2190
+ export function completedWavesFromStatusFiles(allWaves, statusDir, options = {}) {
2191
+ const completed = [];
2192
+ for (const wave of allWaves) {
2193
+ if (analyzeWaveCompletionFromStatusFiles(wave, statusDir, options).ok) {
1986
2194
  completed.push(wave.wave);
1987
2195
  }
1988
2196
  }
@@ -1990,7 +2198,13 @@ export function completedWavesFromStatusFiles(allWaves, statusDir, options = {})
1990
2198
  }
1991
2199
 
1992
2200
  export function reconcileRunStateFromStatusFiles(allWaves, runStatePath, statusDir, options = {}) {
1993
- const completedFromStatus = completedWavesFromStatusFiles(allWaves, statusDir, options);
2201
+ const diagnostics = allWaves.map((wave) => ({
2202
+ wave: wave.wave,
2203
+ ...analyzeWaveCompletionFromStatusFiles(wave, statusDir, options),
2204
+ }));
2205
+ const completedFromStatus = diagnostics
2206
+ .filter((diagnostic) => diagnostic.ok)
2207
+ .map((diagnostic) => diagnostic.wave);
1994
2208
  const before = readRunState(runStatePath);
1995
2209
  const firstMerge = normalizeCompletedWaves([...before.completedWaves, ...completedFromStatus]);
1996
2210
  const latest = readRunState(runStatePath);
@@ -2003,6 +2217,7 @@ export function reconcileRunStateFromStatusFiles(allWaves, runStatePath, statusD
2003
2217
  completedFromStatus,
2004
2218
  addedFromBefore: firstMerge.filter((waveNumber) => !before.completedWaves.includes(waveNumber)),
2005
2219
  addedFromLatest: merged.filter((waveNumber) => !latest.completedWaves.includes(waveNumber)),
2220
+ blockedFromStatus: diagnostics.filter((diagnostic) => !diagnostic.ok),
2006
2221
  state,
2007
2222
  };
2008
2223
  }
package/scripts/wave.mjs CHANGED
@@ -14,6 +14,9 @@ function printHelp() {
14
14
  wave upgrade [options]
15
15
  wave changelog [options]
16
16
  wave doctor [options]
17
+ wave project setup [options]
18
+ wave project show [options]
19
+ wave draft [draft options]
17
20
  wave launch [launcher options]
18
21
  wave autonomous [autonomous options]
19
22
  wave feedback [feedback options]
@@ -40,6 +43,14 @@ if (["init", "upgrade", "changelog", "doctor"].includes(subcommand)) {
40
43
  console.error(`[wave] ${error instanceof Error ? error.message : String(error)}`);
41
44
  process.exit(Number.isInteger(error?.exitCode) ? error.exitCode : 1);
42
45
  }
46
+ } else if (subcommand === "project" || subcommand === "draft") {
47
+ try {
48
+ const { runPlannerCli } = await import("./wave-orchestrator/planner.mjs");
49
+ await runPlannerCli([subcommand, ...rest]);
50
+ } catch (error) {
51
+ console.error(`[wave] ${error instanceof Error ? error.message : String(error)}`);
52
+ process.exit(Number.isInteger(error?.exitCode) ? error.exitCode : 1);
53
+ }
43
54
  } else if (subcommand === "launch") {
44
55
  try {
45
56
  const { runLauncherCli } = await import("./wave-orchestrator/launcher.mjs");
@@ -0,0 +1,6 @@
1
+ # AWS
2
+
3
+ - Name the exact AWS service, account, region, and resource involved.
4
+ - Prefer explicit CLI or console-equivalent evidence for deployment and environment state.
5
+ - Separate IAM or identity issues from workload health or rollout issues.
6
+ - If AWS state is inferred indirectly, mark the proof gap instead of implying live verification.
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "provider-aws",
3
+ "title": "AWS",
4
+ "description": "AWS environment and rollout norms."
5
+ }
@@ -0,0 +1,5 @@
1
+ # Custom Deploy
2
+
3
+ - Make the custom environment contract explicit before treating it as proved.
4
+ - Name the exact verification surface, command, or operator artifact used as evidence.
5
+ - If the environment lacks a stable verification path, record the resulting deploy risk.
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "provider-custom-deploy",
3
+ "title": "Custom Deploy",
4
+ "description": "Fallback deploy norms for custom environments."
5
+ }
@@ -0,0 +1,6 @@
1
+ # Docker Compose
2
+
3
+ - Use compose file names, service names, ports, and health checks exactly.
4
+ - Distinguish local container health from production readiness.
5
+ - Record the exact compose commands or logs used as proof.
6
+ - Make service dependency and readiness ordering explicit when rollout depends on it.
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "provider-docker-compose",
3
+ "title": "Docker Compose",
4
+ "description": "Docker Compose environment and rollout norms."
5
+ }
@@ -0,0 +1,6 @@
1
+ # GitHub Release
2
+
3
+ - Keep tag names, release ids, asset names, and notes exact.
4
+ - Distinguish draft, prerelease, and published release state explicitly.
5
+ - Treat release notes, attached artifacts, and publication state as separate proof surfaces.
6
+ - If publication depends on another deploy system, keep that dependency explicit.
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "provider-github-release",
3
+ "title": "GitHub Release",
4
+ "description": "GitHub release and tag publication norms."
5
+ }
@@ -0,0 +1,6 @@
1
+ # Kubernetes
2
+
3
+ - Name the exact cluster, namespace, workload, and rollout surface involved.
4
+ - Prefer explicit `kubectl` state, health, and event evidence over generic rollout notes.
5
+ - Distinguish manifest drift, admission failure, image failure, and readiness failure.
6
+ - If rollback or restart is involved, make the operator-visible recovery posture explicit.
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "provider-kubernetes",
3
+ "title": "Kubernetes",
4
+ "description": "Kubernetes cluster, workload, and rollout norms."
5
+ }
@@ -0,0 +1,6 @@
1
+ # Railway
2
+
3
+ - Prefer the Railway MCP or Railway CLI as the source of truth for deployment, environment, and service state.
4
+ - Keep service names, environment names, domains, and deployment ids exact.
5
+ - Record what was verified: build logs, deploy logs, variables, domains, or rollout state.
6
+ - If Railway state is degraded or ambiguous, leave a concrete deploy risk instead of implying healthy rollout.
@@ -0,0 +1 @@
1
+ Prefer Railway MCP or CLI commands that produce exact deployment, environment, and service evidence. Avoid vague rollout claims without Railway-backed proof.
@@ -0,0 +1 @@
1
+ Use Railway MCP tools when they are available in-session. Fall back to the Railway CLI only when MCP coverage is missing or insufficient.
@@ -0,0 +1 @@
1
+ The local runtime cannot prove live Railway state. Limit output to prompt smoke validation and identify any missing live verification.
@@ -0,0 +1 @@
1
+ Use injected Railway files and commands as the authoritative path for service and deployment verification. Keep Railway identifiers exact in output.
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "provider-railway",
3
+ "title": "Railway",
4
+ "description": "Railway-specific deploy and environment norms."
5
+ }
@@ -0,0 +1,6 @@
1
+ # SSH Manual
2
+
3
+ - Treat manual host access as high-risk and fail closed on missing proof.
4
+ - Record the exact host or surface touched and the exact checks performed.
5
+ - Avoid destructive manual changes unless explicitly approved by the task and repo policy.
6
+ - Convert shell observations into explicit environment or deploy status markers.
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "provider-ssh-manual",
3
+ "title": "SSH Manual",
4
+ "description": "Manual host and SSH-based environment norms."
5
+ }
@@ -0,0 +1,7 @@
1
+ # Repo Coding Rules
2
+
3
+ - Read `AGENTS.md` before making material edits if it exists.
4
+ - Prefer small, reviewable changes that preserve existing repo patterns.
5
+ - Run the relevant tests or checks for touched surfaces and fix regressions caused by your changes.
6
+ - Keep docs aligned when implementation changes status, ownership, or proof expectations.
7
+ - Do not push by default unless the task explicitly asks for it.
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "repo-coding-rules",
3
+ "title": "Repo Coding Rules",
4
+ "description": "Repository-local coding and validation rules."
5
+ }