@chllming/wave-orchestration 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +549 -0
- package/docs/agents/wave-deploy-verifier-role.md +34 -0
- package/docs/agents/wave-documentation-role.md +30 -0
- package/docs/agents/wave-evaluator-role.md +43 -0
- package/docs/agents/wave-infra-role.md +34 -0
- package/docs/agents/wave-integration-role.md +32 -0
- package/docs/agents/wave-launcher-role.md +37 -0
- package/docs/context7/bundles.json +91 -0
- package/docs/plans/component-cutover-matrix.json +112 -0
- package/docs/plans/component-cutover-matrix.md +49 -0
- package/docs/plans/context7-wave-orchestrator.md +130 -0
- package/docs/plans/current-state.md +44 -0
- package/docs/plans/master-plan.md +16 -0
- package/docs/plans/migration.md +23 -0
- package/docs/plans/wave-orchestrator.md +254 -0
- package/docs/plans/waves/wave-0.md +165 -0
- package/docs/reference/github-packages-setup.md +52 -0
- package/docs/reference/migration-0.2-to-0.5.md +622 -0
- package/docs/reference/npmjs-trusted-publishing.md +55 -0
- package/docs/reference/repository-guidance.md +18 -0
- package/docs/reference/runtime-config/README.md +85 -0
- package/docs/reference/runtime-config/claude.md +105 -0
- package/docs/reference/runtime-config/codex.md +81 -0
- package/docs/reference/runtime-config/opencode.md +93 -0
- package/docs/research/agent-context-sources.md +57 -0
- package/docs/roadmap.md +626 -0
- package/package.json +53 -0
- package/releases/manifest.json +101 -0
- package/scripts/context7-api-check.sh +21 -0
- package/scripts/context7-export-env.sh +52 -0
- package/scripts/research/agent-context-archive.mjs +472 -0
- package/scripts/research/generate-agent-context-indexes.mjs +85 -0
- package/scripts/research/import-agent-context-archive.mjs +793 -0
- package/scripts/research/manifests/harness-and-blackboard-2026-03-21.mjs +201 -0
- package/scripts/wave-autonomous.mjs +13 -0
- package/scripts/wave-cli-bootstrap.mjs +27 -0
- package/scripts/wave-dashboard.mjs +11 -0
- package/scripts/wave-human-feedback.mjs +11 -0
- package/scripts/wave-launcher.mjs +11 -0
- package/scripts/wave-local-executor.mjs +13 -0
- package/scripts/wave-orchestrator/agent-state.mjs +416 -0
- package/scripts/wave-orchestrator/autonomous.mjs +367 -0
- package/scripts/wave-orchestrator/clarification-triage.mjs +605 -0
- package/scripts/wave-orchestrator/config.mjs +848 -0
- package/scripts/wave-orchestrator/context7.mjs +464 -0
- package/scripts/wave-orchestrator/coord-cli.mjs +286 -0
- package/scripts/wave-orchestrator/coordination-store.mjs +987 -0
- package/scripts/wave-orchestrator/coordination.mjs +768 -0
- package/scripts/wave-orchestrator/dashboard-renderer.mjs +254 -0
- package/scripts/wave-orchestrator/dashboard-state.mjs +473 -0
- package/scripts/wave-orchestrator/dep-cli.mjs +219 -0
- package/scripts/wave-orchestrator/docs-queue.mjs +75 -0
- package/scripts/wave-orchestrator/executors.mjs +385 -0
- package/scripts/wave-orchestrator/feedback.mjs +372 -0
- package/scripts/wave-orchestrator/install.mjs +540 -0
- package/scripts/wave-orchestrator/launcher.mjs +3879 -0
- package/scripts/wave-orchestrator/ledger.mjs +332 -0
- package/scripts/wave-orchestrator/local-executor.mjs +263 -0
- package/scripts/wave-orchestrator/replay.mjs +246 -0
- package/scripts/wave-orchestrator/roots.mjs +10 -0
- package/scripts/wave-orchestrator/routing-state.mjs +542 -0
- package/scripts/wave-orchestrator/shared.mjs +405 -0
- package/scripts/wave-orchestrator/terminals.mjs +209 -0
- package/scripts/wave-orchestrator/traces.mjs +1094 -0
- package/scripts/wave-orchestrator/wave-files.mjs +1923 -0
- package/scripts/wave.mjs +103 -0
- package/wave.config.json +115 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
applyContext7SelectionsToWave,
|
|
5
|
+
loadContext7BundleIndex,
|
|
6
|
+
} from "./context7.mjs";
|
|
7
|
+
import { buildLanePaths, ensureDirectory, PACKAGE_ROOT, readJsonOrNull, REPO_ROOT, writeJsonAtomic } from "./shared.mjs";
|
|
8
|
+
import { loadWaveConfig } from "./config.mjs";
|
|
9
|
+
import { applyExecutorSelectionsToWave, parseWaveFiles, validateWaveDefinition } from "./wave-files.mjs";
|
|
10
|
+
|
|
11
|
+
export const INSTALL_STATE_SCHEMA_VERSION = 1;
|
|
12
|
+
export const INSTALL_STATE_DIR = ".wave";
|
|
13
|
+
export const INSTALL_STATE_PATH = path.join(REPO_ROOT, INSTALL_STATE_DIR, "install-state.json");
|
|
14
|
+
export const UPGRADE_HISTORY_DIR = path.join(REPO_ROOT, INSTALL_STATE_DIR, "upgrade-history");
|
|
15
|
+
export const CHANGELOG_MANIFEST_PATH = path.join(PACKAGE_ROOT, "releases", "manifest.json");
|
|
16
|
+
export const PACKAGE_METADATA_PATH = path.join(PACKAGE_ROOT, "package.json");
|
|
17
|
+
export const STARTER_TEMPLATE_PATHS = [
|
|
18
|
+
"wave.config.json",
|
|
19
|
+
"docs/agents/wave-documentation-role.md",
|
|
20
|
+
"docs/agents/wave-evaluator-role.md",
|
|
21
|
+
"docs/agents/wave-integration-role.md",
|
|
22
|
+
"docs/context7/bundles.json",
|
|
23
|
+
"docs/plans/component-cutover-matrix.json",
|
|
24
|
+
"docs/plans/component-cutover-matrix.md",
|
|
25
|
+
"docs/plans/context7-wave-orchestrator.md",
|
|
26
|
+
"docs/plans/current-state.md",
|
|
27
|
+
"docs/plans/master-plan.md",
|
|
28
|
+
"docs/plans/migration.md",
|
|
29
|
+
"docs/plans/wave-orchestrator.md",
|
|
30
|
+
"docs/plans/waves/wave-0.md",
|
|
31
|
+
"docs/reference/repository-guidance.md",
|
|
32
|
+
"docs/reference/runtime-config/README.md",
|
|
33
|
+
"docs/reference/runtime-config/codex.md",
|
|
34
|
+
"docs/reference/runtime-config/claude.md",
|
|
35
|
+
"docs/reference/runtime-config/opencode.md",
|
|
36
|
+
"docs/research/agent-context-sources.md",
|
|
37
|
+
];
|
|
38
|
+
const REQUIRED_GITIGNORE_ENTRIES = [
|
|
39
|
+
".tmp/",
|
|
40
|
+
"docs/research/cache/",
|
|
41
|
+
"docs/research/agent-context-cache/",
|
|
42
|
+
"docs/research/papers/",
|
|
43
|
+
"docs/research/articles/",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function packageMetadata() {
|
|
47
|
+
const payload = readJsonOrNull(PACKAGE_METADATA_PATH);
|
|
48
|
+
if (!payload?.name || !payload?.version) {
|
|
49
|
+
throw new Error(`Invalid package metadata: ${PACKAGE_METADATA_PATH}`);
|
|
50
|
+
}
|
|
51
|
+
return payload;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readInstallState() {
|
|
55
|
+
const payload = readJsonOrNull(INSTALL_STATE_PATH);
|
|
56
|
+
return payload && typeof payload === "object" ? payload : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function writeInstallState(state) {
|
|
60
|
+
ensureDirectory(path.dirname(INSTALL_STATE_PATH));
|
|
61
|
+
writeJsonAtomic(INSTALL_STATE_PATH, state);
|
|
62
|
+
return INSTALL_STATE_PATH;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ensureWorkspaceSubdir(relPath) {
|
|
66
|
+
const target = path.join(REPO_ROOT, relPath);
|
|
67
|
+
ensureDirectory(target);
|
|
68
|
+
return target;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function templateStatusList() {
|
|
72
|
+
return STARTER_TEMPLATE_PATHS.map((relPath) => ({
|
|
73
|
+
path: relPath,
|
|
74
|
+
sourcePath: path.join(PACKAGE_ROOT, relPath),
|
|
75
|
+
targetPath: path.join(REPO_ROOT, relPath),
|
|
76
|
+
exists: fs.existsSync(path.join(REPO_ROOT, relPath)),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function existingBootstrapMarkers() {
|
|
81
|
+
return templateStatusList()
|
|
82
|
+
.filter((entry) => entry.exists)
|
|
83
|
+
.map((entry) => entry.path);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function copyTemplateFile(relPath) {
|
|
87
|
+
const sourcePath = path.join(PACKAGE_ROOT, relPath);
|
|
88
|
+
const targetPath = path.join(REPO_ROOT, relPath);
|
|
89
|
+
if (!fs.existsSync(sourcePath)) {
|
|
90
|
+
throw new Error(`Missing packaged template: ${relPath}`);
|
|
91
|
+
}
|
|
92
|
+
ensureDirectory(path.dirname(targetPath));
|
|
93
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
94
|
+
return targetPath;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function nextHistoryRecord(existingState, entry) {
|
|
98
|
+
const history = Array.isArray(existingState?.history) ? existingState.history.slice(0, 50) : [];
|
|
99
|
+
history.push(entry);
|
|
100
|
+
return history;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeVersionParts(version) {
|
|
104
|
+
return String(version || "")
|
|
105
|
+
.split(".")
|
|
106
|
+
.map((part) => Number.parseInt(part.replace(/[^0-9].*$/, ""), 10) || 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function compareVersions(a, b) {
|
|
110
|
+
const left = normalizeVersionParts(a);
|
|
111
|
+
const right = normalizeVersionParts(b);
|
|
112
|
+
const length = Math.max(left.length, right.length);
|
|
113
|
+
for (let index = 0; index < length; index += 1) {
|
|
114
|
+
const diff = (left[index] || 0) - (right[index] || 0);
|
|
115
|
+
if (diff !== 0) {
|
|
116
|
+
return diff;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readChangelogManifest() {
|
|
123
|
+
const payload = readJsonOrNull(CHANGELOG_MANIFEST_PATH);
|
|
124
|
+
if (!payload?.releases || !Array.isArray(payload.releases)) {
|
|
125
|
+
throw new Error(`Invalid changelog manifest: ${CHANGELOG_MANIFEST_PATH}`);
|
|
126
|
+
}
|
|
127
|
+
return payload;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function releasesBetween(manifest, fromVersion, toVersion) {
|
|
131
|
+
return manifest.releases.filter((release) => {
|
|
132
|
+
if (!release?.version) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const afterStart = !fromVersion || compareVersions(release.version, fromVersion) > 0;
|
|
136
|
+
const beforeEnd = !toVersion || compareVersions(release.version, toVersion) <= 0;
|
|
137
|
+
return afterStart && beforeEnd;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function renderReleaseNotes(releases) {
|
|
142
|
+
if (releases.length === 0) {
|
|
143
|
+
return ["- No packaged release notes in the selected range."];
|
|
144
|
+
}
|
|
145
|
+
return releases.flatMap((release) => [
|
|
146
|
+
`### ${release.version} (${release.date || "undated"})`,
|
|
147
|
+
release.summary ? `- Summary: ${release.summary}` : null,
|
|
148
|
+
...(Array.isArray(release.features) && release.features.length > 0
|
|
149
|
+
? release.features.map((feature) => `- ${feature}`)
|
|
150
|
+
: ["- No feature bullets recorded."]),
|
|
151
|
+
...(Array.isArray(release.manualSteps) && release.manualSteps.length > 0
|
|
152
|
+
? release.manualSteps.map((step) => `- Manual step: ${step}`)
|
|
153
|
+
: []),
|
|
154
|
+
"",
|
|
155
|
+
]).filter(Boolean);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function slugifyVersion(value) {
|
|
159
|
+
return String(value || "unknown").replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function formatUpgradeReport(report) {
|
|
163
|
+
return [
|
|
164
|
+
`# Wave Upgrade Report`,
|
|
165
|
+
"",
|
|
166
|
+
`- Workspace root: \`${REPO_ROOT}\``,
|
|
167
|
+
`- Package: \`${report.packageName}\``,
|
|
168
|
+
`- Previous version: \`${report.previousVersion || "unknown"}\``,
|
|
169
|
+
`- Current version: \`${report.currentVersion}\``,
|
|
170
|
+
`- Generated: ${report.generatedAt}`,
|
|
171
|
+
"",
|
|
172
|
+
"## Release Notes",
|
|
173
|
+
"",
|
|
174
|
+
...renderReleaseNotes(report.releases),
|
|
175
|
+
"## Workspace Impact",
|
|
176
|
+
"",
|
|
177
|
+
"- No repo-owned plans, waves, role prompts, or config files were overwritten.",
|
|
178
|
+
"- New runtime behavior comes from the installed package version.",
|
|
179
|
+
...(report.doctor.errors.length > 0 || report.doctor.warnings.length > 0
|
|
180
|
+
? [
|
|
181
|
+
"",
|
|
182
|
+
"## Follow-Up",
|
|
183
|
+
"",
|
|
184
|
+
...report.doctor.errors.map((issue) => `- Error: ${issue}`),
|
|
185
|
+
...report.doctor.warnings.map((issue) => `- Warning: ${issue}`),
|
|
186
|
+
]
|
|
187
|
+
: []),
|
|
188
|
+
"",
|
|
189
|
+
].join("\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function gitignoreWarnings() {
|
|
193
|
+
const gitignorePath = path.join(REPO_ROOT, ".gitignore");
|
|
194
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
195
|
+
return ['Missing `.gitignore`; add Wave runtime ignores before running real waves.'];
|
|
196
|
+
}
|
|
197
|
+
const content = fs.readFileSync(gitignorePath, "utf8");
|
|
198
|
+
return REQUIRED_GITIGNORE_ENTRIES.filter((entry) => !content.includes(entry)).map(
|
|
199
|
+
(entry) => `Missing recommended .gitignore entry: ${entry}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function runDoctor() {
|
|
204
|
+
const errors = [];
|
|
205
|
+
const warnings = [];
|
|
206
|
+
const installState = readInstallState();
|
|
207
|
+
const metadata = packageMetadata();
|
|
208
|
+
|
|
209
|
+
if (!installState) {
|
|
210
|
+
errors.push("Workspace is not initialized. Run `pnpm exec wave init` or `pnpm exec wave init --adopt-existing`.");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let config = null;
|
|
214
|
+
try {
|
|
215
|
+
config = loadWaveConfig();
|
|
216
|
+
} catch (error) {
|
|
217
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (config) {
|
|
221
|
+
try {
|
|
222
|
+
const lanePaths = buildLanePaths(config.defaultLane, { config });
|
|
223
|
+
if (!fs.existsSync(path.join(REPO_ROOT, "wave.config.json"))) {
|
|
224
|
+
errors.push("Missing wave.config.json.");
|
|
225
|
+
}
|
|
226
|
+
const missingSharedPlanDocs = lanePaths.sharedPlanDocs.filter(
|
|
227
|
+
(docPath) => !fs.existsSync(path.join(REPO_ROOT, docPath)),
|
|
228
|
+
);
|
|
229
|
+
for (const docPath of missingSharedPlanDocs) {
|
|
230
|
+
errors.push(`Missing shared plan doc: ${docPath}`);
|
|
231
|
+
}
|
|
232
|
+
for (const requiredPath of [
|
|
233
|
+
lanePaths.evaluatorRolePromptPath,
|
|
234
|
+
lanePaths.integrationRolePromptPath,
|
|
235
|
+
lanePaths.documentationRolePromptPath,
|
|
236
|
+
lanePaths.context7BundleIndexPath.replace(`${REPO_ROOT}${path.sep}`, ""),
|
|
237
|
+
]) {
|
|
238
|
+
const relPath = path.isAbsolute(requiredPath)
|
|
239
|
+
? path.relative(REPO_ROOT, requiredPath)
|
|
240
|
+
: requiredPath;
|
|
241
|
+
if (!fs.existsSync(path.join(REPO_ROOT, relPath))) {
|
|
242
|
+
errors.push(`Missing required Wave file: ${relPath}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (fs.existsSync(lanePaths.wavesDir)) {
|
|
246
|
+
const context7BundleIndex = loadContext7BundleIndex(lanePaths.context7BundleIndexPath);
|
|
247
|
+
parseWaveFiles(lanePaths.wavesDir, { laneProfile: lanePaths.laneProfile })
|
|
248
|
+
.map((wave) =>
|
|
249
|
+
applyExecutorSelectionsToWave(wave, {
|
|
250
|
+
laneProfile: lanePaths.laneProfile,
|
|
251
|
+
executorMode: lanePaths.executors.default,
|
|
252
|
+
codexSandboxMode: lanePaths.executors.codex.sandbox,
|
|
253
|
+
}),
|
|
254
|
+
)
|
|
255
|
+
.map((wave) =>
|
|
256
|
+
applyContext7SelectionsToWave(wave, {
|
|
257
|
+
lane: lanePaths.lane,
|
|
258
|
+
bundleIndex: context7BundleIndex,
|
|
259
|
+
}),
|
|
260
|
+
)
|
|
261
|
+
.forEach((wave) => validateWaveDefinition(wave, { laneProfile: lanePaths.laneProfile }));
|
|
262
|
+
} else {
|
|
263
|
+
warnings.push(`No waves directory found at ${path.relative(REPO_ROOT, lanePaths.wavesDir)}.`);
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
warnings.push(...gitignoreWarnings());
|
|
271
|
+
if (installState?.installedVersion && compareVersions(metadata.version, installState.installedVersion) !== 0) {
|
|
272
|
+
warnings.push(
|
|
273
|
+
`Installed package version is ${metadata.version} but install-state records ${installState.installedVersion}; run \`pnpm exec wave upgrade\`.`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
ok: errors.length === 0,
|
|
279
|
+
workspaceRoot: REPO_ROOT,
|
|
280
|
+
packageName: metadata.name,
|
|
281
|
+
packageVersion: metadata.version,
|
|
282
|
+
errors,
|
|
283
|
+
warnings,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function initializeWorkspace(options = {}) {
|
|
288
|
+
const adoptExisting = Boolean(options.adoptExisting);
|
|
289
|
+
const existingState = readInstallState();
|
|
290
|
+
const metadata = packageMetadata();
|
|
291
|
+
const markers = existingBootstrapMarkers();
|
|
292
|
+
|
|
293
|
+
if (existingState) {
|
|
294
|
+
return {
|
|
295
|
+
mode: "already-initialized",
|
|
296
|
+
seededFiles: [],
|
|
297
|
+
adoptedFiles: existingState.adoptedFiles || [],
|
|
298
|
+
installStatePath: INSTALL_STATE_PATH,
|
|
299
|
+
state: existingState,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (markers.length > 0 && !adoptExisting) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`Existing Wave bootstrap files detected (${markers.slice(0, 8).join(", ")}). Re-run with \`pnpm exec wave init --adopt-existing\` to record them without overwriting anything.`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
ensureWorkspaceSubdir(INSTALL_STATE_DIR);
|
|
310
|
+
ensureWorkspaceSubdir(path.join(INSTALL_STATE_DIR, "upgrade-history"));
|
|
311
|
+
|
|
312
|
+
const seededFiles = [];
|
|
313
|
+
const adoptedFiles = [];
|
|
314
|
+
if (adoptExisting) {
|
|
315
|
+
adoptedFiles.push(...markers);
|
|
316
|
+
} else {
|
|
317
|
+
for (const relPath of STARTER_TEMPLATE_PATHS) {
|
|
318
|
+
copyTemplateFile(relPath);
|
|
319
|
+
seededFiles.push(relPath);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const now = new Date().toISOString();
|
|
324
|
+
const state = {
|
|
325
|
+
schemaVersion: INSTALL_STATE_SCHEMA_VERSION,
|
|
326
|
+
packageName: metadata.name,
|
|
327
|
+
installedVersion: metadata.version,
|
|
328
|
+
initializedAt: now,
|
|
329
|
+
lastUpgradeAt: now,
|
|
330
|
+
initMode: adoptExisting ? "adopt-existing" : "fresh",
|
|
331
|
+
seededFiles,
|
|
332
|
+
adoptedFiles,
|
|
333
|
+
history: [
|
|
334
|
+
{
|
|
335
|
+
at: now,
|
|
336
|
+
action: adoptExisting ? "init-adopt-existing" : "init-fresh",
|
|
337
|
+
version: metadata.version,
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
};
|
|
341
|
+
writeInstallState(state);
|
|
342
|
+
return {
|
|
343
|
+
mode: state.initMode,
|
|
344
|
+
seededFiles,
|
|
345
|
+
adoptedFiles,
|
|
346
|
+
installStatePath: INSTALL_STATE_PATH,
|
|
347
|
+
state,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function readChangelog(options = {}) {
|
|
352
|
+
const manifest = readChangelogManifest();
|
|
353
|
+
const installState = readInstallState();
|
|
354
|
+
const currentVersion = packageMetadata().version;
|
|
355
|
+
const releases = options.sinceInstalled
|
|
356
|
+
? releasesBetween(manifest, installState?.installedVersion || null, currentVersion)
|
|
357
|
+
: manifest.releases;
|
|
358
|
+
return {
|
|
359
|
+
packageName: manifest.packageName || packageMetadata().name,
|
|
360
|
+
currentVersion,
|
|
361
|
+
installedVersion: installState?.installedVersion || null,
|
|
362
|
+
releases,
|
|
363
|
+
markdown: [
|
|
364
|
+
"# Wave Package Changelog",
|
|
365
|
+
"",
|
|
366
|
+
...renderReleaseNotes(releases),
|
|
367
|
+
].join("\n"),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function upgradeWorkspace() {
|
|
372
|
+
const existingState = readInstallState();
|
|
373
|
+
if (!existingState) {
|
|
374
|
+
throw new Error("Workspace is not initialized. Run `pnpm exec wave init` first.");
|
|
375
|
+
}
|
|
376
|
+
const metadata = packageMetadata();
|
|
377
|
+
const manifest = readChangelogManifest();
|
|
378
|
+
const previousVersion = existingState.installedVersion || null;
|
|
379
|
+
const releases = releasesBetween(manifest, previousVersion, metadata.version);
|
|
380
|
+
const doctor = runDoctor();
|
|
381
|
+
const generatedAt = new Date().toISOString();
|
|
382
|
+
const report = {
|
|
383
|
+
packageName: metadata.name,
|
|
384
|
+
previousVersion,
|
|
385
|
+
currentVersion: metadata.version,
|
|
386
|
+
generatedAt,
|
|
387
|
+
releases,
|
|
388
|
+
doctor,
|
|
389
|
+
};
|
|
390
|
+
ensureDirectory(UPGRADE_HISTORY_DIR);
|
|
391
|
+
const reportBaseName = `${generatedAt.replace(/[:]/g, "-")}-${slugifyVersion(previousVersion)}-to-${slugifyVersion(metadata.version)}`;
|
|
392
|
+
const markdownPath = path.join(UPGRADE_HISTORY_DIR, `${reportBaseName}.md`);
|
|
393
|
+
const jsonPath = path.join(UPGRADE_HISTORY_DIR, `${reportBaseName}.json`);
|
|
394
|
+
fs.writeFileSync(markdownPath, `${formatUpgradeReport(report)}\n`, "utf8");
|
|
395
|
+
writeJsonAtomic(jsonPath, report);
|
|
396
|
+
|
|
397
|
+
const nextState = {
|
|
398
|
+
...existingState,
|
|
399
|
+
installedVersion: metadata.version,
|
|
400
|
+
lastUpgradeAt: generatedAt,
|
|
401
|
+
lastUpgradeReport: path.relative(REPO_ROOT, markdownPath),
|
|
402
|
+
history: nextHistoryRecord(existingState, {
|
|
403
|
+
at: generatedAt,
|
|
404
|
+
action: "upgrade",
|
|
405
|
+
fromVersion: previousVersion,
|
|
406
|
+
toVersion: metadata.version,
|
|
407
|
+
report: path.relative(REPO_ROOT, markdownPath),
|
|
408
|
+
}),
|
|
409
|
+
};
|
|
410
|
+
writeInstallState(nextState);
|
|
411
|
+
return {
|
|
412
|
+
report,
|
|
413
|
+
markdownPath,
|
|
414
|
+
jsonPath,
|
|
415
|
+
state: nextState,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function printJson(payload) {
|
|
420
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function printHelp() {
|
|
424
|
+
console.log(`Usage:
|
|
425
|
+
wave init [--adopt-existing] [--json]
|
|
426
|
+
wave upgrade [--json]
|
|
427
|
+
wave changelog [--since-installed] [--json]
|
|
428
|
+
wave doctor [--json]
|
|
429
|
+
`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export async function runInstallCli(argv) {
|
|
433
|
+
const args = Array.isArray(argv) ? argv.slice() : [];
|
|
434
|
+
const subcommand = String(args.shift() || "").trim().toLowerCase();
|
|
435
|
+
const options = {
|
|
436
|
+
adoptExisting: false,
|
|
437
|
+
json: false,
|
|
438
|
+
sinceInstalled: false,
|
|
439
|
+
};
|
|
440
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
441
|
+
const arg = args[index];
|
|
442
|
+
if (arg === "--adopt-existing") {
|
|
443
|
+
options.adoptExisting = true;
|
|
444
|
+
} else if (arg === "--json") {
|
|
445
|
+
options.json = true;
|
|
446
|
+
} else if (arg === "--since-installed") {
|
|
447
|
+
options.sinceInstalled = true;
|
|
448
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
449
|
+
printHelp();
|
|
450
|
+
return;
|
|
451
|
+
} else {
|
|
452
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (!subcommand) {
|
|
457
|
+
printHelp();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (subcommand === "init") {
|
|
462
|
+
const result = initializeWorkspace({ adoptExisting: options.adoptExisting });
|
|
463
|
+
if (options.json) {
|
|
464
|
+
printJson(result);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
console.log(`[wave:init] mode=${result.mode}`);
|
|
468
|
+
if (result.seededFiles.length > 0) {
|
|
469
|
+
console.log(`[wave:init] seeded files: ${result.seededFiles.join(", ")}`);
|
|
470
|
+
}
|
|
471
|
+
if (result.adoptedFiles.length > 0) {
|
|
472
|
+
console.log(`[wave:init] adopted files: ${result.adoptedFiles.join(", ")}`);
|
|
473
|
+
}
|
|
474
|
+
console.log(`[wave:init] install state: ${path.relative(REPO_ROOT, result.installStatePath)}`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (subcommand === "upgrade") {
|
|
479
|
+
const result = upgradeWorkspace();
|
|
480
|
+
if (options.json) {
|
|
481
|
+
printJson({
|
|
482
|
+
markdownPath: path.relative(REPO_ROOT, result.markdownPath),
|
|
483
|
+
jsonPath: path.relative(REPO_ROOT, result.jsonPath),
|
|
484
|
+
report: result.report,
|
|
485
|
+
});
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
console.log(`[wave:upgrade] ${result.report.previousVersion || "unknown"} -> ${result.report.currentVersion}`);
|
|
489
|
+
console.log(`[wave:upgrade] report: ${path.relative(REPO_ROOT, result.markdownPath)}`);
|
|
490
|
+
if (result.report.doctor.warnings.length > 0) {
|
|
491
|
+
for (const warning of result.report.doctor.warnings) {
|
|
492
|
+
console.warn(`[wave:upgrade] warning: ${warning}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (result.report.doctor.errors.length > 0) {
|
|
496
|
+
for (const error of result.report.doctor.errors) {
|
|
497
|
+
console.error(`[wave:upgrade] error: ${error}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (subcommand === "changelog") {
|
|
504
|
+
const result = readChangelog({ sinceInstalled: options.sinceInstalled });
|
|
505
|
+
if (options.json) {
|
|
506
|
+
printJson(result);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
console.log(result.markdown);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (subcommand === "doctor") {
|
|
514
|
+
const result = runDoctor();
|
|
515
|
+
if (options.json) {
|
|
516
|
+
printJson(result);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
console.log(`[wave:doctor] workspace=${result.workspaceRoot}`);
|
|
520
|
+
console.log(`[wave:doctor] package=${result.packageName}@${result.packageVersion}`);
|
|
521
|
+
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
522
|
+
console.log("[wave:doctor] ok");
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
for (const error of result.errors) {
|
|
526
|
+
console.error(`[wave:doctor] error: ${error}`);
|
|
527
|
+
}
|
|
528
|
+
for (const warning of result.warnings) {
|
|
529
|
+
console.warn(`[wave:doctor] warning: ${warning}`);
|
|
530
|
+
}
|
|
531
|
+
if (result.errors.length > 0) {
|
|
532
|
+
const error = new Error("Wave doctor found blocking issues.");
|
|
533
|
+
error.exitCode = 1;
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
throw new Error(`Unknown subcommand: ${subcommand}`);
|
|
540
|
+
}
|