@chllming/wave-orchestration 0.5.3 → 0.6.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 (136) hide show
  1. package/CHANGELOG.md +53 -3
  2. package/README.md +81 -506
  3. package/docs/README.md +53 -0
  4. package/docs/agents/wave-cont-eval-role.md +36 -0
  5. package/docs/agents/{wave-evaluator-role.md → wave-cont-qa-role.md} +14 -11
  6. package/docs/agents/wave-documentation-role.md +1 -1
  7. package/docs/agents/wave-infra-role.md +1 -1
  8. package/docs/agents/wave-integration-role.md +3 -3
  9. package/docs/agents/wave-launcher-role.md +4 -3
  10. package/docs/agents/wave-security-role.md +40 -0
  11. package/docs/concepts/context7-vs-skills.md +94 -0
  12. package/docs/concepts/operating-modes.md +91 -0
  13. package/docs/concepts/runtime-agnostic-orchestration.md +95 -0
  14. package/docs/concepts/what-is-a-wave.md +183 -0
  15. package/docs/evals/README.md +166 -0
  16. package/docs/evals/benchmark-catalog.json +663 -0
  17. package/docs/guides/author-and-run-waves.md +135 -0
  18. package/docs/guides/planner.md +118 -0
  19. package/docs/guides/terminal-surfaces.md +82 -0
  20. package/docs/image.png +0 -0
  21. package/docs/plans/component-cutover-matrix.json +1 -1
  22. package/docs/plans/component-cutover-matrix.md +1 -1
  23. package/docs/plans/context7-wave-orchestrator.md +2 -0
  24. package/docs/plans/current-state.md +29 -1
  25. package/docs/plans/examples/wave-example-live-proof.md +435 -0
  26. package/docs/plans/master-plan.md +3 -3
  27. package/docs/plans/migration.md +46 -3
  28. package/docs/plans/wave-orchestrator.md +71 -8
  29. package/docs/plans/waves/wave-0.md +4 -4
  30. package/docs/reference/live-proof-waves.md +177 -0
  31. package/docs/reference/migration-0.2-to-0.5.md +26 -19
  32. package/docs/reference/npmjs-trusted-publishing.md +6 -5
  33. package/docs/reference/runtime-config/README.md +29 -0
  34. package/docs/reference/sample-waves.md +87 -0
  35. package/docs/reference/skills.md +224 -0
  36. package/docs/research/agent-context-sources.md +130 -11
  37. package/docs/research/coordination-failure-review.md +266 -0
  38. package/docs/roadmap.md +164 -564
  39. package/package.json +3 -2
  40. package/releases/manifest.json +37 -2
  41. package/scripts/research/agent-context-archive.mjs +83 -1
  42. package/scripts/research/manifests/agent-context-expanded-2026-03-22.mjs +811 -0
  43. package/scripts/wave-orchestrator/adhoc.mjs +1331 -0
  44. package/scripts/wave-orchestrator/agent-state.mjs +358 -6
  45. package/scripts/wave-orchestrator/artifact-schemas.mjs +173 -0
  46. package/scripts/wave-orchestrator/clarification-triage.mjs +10 -3
  47. package/scripts/wave-orchestrator/config.mjs +65 -12
  48. package/scripts/wave-orchestrator/context7.mjs +11 -0
  49. package/scripts/wave-orchestrator/coord-cli.mjs +51 -19
  50. package/scripts/wave-orchestrator/coordination-store.mjs +26 -4
  51. package/scripts/wave-orchestrator/coordination.mjs +99 -9
  52. package/scripts/wave-orchestrator/dashboard-state.mjs +20 -8
  53. package/scripts/wave-orchestrator/dep-cli.mjs +5 -2
  54. package/scripts/wave-orchestrator/docs-queue.mjs +8 -2
  55. package/scripts/wave-orchestrator/evals.mjs +451 -0
  56. package/scripts/wave-orchestrator/executors.mjs +24 -11
  57. package/scripts/wave-orchestrator/feedback.mjs +15 -1
  58. package/scripts/wave-orchestrator/install.mjs +69 -7
  59. package/scripts/wave-orchestrator/launcher-closure.mjs +281 -0
  60. package/scripts/wave-orchestrator/launcher-runtime.mjs +334 -0
  61. package/scripts/wave-orchestrator/launcher.mjs +778 -577
  62. package/scripts/wave-orchestrator/ledger.mjs +123 -20
  63. package/scripts/wave-orchestrator/local-executor.mjs +99 -12
  64. package/scripts/wave-orchestrator/planner.mjs +1463 -0
  65. package/scripts/wave-orchestrator/project-profile.mjs +190 -0
  66. package/scripts/wave-orchestrator/replay.mjs +6 -3
  67. package/scripts/wave-orchestrator/role-helpers.mjs +84 -0
  68. package/scripts/wave-orchestrator/shared.mjs +77 -11
  69. package/scripts/wave-orchestrator/skills.mjs +979 -0
  70. package/scripts/wave-orchestrator/terminals.mjs +16 -0
  71. package/scripts/wave-orchestrator/traces.mjs +73 -27
  72. package/scripts/wave-orchestrator/wave-files.mjs +1224 -163
  73. package/scripts/wave.mjs +20 -0
  74. package/skills/README.md +202 -0
  75. package/skills/provider-aws/SKILL.md +117 -0
  76. package/skills/provider-aws/adapters/claude.md +1 -0
  77. package/skills/provider-aws/adapters/codex.md +1 -0
  78. package/skills/provider-aws/references/service-verification.md +39 -0
  79. package/skills/provider-aws/skill.json +54 -0
  80. package/skills/provider-custom-deploy/SKILL.md +64 -0
  81. package/skills/provider-custom-deploy/skill.json +50 -0
  82. package/skills/provider-docker-compose/SKILL.md +96 -0
  83. package/skills/provider-docker-compose/adapters/local.md +1 -0
  84. package/skills/provider-docker-compose/skill.json +53 -0
  85. package/skills/provider-github-release/SKILL.md +121 -0
  86. package/skills/provider-github-release/adapters/claude.md +1 -0
  87. package/skills/provider-github-release/adapters/codex.md +1 -0
  88. package/skills/provider-github-release/skill.json +55 -0
  89. package/skills/provider-kubernetes/SKILL.md +143 -0
  90. package/skills/provider-kubernetes/adapters/claude.md +1 -0
  91. package/skills/provider-kubernetes/adapters/codex.md +1 -0
  92. package/skills/provider-kubernetes/references/kubectl-patterns.md +58 -0
  93. package/skills/provider-kubernetes/skill.json +52 -0
  94. package/skills/provider-railway/SKILL.md +123 -0
  95. package/skills/provider-railway/adapters/claude.md +1 -0
  96. package/skills/provider-railway/adapters/codex.md +1 -0
  97. package/skills/provider-railway/adapters/local.md +1 -0
  98. package/skills/provider-railway/adapters/opencode.md +1 -0
  99. package/skills/provider-railway/references/verification-commands.md +39 -0
  100. package/skills/provider-railway/skill.json +71 -0
  101. package/skills/provider-ssh-manual/SKILL.md +97 -0
  102. package/skills/provider-ssh-manual/skill.json +54 -0
  103. package/skills/repo-coding-rules/SKILL.md +91 -0
  104. package/skills/repo-coding-rules/skill.json +34 -0
  105. package/skills/role-cont-eval/SKILL.md +90 -0
  106. package/skills/role-cont-eval/adapters/codex.md +1 -0
  107. package/skills/role-cont-eval/skill.json +36 -0
  108. package/skills/role-cont-qa/SKILL.md +93 -0
  109. package/skills/role-cont-qa/adapters/claude.md +1 -0
  110. package/skills/role-cont-qa/skill.json +36 -0
  111. package/skills/role-deploy/SKILL.md +96 -0
  112. package/skills/role-deploy/skill.json +36 -0
  113. package/skills/role-documentation/SKILL.md +72 -0
  114. package/skills/role-documentation/skill.json +36 -0
  115. package/skills/role-implementation/SKILL.md +68 -0
  116. package/skills/role-implementation/skill.json +36 -0
  117. package/skills/role-infra/SKILL.md +80 -0
  118. package/skills/role-infra/skill.json +36 -0
  119. package/skills/role-integration/SKILL.md +84 -0
  120. package/skills/role-integration/skill.json +36 -0
  121. package/skills/role-research/SKILL.md +64 -0
  122. package/skills/role-research/skill.json +36 -0
  123. package/skills/role-security/SKILL.md +60 -0
  124. package/skills/role-security/skill.json +36 -0
  125. package/skills/runtime-claude/SKILL.md +65 -0
  126. package/skills/runtime-claude/skill.json +36 -0
  127. package/skills/runtime-codex/SKILL.md +57 -0
  128. package/skills/runtime-codex/skill.json +36 -0
  129. package/skills/runtime-local/SKILL.md +44 -0
  130. package/skills/runtime-local/skill.json +36 -0
  131. package/skills/runtime-opencode/SKILL.md +57 -0
  132. package/skills/runtime-opencode/skill.json +36 -0
  133. package/skills/wave-core/SKILL.md +114 -0
  134. package/skills/wave-core/references/marker-syntax.md +62 -0
  135. package/skills/wave-core/skill.json +35 -0
  136. package/wave.config.json +61 -5
@@ -0,0 +1,190 @@
1
+ import path from "node:path";
2
+ import { loadWaveConfig } from "./config.mjs";
3
+ import { REPO_ROOT, ensureDirectory, readJsonOrNull, writeJsonAtomic } from "./shared.mjs";
4
+ import { normalizeTerminalSurface } from "./terminals.mjs";
5
+
6
+ export const PROJECT_PROFILE_SCHEMA_VERSION = 1;
7
+ export const PROJECT_PROFILE_PATH = path.join(REPO_ROOT, ".wave", "project-profile.json");
8
+ export const PROJECT_OVERSIGHT_MODES = ["oversight", "dark-factory"];
9
+ export const PROJECT_PROFILE_TERMINAL_SURFACES = ["vscode", "tmux"];
10
+ export const DEPLOY_ENVIRONMENT_KINDS = [
11
+ "railway-mcp",
12
+ "railway-cli",
13
+ "docker-compose",
14
+ "kubernetes",
15
+ "ssh-manual",
16
+ "custom",
17
+ ];
18
+ export const DRAFT_TEMPLATES = ["implementation", "qa", "infra", "release"];
19
+
20
+ function cleanText(value) {
21
+ return String(value ?? "").trim();
22
+ }
23
+
24
+ function normalizePathForProfile(value) {
25
+ const normalized = cleanText(value).replaceAll("\\", "/");
26
+ if (!normalized) {
27
+ return null;
28
+ }
29
+ return path.isAbsolute(normalized) ? path.relative(REPO_ROOT, normalized) : normalized;
30
+ }
31
+
32
+ function normalizeProjectTerminalSurface(value, label = "defaultTerminalSurface") {
33
+ const normalized = normalizeTerminalSurface(value, label);
34
+ return normalized === "none" ? "vscode" : normalized;
35
+ }
36
+
37
+ export function normalizeOversightMode(value, label = "oversight mode") {
38
+ const normalized = cleanText(value).toLowerCase();
39
+ if (!PROJECT_OVERSIGHT_MODES.includes(normalized)) {
40
+ throw new Error(`${label} must be one of: ${PROJECT_OVERSIGHT_MODES.join(", ")}`);
41
+ }
42
+ return normalized;
43
+ }
44
+
45
+ export function normalizeDraftTemplate(value, label = "draft template") {
46
+ const normalized = cleanText(value).toLowerCase();
47
+ if (!DRAFT_TEMPLATES.includes(normalized)) {
48
+ throw new Error(`${label} must be one of: ${DRAFT_TEMPLATES.join(", ")}`);
49
+ }
50
+ return normalized;
51
+ }
52
+
53
+ function normalizeDeployEnvironment(rawEnvironment, index) {
54
+ if (!rawEnvironment || typeof rawEnvironment !== "object" || Array.isArray(rawEnvironment)) {
55
+ throw new Error(`deployEnvironments[${index}] must be an object`);
56
+ }
57
+ const id = cleanText(rawEnvironment.id).toLowerCase();
58
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(id)) {
59
+ throw new Error(`deployEnvironments[${index}].id must be a lowercase repo-safe identifier`);
60
+ }
61
+ const kind = cleanText(rawEnvironment.kind).toLowerCase();
62
+ if (!DEPLOY_ENVIRONMENT_KINDS.includes(kind)) {
63
+ throw new Error(
64
+ `deployEnvironments[${index}].kind must be one of: ${DEPLOY_ENVIRONMENT_KINDS.join(", ")}`,
65
+ );
66
+ }
67
+ return {
68
+ id,
69
+ name: cleanText(rawEnvironment.name) || id,
70
+ kind,
71
+ isDefault: rawEnvironment.isDefault === true,
72
+ notes: cleanText(rawEnvironment.notes) || null,
73
+ };
74
+ }
75
+
76
+ export function buildDefaultProjectProfile(config = loadWaveConfig()) {
77
+ const now = new Date().toISOString();
78
+ return {
79
+ schemaVersion: PROJECT_PROFILE_SCHEMA_VERSION,
80
+ initializedAt: now,
81
+ updatedAt: now,
82
+ newProject: false,
83
+ defaultOversightMode: "oversight",
84
+ defaultTerminalSurface: "vscode",
85
+ deployEnvironments: [],
86
+ plannerDefaults: {
87
+ template: "implementation",
88
+ lane: cleanText(config.defaultLane) || "main",
89
+ },
90
+ source: {
91
+ projectName: cleanText(config.projectName) || "Wave Orchestrator",
92
+ configPath: normalizePathForProfile(config.configPath || "wave.config.json"),
93
+ },
94
+ };
95
+ }
96
+
97
+ export function normalizeProjectProfile(rawProfile, options = {}) {
98
+ if (!rawProfile || typeof rawProfile !== "object" || Array.isArray(rawProfile)) {
99
+ throw new Error(`Project profile is invalid: ${path.relative(REPO_ROOT, PROJECT_PROFILE_PATH)}`);
100
+ }
101
+ const config = options.config || loadWaveConfig();
102
+ const base = buildDefaultProjectProfile(config);
103
+ const deployEnvironments = Array.isArray(rawProfile.deployEnvironments)
104
+ ? rawProfile.deployEnvironments.map((entry, index) => normalizeDeployEnvironment(entry, index))
105
+ : [];
106
+ const defaultEnvironmentIndex = deployEnvironments.findIndex((entry) => entry.isDefault);
107
+ if (defaultEnvironmentIndex === -1 && deployEnvironments.length > 0) {
108
+ deployEnvironments[0].isDefault = true;
109
+ } else if (defaultEnvironmentIndex > -1) {
110
+ deployEnvironments.forEach((entry, index) => {
111
+ entry.isDefault = index === defaultEnvironmentIndex;
112
+ });
113
+ }
114
+ const plannerDefaults =
115
+ rawProfile.plannerDefaults && typeof rawProfile.plannerDefaults === "object"
116
+ ? rawProfile.plannerDefaults
117
+ : {};
118
+ return {
119
+ schemaVersion: PROJECT_PROFILE_SCHEMA_VERSION,
120
+ initializedAt: cleanText(rawProfile.initializedAt) || base.initializedAt,
121
+ updatedAt: cleanText(rawProfile.updatedAt) || base.updatedAt,
122
+ newProject: rawProfile.newProject === true,
123
+ defaultOversightMode: normalizeOversightMode(
124
+ rawProfile.defaultOversightMode || base.defaultOversightMode,
125
+ "defaultOversightMode",
126
+ ),
127
+ defaultTerminalSurface: normalizeProjectTerminalSurface(
128
+ rawProfile.defaultTerminalSurface || base.defaultTerminalSurface,
129
+ "defaultTerminalSurface",
130
+ ),
131
+ deployEnvironments,
132
+ plannerDefaults: {
133
+ template: normalizeDraftTemplate(
134
+ plannerDefaults.template || base.plannerDefaults.template,
135
+ "plannerDefaults.template",
136
+ ),
137
+ lane: cleanText(plannerDefaults.lane) || base.plannerDefaults.lane,
138
+ },
139
+ source: {
140
+ projectName: cleanText(rawProfile.source?.projectName) || base.source.projectName,
141
+ configPath: normalizePathForProfile(rawProfile.source?.configPath) || base.source.configPath,
142
+ },
143
+ };
144
+ }
145
+
146
+ export function readProjectProfile(options = {}) {
147
+ const payload = readJsonOrNull(PROJECT_PROFILE_PATH);
148
+ if (!payload) {
149
+ return null;
150
+ }
151
+ return normalizeProjectProfile(payload, options);
152
+ }
153
+
154
+ export function writeProjectProfile(profile, options = {}) {
155
+ const config = options.config || loadWaveConfig();
156
+ const now = new Date().toISOString();
157
+ const normalized = normalizeProjectProfile(
158
+ {
159
+ ...profile,
160
+ initializedAt: profile?.initializedAt || now,
161
+ updatedAt: now,
162
+ source: {
163
+ projectName: profile?.source?.projectName || config.projectName,
164
+ configPath: normalizePathForProfile(config.configPath || "wave.config.json"),
165
+ },
166
+ },
167
+ { config },
168
+ );
169
+ ensureDirectory(path.dirname(PROJECT_PROFILE_PATH));
170
+ writeJsonAtomic(PROJECT_PROFILE_PATH, normalized);
171
+ return normalized;
172
+ }
173
+
174
+ export function updateProjectProfile(mutator, options = {}) {
175
+ const config = options.config || loadWaveConfig();
176
+ const current = readProjectProfile({ config }) || buildDefaultProjectProfile(config);
177
+ const next = typeof mutator === "function" ? mutator(current) : { ...current, ...(mutator || {}) };
178
+ return writeProjectProfile(
179
+ {
180
+ ...current,
181
+ ...(next || {}),
182
+ initializedAt: current.initializedAt,
183
+ },
184
+ { config },
185
+ );
186
+ }
187
+
188
+ export function resolveDefaultTerminalSurface(profile) {
189
+ return normalizeProjectTerminalSurface(profile?.defaultTerminalSurface || "vscode");
190
+ }
@@ -22,19 +22,22 @@ function buildReplayLanePaths(metadata) {
22
22
  : null;
23
23
  const roles = replayContext?.roles || metadata?.roles || {};
24
24
  const validation = replayContext?.validation || metadata?.validation || {};
25
- const evaluatorAgentId = roles.evaluatorAgentId || "A0";
25
+ const contQaAgentId = roles.contQaAgentId || roles.evaluatorAgentId || "A0";
26
+ const contEvalAgentId = roles.contEvalAgentId || "E0";
26
27
  const integrationAgentId = roles.integrationAgentId || "A8";
27
28
  const documentationAgentId = roles.documentationAgentId || "A9";
28
29
  return {
29
30
  lane: replayContext?.lane || metadata?.lane || "main",
30
- evaluatorAgentId,
31
+ contQaAgentId,
32
+ contEvalAgentId,
31
33
  integrationAgentId,
32
34
  documentationAgentId,
33
35
  requireIntegrationStewardFromWave:
34
36
  validation.requireIntegrationStewardFromWave ?? null,
35
37
  laneProfile: {
36
38
  roles: {
37
- evaluatorAgentId,
39
+ contQaAgentId,
40
+ contEvalAgentId,
38
41
  integrationAgentId,
39
42
  documentationAgentId,
40
43
  },
@@ -0,0 +1,84 @@
1
+ import {
2
+ DEFAULT_CONT_EVAL_AGENT_ID,
3
+ DEFAULT_SECURITY_ROLE_PROMPT_PATH,
4
+ } from "./config.mjs";
5
+
6
+ function cleanPath(value) {
7
+ return String(value || "")
8
+ .trim()
9
+ .replaceAll("\\", "/");
10
+ }
11
+
12
+ export function isContQaReportPath(relPath) {
13
+ return /(?:^|\/)(?:reviews?|.*cont[-_]?qa).*\.(?:md|txt)$/i.test(cleanPath(relPath));
14
+ }
15
+
16
+ export function isContEvalReportPath(relPath) {
17
+ return /(?:^|\/)(?:reviews?|.*cont[-_]?eval|.*eval).*\.(?:md|txt)$/i.test(cleanPath(relPath));
18
+ }
19
+
20
+ export function isSecurityRolePromptPath(
21
+ relPath,
22
+ securityRolePromptPath = DEFAULT_SECURITY_ROLE_PROMPT_PATH,
23
+ ) {
24
+ const normalized = cleanPath(relPath);
25
+ const configured = cleanPath(securityRolePromptPath);
26
+ return (
27
+ normalized === configured ||
28
+ normalized === DEFAULT_SECURITY_ROLE_PROMPT_PATH ||
29
+ normalized.endsWith("/wave-security-role.md")
30
+ );
31
+ }
32
+
33
+ export function isSecurityReportPath(relPath) {
34
+ return /(?:^|\/).*security.*\.(?:md|txt)$/i.test(cleanPath(relPath));
35
+ }
36
+
37
+ export function isContEvalImplementationOwningAgent(
38
+ agent,
39
+ { contEvalAgentId = DEFAULT_CONT_EVAL_AGENT_ID } = {},
40
+ ) {
41
+ if (!agent || agent.agentId !== contEvalAgentId) {
42
+ return false;
43
+ }
44
+ const ownedPaths = Array.isArray(agent.ownedPaths) ? agent.ownedPaths.map(cleanPath).filter(Boolean) : [];
45
+ if (ownedPaths.length === 0) {
46
+ return false;
47
+ }
48
+ return ownedPaths.some((ownedPath) => !isContEvalReportPath(ownedPath));
49
+ }
50
+
51
+ export function isContEvalReportOnlyAgent(
52
+ agent,
53
+ { contEvalAgentId = DEFAULT_CONT_EVAL_AGENT_ID } = {},
54
+ ) {
55
+ return agent?.agentId === contEvalAgentId && !isContEvalImplementationOwningAgent(agent, {
56
+ contEvalAgentId,
57
+ });
58
+ }
59
+
60
+ export function isSecurityReviewAgent(
61
+ agent,
62
+ { securityRolePromptPath = DEFAULT_SECURITY_ROLE_PROMPT_PATH } = {},
63
+ ) {
64
+ if (!agent || typeof agent !== "object") {
65
+ return false;
66
+ }
67
+ const rolePromptPaths = Array.isArray(agent.rolePromptPaths) ? agent.rolePromptPaths : [];
68
+ if (
69
+ rolePromptPaths.some((rolePromptPath) =>
70
+ isSecurityRolePromptPath(rolePromptPath, securityRolePromptPath),
71
+ )
72
+ ) {
73
+ return true;
74
+ }
75
+ const capabilities = Array.isArray(agent.capabilities)
76
+ ? agent.capabilities.map((entry) => String(entry || "").trim().toLowerCase())
77
+ : [];
78
+ return capabilities.includes("security-review");
79
+ }
80
+
81
+ export function resolveSecurityReviewReportPath(agent) {
82
+ const ownedPaths = Array.isArray(agent?.ownedPaths) ? agent.ownedPaths.map(cleanPath).filter(Boolean) : [];
83
+ return ownedPaths.find((ownedPath) => isSecurityReportPath(ownedPath)) || null;
84
+ }
@@ -77,22 +77,71 @@ export function sanitizeOrchestratorId(value) {
77
77
  return id.slice(0, 64);
78
78
  }
79
79
 
80
+ export function sanitizeAdhocRunId(value) {
81
+ const id = String(value || "")
82
+ .trim()
83
+ .toLowerCase()
84
+ .replace(/[^a-z0-9._-]+/g, "-")
85
+ .replace(/-+/g, "-")
86
+ .replace(/^-+|-+$/g, "");
87
+ if (!id) {
88
+ throw new Error("Ad-hoc run ID is required");
89
+ }
90
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(id)) {
91
+ throw new Error(`Invalid ad-hoc run ID: ${value}`);
92
+ }
93
+ return id;
94
+ }
95
+
96
+ export function buildWorkspaceTmuxToken(workspaceRoot = REPO_ROOT) {
97
+ const repoBase =
98
+ path
99
+ .basename(path.resolve(String(workspaceRoot || REPO_ROOT)))
100
+ .toLowerCase()
101
+ .replace(/[^a-z0-9]+/g, "_")
102
+ .replace(/^_+|_+$/g, "")
103
+ .slice(0, 12) || "repo";
104
+ const repoHash = crypto
105
+ .createHash("sha1")
106
+ .update(path.resolve(String(workspaceRoot || REPO_ROOT)))
107
+ .digest("hex")
108
+ .slice(0, 8);
109
+ return `${repoBase}_${repoHash}`;
110
+ }
111
+
80
112
  export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
81
113
  const config = options.config || loadWaveConfig();
82
- const laneProfile = resolveLaneProfile(config, laneInput || config.defaultLane);
114
+ const baseLaneProfile = resolveLaneProfile(config, laneInput || config.defaultLane);
115
+ const adhocRunId = options.adhocRunId ? sanitizeAdhocRunId(options.adhocRunId) : null;
116
+ const laneProfile = adhocRunId
117
+ ? {
118
+ ...baseLaneProfile,
119
+ validation: {
120
+ ...baseLaneProfile.validation,
121
+ requireComponentPromotionsFromWave: null,
122
+ requireAgentComponentsFromWave: null,
123
+ },
124
+ }
125
+ : baseLaneProfile;
83
126
  const lane = laneProfile.lane;
84
127
  const laneTmux = lane.replace(/-/g, "_");
128
+ const runKind = adhocRunId ? "adhoc" : "roadmap";
85
129
  const runVariant = String(options.runVariant || "")
86
130
  .trim()
87
131
  .toLowerCase();
88
132
  if (runVariant && runVariant !== "dry-run") {
89
133
  throw new Error(`Unsupported lane path variant: ${options.runVariant}`);
90
134
  }
135
+ const workspaceTmuxToken = buildWorkspaceTmuxToken(REPO_ROOT);
91
136
  const docsDir = path.join(REPO_ROOT, laneProfile.docsDir);
92
137
  const plansDir = path.join(REPO_ROOT, laneProfile.plansDir);
93
138
  const preferredWavesDir = path.join(REPO_ROOT, laneProfile.wavesDir);
94
139
  const legacyWavesDir = path.join(docsDir, "waves");
95
- const baseStateDir = path.join(REPO_ROOT, laneProfile.paths.stateRoot, `${lane}-wave-launcher`);
140
+ const adhocRootDir = path.join(REPO_ROOT, ".wave", "adhoc");
141
+ const adhocRunDir = adhocRunId ? path.join(adhocRootDir, "runs", adhocRunId) : null;
142
+ const baseStateDir = adhocRunId
143
+ ? path.join(REPO_ROOT, laneProfile.paths.stateRoot, `${lane}-wave-launcher`, "adhoc", adhocRunId)
144
+ : path.join(REPO_ROOT, laneProfile.paths.stateRoot, `${lane}-wave-launcher`);
96
145
  const stateDir = runVariant === "dry-run" ? path.join(baseStateDir, "dry-run") : baseStateDir;
97
146
  const orchestratorStateDir =
98
147
  runVariant === "dry-run"
@@ -103,14 +152,24 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
103
152
  config,
104
153
  laneProfile,
105
154
  lane,
155
+ runKind,
156
+ runId: adhocRunId,
106
157
  runVariant,
107
158
  docsDir,
108
159
  plansDir,
109
160
  wavesDir:
110
- fs.existsSync(preferredWavesDir) || !fs.existsSync(legacyWavesDir)
161
+ adhocRunDir ||
162
+ (fs.existsSync(preferredWavesDir) || !fs.existsSync(legacyWavesDir)
111
163
  ? preferredWavesDir
112
- : legacyWavesDir,
164
+ : legacyWavesDir),
113
165
  legacyWavesDir,
166
+ adhocRootDir,
167
+ adhocRunDir,
168
+ adhocIndexPath: path.join(adhocRootDir, "index.json"),
169
+ adhocRequestPath: adhocRunDir ? path.join(adhocRunDir, "request.json") : null,
170
+ adhocSpecPath: adhocRunDir ? path.join(adhocRunDir, "spec.json") : null,
171
+ adhocWavePath: adhocRunDir ? path.join(adhocRunDir, "wave-0.md") : null,
172
+ adhocResultPath: adhocRunDir ? path.join(adhocRunDir, "result.json") : null,
114
173
  promptsDir: path.join(stateDir, "prompts"),
115
174
  logsDir: path.join(stateDir, "logs"),
116
175
  statusDir: path.join(stateDir, "status"),
@@ -121,6 +180,7 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
121
180
  inboxesDir: path.join(stateDir, "inboxes"),
122
181
  ledgerDir: path.join(stateDir, "ledger"),
123
182
  integrationDir: path.join(stateDir, "integration"),
183
+ securityDir: path.join(stateDir, "security"),
124
184
  dependencySnapshotsDir: path.join(stateDir, "dependencies"),
125
185
  docsQueueDir: path.join(stateDir, "docs-queue"),
126
186
  tracesDir: path.join(stateDir, "traces"),
@@ -128,7 +188,9 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
128
188
  executorOverlaysDir: path.join(stateDir, "executors"),
129
189
  stateDir,
130
190
  terminalsPath: path.join(REPO_ROOT, laneProfile.paths.terminalsPath),
191
+ skillsDir: path.join(REPO_ROOT, laneProfile.skills?.dir || "skills"),
131
192
  context7BundleIndexPath: path.join(REPO_ROOT, laneProfile.paths.context7BundleIndexPath),
193
+ benchmarkCatalogPath: path.join(REPO_ROOT, laneProfile.paths.benchmarkCatalogPath),
132
194
  componentCutoverMatrixDocPath: path.join(
133
195
  REPO_ROOT,
134
196
  laneProfile.paths.componentCutoverMatrixDocPath,
@@ -137,15 +199,18 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
137
199
  REPO_ROOT,
138
200
  laneProfile.paths.componentCutoverMatrixJsonPath,
139
201
  ),
140
- sharedPlanDocs: laneProfile.sharedPlanDocs,
202
+ sharedPlanDocs: laneProfile.sharedPlanDocs || [],
141
203
  requiredPromptReferences: laneProfile.validation.requiredPromptReferences,
142
204
  rolePromptDir: laneProfile.roles.rolePromptDir,
143
- evaluatorAgentId: laneProfile.roles.evaluatorAgentId,
205
+ contQaAgentId: laneProfile.roles.contQaAgentId,
206
+ contEvalAgentId: laneProfile.roles.contEvalAgentId,
144
207
  integrationAgentId: laneProfile.roles.integrationAgentId,
145
208
  documentationAgentId: laneProfile.roles.documentationAgentId,
146
- evaluatorRolePromptPath: laneProfile.roles.evaluatorRolePromptPath,
209
+ contQaRolePromptPath: laneProfile.roles.contQaRolePromptPath,
210
+ contEvalRolePromptPath: laneProfile.roles.contEvalRolePromptPath,
147
211
  integrationRolePromptPath: laneProfile.roles.integrationRolePromptPath,
148
212
  documentationRolePromptPath: laneProfile.roles.documentationRolePromptPath,
213
+ securityRolePromptPath: laneProfile.roles.securityRolePromptPath,
149
214
  requireDocumentationStewardFromWave:
150
215
  laneProfile.validation.requireDocumentationStewardFromWave,
151
216
  requireContext7DeclarationsFromWave:
@@ -157,6 +222,7 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
157
222
  laneProfile.validation.requireComponentPromotionsFromWave,
158
223
  requireAgentComponentsFromWave: laneProfile.validation.requireAgentComponentsFromWave,
159
224
  executors: laneProfile.executors,
225
+ skills: laneProfile.skills,
160
226
  capabilityRouting: laneProfile.capabilityRouting,
161
227
  defaultManifestPath: path.join(stateDir, "waves.manifest.json"),
162
228
  defaultRunStatePath: path.join(stateDir, "run-state.json"),
@@ -165,10 +231,10 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
165
231
  terminalNamePrefix: `${lane}-wave`,
166
232
  dashboardTerminalNamePrefix: `${lane}-wave-dashboard`,
167
233
  globalDashboardTerminalName: `${lane}-wave-dashboard-global`,
168
- tmuxSessionPrefix: `oc_${laneTmux}_wave`,
169
- tmuxDashboardSessionPrefix: `oc_${laneTmux}_wave_dashboard`,
170
- tmuxGlobalDashboardSessionPrefix: `oc_${laneTmux}_wave_dashboard_global`,
171
- tmuxSocketName: `oc_${laneTmux}_waves`,
234
+ tmuxSessionPrefix: `oc_${laneTmux}_${workspaceTmuxToken}_wave`,
235
+ tmuxDashboardSessionPrefix: `oc_${laneTmux}_${workspaceTmuxToken}_wave_dashboard`,
236
+ tmuxGlobalDashboardSessionPrefix: `oc_${laneTmux}_${workspaceTmuxToken}_wave_dashboard_global`,
237
+ tmuxSocketName: `oc_${laneTmux}_${workspaceTmuxToken}_waves`,
172
238
  orchestratorStateDir,
173
239
  defaultOrchestratorBoardPath: path.join(
174
240
  orchestratorStateDir,