@zeyue0329/xiaoma-cli 1.0.37 → 1.0.39

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 (89) hide show
  1. package/.idea/workspace.xml +27 -26
  2. package/JAVA-BACKEND-COMMANDS-REFERENCE.md +62 -52
  3. package/JAVA-BACKEND-ITERATION-GUIDE.md +125 -18
  4. package/README.md +1 -1
  5. package/common/utils/bmad-doc-template.md +5 -5
  6. package/dist/agents/analyst.txt +35 -5
  7. package/dist/agents/architect.txt +217 -31
  8. package/dist/agents/automation-orchestrator.txt +4 -4
  9. package/dist/agents/dev.txt +3 -3
  10. package/dist/agents/full-requirement-orchestrator.txt +11 -11
  11. package/dist/agents/qa.txt +102 -102
  12. package/dist/agents/sm.txt +6 -6
  13. package/dist/agents/ux-expert.txt +6 -1
  14. package/dist/agents/workflow-executor.txt +879 -0
  15. package/dist/agents/xiaoma-master.txt +258 -37
  16. package/dist/teams/team-all.txt +1223 -445
  17. package/dist/teams/team-fullstack-with-database.txt +384 -446
  18. package/dist/teams/team-fullstack.txt +258 -37
  19. package/dist/teams/team-ide-minimal.txt +111 -111
  20. package/dist/teams/team-no-ui.txt +252 -36
  21. package/docs/architecture-sharding-modification.md +623 -0
  22. package/docs/automated-requirements-analysis-outputs.md +896 -0
  23. package/package.json +1 -1
  24. package/tools/builders/web-builder.js +292 -142
  25. package/tools/bump-all-versions.js +50 -32
  26. package/tools/cli.js +52 -47
  27. package/tools/flattener/aggregate.js +30 -12
  28. package/tools/flattener/binary.js +46 -43
  29. package/tools/flattener/discovery.js +23 -15
  30. package/tools/flattener/files.js +6 -6
  31. package/tools/flattener/ignoreRules.js +122 -121
  32. package/tools/flattener/main.js +249 -144
  33. package/tools/flattener/projectRoot.js +74 -69
  34. package/tools/flattener/prompts.js +12 -10
  35. package/tools/flattener/stats.helpers.js +90 -61
  36. package/tools/flattener/stats.js +1 -1
  37. package/tools/flattener/test-matrix.js +225 -170
  38. package/tools/flattener/xml.js +31 -23
  39. package/tools/installer/bin/xiaoma.js +199 -153
  40. package/tools/installer/lib/config-loader.js +76 -47
  41. package/tools/installer/lib/file-manager.js +101 -44
  42. package/tools/installer/lib/ide-base-setup.js +49 -39
  43. package/tools/installer/lib/ide-setup.js +694 -380
  44. package/tools/installer/lib/installer.js +802 -469
  45. package/tools/installer/lib/memory-profiler.js +22 -12
  46. package/tools/installer/lib/module-manager.js +16 -14
  47. package/tools/installer/lib/resource-locator.js +61 -35
  48. package/tools/lib/dependency-resolver.js +34 -23
  49. package/tools/lib/yaml-utils.js +7 -2
  50. package/tools/preview-release-notes.js +33 -25
  51. package/tools/shared/bannerArt.js +3 -3
  52. package/tools/sync-installer-version.js +16 -7
  53. package/tools/upgraders/v3-to-v4-upgrader.js +244 -163
  54. package/tools/version-bump.js +24 -18
  55. package/tools/xiaoma-npx-wrapper.js +15 -10
  56. package/tools/yaml-format.js +60 -36
  57. package/xiaoma-core/agent-teams/team-fullstack-with-database.yaml +0 -1
  58. package/xiaoma-core/agents/automated-fix-validator.yaml +2 -1
  59. package/xiaoma-core/agents/automated-quality-validator.yaml +10 -5
  60. package/xiaoma-core/agents/automation-orchestrator.md +4 -4
  61. package/xiaoma-core/agents/dev.md +4 -4
  62. package/xiaoma-core/agents/enhanced-workflow-orchestrator.yaml +2 -1
  63. package/xiaoma-core/agents/full-requirement-orchestrator.md +11 -11
  64. package/xiaoma-core/agents/global-requirements-auditor.yaml +11 -3
  65. package/xiaoma-core/agents/intelligent-template-adapter.yaml +19 -5
  66. package/xiaoma-core/agents/master-execution-engine.yaml +19 -5
  67. package/xiaoma-core/agents/workflow-executor.md +8 -4
  68. package/xiaoma-core/agents/xiaoma-master.md +1 -1
  69. package/xiaoma-core/data/test-levels-framework.md +12 -12
  70. package/xiaoma-core/tasks/analyze-existing-database.md +1 -1
  71. package/xiaoma-core/tasks/apply-qa-fixes.md +3 -3
  72. package/xiaoma-core/tasks/batch-story-generation.md +22 -22
  73. package/xiaoma-core/tasks/create-enhanced-story-with-database.md +6 -6
  74. package/xiaoma-core/tasks/nfr-assess.md +6 -6
  75. package/xiaoma-core/tasks/project-integration-testing.md +42 -42
  76. package/xiaoma-core/tasks/qa-gate.md +23 -23
  77. package/xiaoma-core/tasks/review-story.md +18 -18
  78. package/xiaoma-core/tasks/risk-profile.md +25 -25
  79. package/xiaoma-core/tasks/serial-development-orchestration.md +51 -51
  80. package/xiaoma-core/tasks/test-design.md +9 -9
  81. package/xiaoma-core/tasks/trace-requirements.md +21 -21
  82. package/xiaoma-core/templates/competitor-analysis-tmpl.yaml +35 -5
  83. package/xiaoma-core/templates/front-end-architecture-tmpl.yaml +77 -11
  84. package/xiaoma-core/templates/front-end-spec-tmpl.yaml +6 -1
  85. package/xiaoma-core/templates/fullstack-architecture-tmpl.yaml +140 -20
  86. package/xiaoma-core/templates/global-qa-monitoring-tmpl.yaml +2 -1
  87. package/xiaoma-core/templates/requirements-coverage-audit.yaml +2 -1
  88. package/xiaoma-core/workflows/automated-requirements-analysis.yaml +283 -6
  89. package/dist/agents/database-architect.txt +0 -322
@@ -1,40 +1,47 @@
1
- const path = require('node:path');
2
- const fs = require('fs-extra');
3
- const chalk = require('chalk');
4
- const ora = require('ora');
5
- const inquirer = require('inquirer');
6
- const fileManager = require('./file-manager');
7
- const configLoader = require('./config-loader');
8
- const ideSetup = require('./ide-setup');
9
- const { extractYamlFromAgent } = require('../../lib/yaml-utils');
10
- const resourceLocator = require('./resource-locator');
1
+ const path = require("node:path");
2
+ const fs = require("fs-extra");
3
+ const chalk = require("chalk");
4
+ const ora = require("ora");
5
+ const inquirer = require("inquirer");
6
+ const fileManager = require("./file-manager");
7
+ const configLoader = require("./config-loader");
8
+ const ideSetup = require("./ide-setup");
9
+ const { extractYamlFromAgent } = require("../../lib/yaml-utils");
10
+ const resourceLocator = require("./resource-locator");
11
11
 
12
12
  class Installer {
13
13
  async getCoreVersion() {
14
14
  try {
15
15
  // Always use package.json version
16
- const packagePath = path.join(__dirname, '..', '..', '..', 'package.json');
16
+ const packagePath = path.join(
17
+ __dirname,
18
+ "..",
19
+ "..",
20
+ "..",
21
+ "package.json",
22
+ );
17
23
  const packageJson = require(packagePath);
18
24
  return packageJson.version;
19
25
  } catch {
20
26
  console.warn("Could not read version from package.json, using 'unknown'");
21
- return 'unknown';
27
+ return "unknown";
22
28
  }
23
29
  }
24
30
 
25
31
  async install(config) {
26
- const spinner = ora('Analyzing installation directory...').start();
32
+ const spinner = ora("Analyzing installation directory...").start();
27
33
 
28
34
  try {
29
35
  // Store the original CWD where npx was executed
30
- const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
36
+ const originalCwd =
37
+ process.env.INIT_CWD || process.env.PWD || process.cwd();
31
38
 
32
39
  // Resolve installation directory relative to where the user ran the command
33
40
  let installDir = path.isAbsolute(config.directory)
34
41
  ? config.directory
35
42
  : path.resolve(originalCwd, config.directory);
36
43
 
37
- if (path.basename(installDir) === '.xiaoma-core') {
44
+ if (path.basename(installDir) === ".xiaoma-core") {
38
45
  // If user points directly to .xiaoma-core, treat its parent as the project root
39
46
  installDir = path.dirname(installDir);
40
47
  }
@@ -51,42 +58,42 @@ class Installer {
51
58
 
52
59
  const { action } = await inquirer.prompt([
53
60
  {
54
- type: 'list',
55
- name: 'action',
56
- message: 'What would you like to do?',
61
+ type: "list",
62
+ name: "action",
63
+ message: "What would you like to do?",
57
64
  choices: [
58
65
  {
59
- name: 'Create the directory and continue',
60
- value: 'create',
66
+ name: "Create the directory and continue",
67
+ value: "create",
61
68
  },
62
69
  {
63
- name: 'Choose a different directory',
64
- value: 'change',
70
+ name: "Choose a different directory",
71
+ value: "change",
65
72
  },
66
73
  {
67
- name: 'Cancel installation',
68
- value: 'cancel',
74
+ name: "Cancel installation",
75
+ value: "cancel",
69
76
  },
70
77
  ],
71
78
  },
72
79
  ]);
73
80
 
74
81
  switch (action) {
75
- case 'cancel': {
76
- console.log('Installation cancelled.');
82
+ case "cancel": {
83
+ console.log("Installation cancelled.");
77
84
  process.exit(0);
78
85
 
79
86
  break;
80
87
  }
81
- case 'change': {
88
+ case "change": {
82
89
  const { newDirectory } = await inquirer.prompt([
83
90
  {
84
- type: 'input',
85
- name: 'newDirectory',
86
- message: 'Enter the new directory path:',
91
+ type: "input",
92
+ name: "newDirectory",
93
+ message: "Enter the new directory path:",
87
94
  validate: (input) => {
88
95
  if (!input.trim()) {
89
- return 'Please enter a valid directory path';
96
+ return "Please enter a valid directory path";
90
97
  }
91
98
  return true;
92
99
  },
@@ -96,13 +103,15 @@ class Installer {
96
103
  config.directory = newDirectory;
97
104
  return await this.install(config); // Recursive call with new directory
98
105
  }
99
- case 'create': {
106
+ case "create": {
100
107
  try {
101
108
  await fileManager.ensureDirectory(installDir);
102
109
  console.log(`✓ Created directory: ${installDir}`);
103
110
  } catch (error) {
104
111
  console.error(`Failed to create directory: ${error.message}`);
105
- console.error('You may need to check permissions or use a different path.');
112
+ console.error(
113
+ "You may need to check permissions or use a different path.",
114
+ );
106
115
  process.exit(1);
107
116
  }
108
117
 
@@ -111,17 +120,22 @@ class Installer {
111
120
  // No default
112
121
  }
113
122
 
114
- spinner.start('Analyzing installation directory...');
123
+ spinner.start("Analyzing installation directory...");
115
124
  }
116
125
 
117
126
  // If this is an update request from early detection, handle it directly
118
- if (config.installType === 'update') {
127
+ if (config.installType === "update") {
119
128
  const state = await this.detectInstallationState(installDir);
120
- if (state.type === 'v4_existing') {
121
- return await this.performUpdate(config, installDir, state.manifest, spinner);
129
+ if (state.type === "v4_existing") {
130
+ return await this.performUpdate(
131
+ config,
132
+ installDir,
133
+ state.manifest,
134
+ spinner,
135
+ );
122
136
  } else {
123
- spinner.fail('No existing v4 installation found to update');
124
- throw new Error('No existing v4 installation found');
137
+ spinner.fail("No existing v4 installation found to update");
138
+ throw new Error("No existing v4 installation found");
125
139
  }
126
140
  }
127
141
 
@@ -130,28 +144,43 @@ class Installer {
130
144
 
131
145
  // Handle different states
132
146
  switch (state.type) {
133
- case 'clean': {
147
+ case "clean": {
134
148
  return await this.performFreshInstall(config, installDir, spinner);
135
149
  }
136
150
 
137
- case 'v4_existing': {
138
- return await this.handleExistingV4Installation(config, installDir, state, spinner);
151
+ case "v4_existing": {
152
+ return await this.handleExistingV4Installation(
153
+ config,
154
+ installDir,
155
+ state,
156
+ spinner,
157
+ );
139
158
  }
140
159
 
141
- case 'v3_existing': {
142
- return await this.handleV3Installation(config, installDir, state, spinner);
160
+ case "v3_existing": {
161
+ return await this.handleV3Installation(
162
+ config,
163
+ installDir,
164
+ state,
165
+ spinner,
166
+ );
143
167
  }
144
168
 
145
- case 'unknown_existing': {
146
- return await this.handleUnknownInstallation(config, installDir, state, spinner);
169
+ case "unknown_existing": {
170
+ return await this.handleUnknownInstallation(
171
+ config,
172
+ installDir,
173
+ state,
174
+ spinner,
175
+ );
147
176
  }
148
177
  }
149
178
  } catch (error) {
150
179
  // Check if modules were initialized
151
180
  if (spinner) {
152
- spinner.fail('Installation failed');
181
+ spinner.fail("Installation failed");
153
182
  } else {
154
- console.error('Installation failed:', error.message);
183
+ console.error("Installation failed:", error.message);
155
184
  }
156
185
  throw error;
157
186
  }
@@ -159,7 +188,7 @@ class Installer {
159
188
 
160
189
  async detectInstallationState(installDir) {
161
190
  const state = {
162
- type: 'clean',
191
+ type: "clean",
163
192
  hasV4Manifest: false,
164
193
  hasV3Structure: false,
165
194
  hasBmadCore: false,
@@ -174,11 +203,11 @@ class Installer {
174
203
  }
175
204
 
176
205
  // Check for V4 installation (has .xiaoma-core with manifest)
177
- const bmadCorePath = path.join(installDir, '.xiaoma-core');
178
- const manifestPath = path.join(bmadCorePath, 'install-manifest.yaml');
206
+ const bmadCorePath = path.join(installDir, ".xiaoma-core");
207
+ const manifestPath = path.join(bmadCorePath, "install-manifest.yaml");
179
208
 
180
209
  if (await fileManager.pathExists(manifestPath)) {
181
- state.type = 'v4_existing';
210
+ state.type = "v4_existing";
182
211
  state.hasV4Manifest = true;
183
212
  state.hasBmadCore = true;
184
213
  state.manifest = await fileManager.readManifest(installDir);
@@ -186,25 +215,25 @@ class Installer {
186
215
  }
187
216
 
188
217
  // Check for V3 installation (has bmad-agent directory)
189
- const bmadAgentPath = path.join(installDir, 'bmad-agent');
218
+ const bmadAgentPath = path.join(installDir, "bmad-agent");
190
219
  if (await fileManager.pathExists(bmadAgentPath)) {
191
- state.type = 'v3_existing';
220
+ state.type = "v3_existing";
192
221
  state.hasV3Structure = true;
193
222
  return state;
194
223
  }
195
224
 
196
225
  // Check for .xiaoma-core without manifest (broken V4 or manual copy)
197
226
  if (await fileManager.pathExists(bmadCorePath)) {
198
- state.type = 'unknown_existing';
227
+ state.type = "unknown_existing";
199
228
  state.hasBmadCore = true;
200
229
  return state;
201
230
  }
202
231
 
203
232
  // Check if directory has other files
204
- const files = await resourceLocator.findFiles('**/*', {
233
+ const files = await resourceLocator.findFiles("**/*", {
205
234
  cwd: installDir,
206
235
  nodir: true,
207
- ignore: ['**/.git/**', '**/node_modules/**'],
236
+ ignore: ["**/.git/**", "**/node_modules/**"],
208
237
  });
209
238
 
210
239
  if (files.length > 0) {
@@ -221,41 +250,41 @@ class Installer {
221
250
  }
222
251
 
223
252
  async performFreshInstall(config, installDir, spinner, options = {}) {
224
- spinner.text = 'Installing XiaoMa-Cli...';
253
+ spinner.text = "Installing XiaoMa-Cli...";
225
254
 
226
255
  let files = [];
227
256
 
228
257
  switch (config.installType) {
229
- case 'full': {
258
+ case "full": {
230
259
  // Full installation - copy entire .xiaoma-core folder as a subdirectory
231
- spinner.text = 'Copying complete .xiaoma-core folder...';
260
+ spinner.text = "Copying complete .xiaoma-core folder...";
232
261
  const sourceDir = resourceLocator.getBmadCorePath();
233
- const bmadCoreDestDir = path.join(installDir, '.xiaoma-core');
262
+ const bmadCoreDestDir = path.join(installDir, ".xiaoma-core");
234
263
  await fileManager.copyDirectoryWithRootReplacement(
235
264
  sourceDir,
236
265
  bmadCoreDestDir,
237
- '.xiaoma-core',
266
+ ".xiaoma-core",
238
267
  );
239
268
 
240
269
  // Copy common/ items to .xiaoma-core
241
- spinner.text = 'Copying common utilities...';
242
- await this.copyCommonItems(installDir, '.xiaoma-core', spinner);
270
+ spinner.text = "Copying common utilities...";
271
+ await this.copyCommonItems(installDir, ".xiaoma-core", spinner);
243
272
 
244
273
  // Copy documentation files from docs/ to .xiaoma-core
245
- spinner.text = 'Copying documentation files...';
246
- await this.copyDocsItems(installDir, '.xiaoma-core', spinner);
274
+ spinner.text = "Copying documentation files...";
275
+ await this.copyDocsItems(installDir, ".xiaoma-core", spinner);
247
276
 
248
277
  // Get list of all files for manifest
249
- const foundFiles = await resourceLocator.findFiles('**/*', {
278
+ const foundFiles = await resourceLocator.findFiles("**/*", {
250
279
  cwd: bmadCoreDestDir,
251
280
  nodir: true,
252
- ignore: ['**/.git/**', '**/node_modules/**'],
281
+ ignore: ["**/.git/**", "**/node_modules/**"],
253
282
  });
254
- files = foundFiles.map((file) => path.join('.xiaoma-core', file));
283
+ files = foundFiles.map((file) => path.join(".xiaoma-core", file));
255
284
 
256
285
  break;
257
286
  }
258
- case 'single-agent': {
287
+ case "single-agent": {
259
288
  // Single agent installation
260
289
  spinner.text = `Installing ${config.agent} agent...`;
261
290
 
@@ -263,44 +292,54 @@ class Installer {
263
292
  const agentPath = configLoader.getAgentPath(config.agent);
264
293
  const destinationAgentPath = path.join(
265
294
  installDir,
266
- '.xiaoma-core',
267
- 'agents',
295
+ ".xiaoma-core",
296
+ "agents",
268
297
  `${config.agent}.md`,
269
298
  );
270
299
  await fileManager.copyFileWithRootReplacement(
271
300
  agentPath,
272
301
  destinationAgentPath,
273
- '.xiaoma-core',
302
+ ".xiaoma-core",
274
303
  );
275
304
  files.push(`.xiaoma-core/agents/${config.agent}.md`);
276
305
 
277
306
  // Copy dependencies
278
- const { all: dependencies } = await resourceLocator.getAgentDependencies(config.agent);
307
+ const { all: dependencies } =
308
+ await resourceLocator.getAgentDependencies(config.agent);
279
309
  const sourceBase = resourceLocator.getBmadCorePath();
280
310
 
281
311
  for (const dep of dependencies) {
282
312
  spinner.text = `Copying dependency: ${dep}`;
283
313
 
284
- if (dep.includes('*')) {
314
+ if (dep.includes("*")) {
285
315
  // Handle glob patterns with {root} replacement
286
316
  const copiedFiles = await fileManager.copyGlobPattern(
287
- dep.replace('.xiaoma-core/', ''),
317
+ dep.replace(".xiaoma-core/", ""),
288
318
  sourceBase,
289
- path.join(installDir, '.xiaoma-core'),
290
- '.xiaoma-core',
319
+ path.join(installDir, ".xiaoma-core"),
320
+ ".xiaoma-core",
291
321
  );
292
322
  files.push(...copiedFiles.map((f) => `.xiaoma-core/${f}`));
293
323
  } else {
294
324
  // Handle single files with {root} replacement if needed
295
- const sourcePath = path.join(sourceBase, dep.replace('.xiaoma-core/', ''));
325
+ const sourcePath = path.join(
326
+ sourceBase,
327
+ dep.replace(".xiaoma-core/", ""),
328
+ );
296
329
  const destinationPath = path.join(installDir, dep);
297
330
 
298
331
  const needsRootReplacement =
299
- dep.endsWith('.md') || dep.endsWith('.yaml') || dep.endsWith('.yml');
332
+ dep.endsWith(".md") ||
333
+ dep.endsWith(".yaml") ||
334
+ dep.endsWith(".yml");
300
335
  let success = false;
301
336
 
302
337
  success = await (needsRootReplacement
303
- ? fileManager.copyFileWithRootReplacement(sourcePath, destinationPath, '.xiaoma-core')
338
+ ? fileManager.copyFileWithRootReplacement(
339
+ sourcePath,
340
+ destinationPath,
341
+ ".xiaoma-core",
342
+ )
304
343
  : fileManager.copyFile(sourcePath, destinationPath));
305
344
 
306
345
  if (success) {
@@ -310,49 +349,68 @@ class Installer {
310
349
  }
311
350
 
312
351
  // Copy common/ items to .xiaoma-core
313
- spinner.text = 'Copying common utilities...';
314
- const commonFiles = await this.copyCommonItems(installDir, '.xiaoma-core', spinner);
352
+ spinner.text = "Copying common utilities...";
353
+ const commonFiles = await this.copyCommonItems(
354
+ installDir,
355
+ ".xiaoma-core",
356
+ spinner,
357
+ );
315
358
  files.push(...commonFiles);
316
359
 
317
360
  // Copy documentation files from docs/ to .xiaoma-core
318
- spinner.text = 'Copying documentation files...';
319
- const documentFiles = await this.copyDocsItems(installDir, '.xiaoma-core', spinner);
361
+ spinner.text = "Copying documentation files...";
362
+ const documentFiles = await this.copyDocsItems(
363
+ installDir,
364
+ ".xiaoma-core",
365
+ spinner,
366
+ );
320
367
  files.push(...documentFiles);
321
368
 
322
369
  break;
323
370
  }
324
- case 'team': {
371
+ case "team": {
325
372
  // Team installation
326
373
  spinner.text = `Installing ${config.team} team...`;
327
374
 
328
375
  // Get team dependencies
329
- const teamDependencies = await configLoader.getTeamDependencies(config.team);
376
+ const teamDependencies = await configLoader.getTeamDependencies(
377
+ config.team,
378
+ );
330
379
  const sourceBase = resourceLocator.getBmadCorePath();
331
380
 
332
381
  // Install all team dependencies
333
382
  for (const dep of teamDependencies) {
334
383
  spinner.text = `Copying team dependency: ${dep}`;
335
384
 
336
- if (dep.includes('*')) {
385
+ if (dep.includes("*")) {
337
386
  // Handle glob patterns with {root} replacement
338
387
  const copiedFiles = await fileManager.copyGlobPattern(
339
- dep.replace('.xiaoma-core/', ''),
388
+ dep.replace(".xiaoma-core/", ""),
340
389
  sourceBase,
341
- path.join(installDir, '.xiaoma-core'),
342
- '.xiaoma-core',
390
+ path.join(installDir, ".xiaoma-core"),
391
+ ".xiaoma-core",
343
392
  );
344
393
  files.push(...copiedFiles.map((f) => `.xiaoma-core/${f}`));
345
394
  } else {
346
395
  // Handle single files with {root} replacement if needed
347
- const sourcePath = path.join(sourceBase, dep.replace('.xiaoma-core/', ''));
396
+ const sourcePath = path.join(
397
+ sourceBase,
398
+ dep.replace(".xiaoma-core/", ""),
399
+ );
348
400
  const destinationPath = path.join(installDir, dep);
349
401
 
350
402
  const needsRootReplacement =
351
- dep.endsWith('.md') || dep.endsWith('.yaml') || dep.endsWith('.yml');
403
+ dep.endsWith(".md") ||
404
+ dep.endsWith(".yaml") ||
405
+ dep.endsWith(".yml");
352
406
  let success = false;
353
407
 
354
408
  success = await (needsRootReplacement
355
- ? fileManager.copyFileWithRootReplacement(sourcePath, destinationPath, '.xiaoma-core')
409
+ ? fileManager.copyFileWithRootReplacement(
410
+ sourcePath,
411
+ destinationPath,
412
+ ".xiaoma-core",
413
+ )
356
414
  : fileManager.copyFile(sourcePath, destinationPath));
357
415
 
358
416
  if (success) {
@@ -362,21 +420,29 @@ class Installer {
362
420
  }
363
421
 
364
422
  // Copy common/ items to .xiaoma-core
365
- spinner.text = 'Copying common utilities...';
366
- const commonFiles = await this.copyCommonItems(installDir, '.xiaoma-core', spinner);
423
+ spinner.text = "Copying common utilities...";
424
+ const commonFiles = await this.copyCommonItems(
425
+ installDir,
426
+ ".xiaoma-core",
427
+ spinner,
428
+ );
367
429
  files.push(...commonFiles);
368
430
 
369
431
  // Copy documentation files from docs/ to .xiaoma-core
370
- spinner.text = 'Copying documentation files...';
371
- const documentFiles = await this.copyDocsItems(installDir, '.xiaoma-core', spinner);
432
+ spinner.text = "Copying documentation files...";
433
+ const documentFiles = await this.copyDocsItems(
434
+ installDir,
435
+ ".xiaoma-core",
436
+ spinner,
437
+ );
372
438
  files.push(...documentFiles);
373
439
 
374
440
  break;
375
441
  }
376
- case 'expansion-only': {
442
+ case "expansion-only": {
377
443
  // Expansion-only installation - DO NOT create .xiaoma-core
378
444
  // Only install expansion packs
379
- spinner.text = 'Installing expansion packs only...';
445
+ spinner.text = "Installing expansion packs only...";
380
446
 
381
447
  break;
382
448
  }
@@ -394,9 +460,10 @@ class Installer {
394
460
 
395
461
  // Install web bundles if requested
396
462
  if (config.includeWebBundles && config.webBundlesDirectory) {
397
- spinner.text = 'Installing web bundles...';
463
+ spinner.text = "Installing web bundles...";
398
464
  // Resolve web bundles directory using the same logic as the main installation directory
399
- const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
465
+ const originalCwd =
466
+ process.env.INIT_CWD || process.env.PWD || process.cwd();
400
467
  let resolvedWebBundlesDir = path.isAbsolute(config.webBundlesDirectory)
401
468
  ? config.webBundlesDirectory
402
469
  : path.resolve(originalCwd, config.webBundlesDirectory);
@@ -409,31 +476,38 @@ class Installer {
409
476
  for (const ide of ides) {
410
477
  spinner.text = `Setting up ${ide} integration...`;
411
478
  let preConfiguredSettings = null;
412
- if (ide === 'github-copilot') {
479
+ if (ide === "github-copilot") {
413
480
  preConfiguredSettings = config.githubCopilotConfig;
414
- } else if (ide === 'auggie-cli') {
481
+ } else if (ide === "auggie-cli") {
415
482
  preConfiguredSettings = config.augmentCodeConfig;
416
483
  }
417
- await ideSetup.setup(ide, installDir, config.agent, spinner, preConfiguredSettings);
484
+ await ideSetup.setup(
485
+ ide,
486
+ installDir,
487
+ config.agent,
488
+ spinner,
489
+ preConfiguredSettings,
490
+ );
418
491
  }
419
492
  }
420
493
 
421
494
  // Modify core-config.yaml if sharding preferences were provided
422
495
  if (
423
- config.installType !== 'expansion-only' &&
424
- (config.prdSharded !== undefined || config.architectureSharded !== undefined)
496
+ config.installType !== "expansion-only" &&
497
+ (config.prdSharded !== undefined ||
498
+ config.architectureSharded !== undefined)
425
499
  ) {
426
- spinner.text = 'Configuring document sharding settings...';
500
+ spinner.text = "Configuring document sharding settings...";
427
501
  await fileManager.modifyCoreConfig(installDir, config);
428
502
  }
429
503
 
430
504
  // Create manifest (skip for expansion-only installations)
431
- if (config.installType !== 'expansion-only') {
432
- spinner.text = 'Creating installation manifest...';
505
+ if (config.installType !== "expansion-only") {
506
+ spinner.text = "Creating installation manifest...";
433
507
  await fileManager.createManifest(installDir, config, files);
434
508
  }
435
509
 
436
- spinner.succeed('Installation complete!');
510
+ spinner.succeed("Installation complete!");
437
511
  this.showSuccessMessage(config, installDir, options);
438
512
  }
439
513
 
@@ -444,15 +518,20 @@ class Installer {
444
518
  const newVersion = await this.getCoreVersion();
445
519
  const versionCompare = this.compareVersions(currentVersion, newVersion);
446
520
 
447
- console.log(chalk.yellow('\n🔍 Found existing XiaoMa v4 installation'));
521
+ console.log(chalk.yellow("\n🔍 Found existing XiaoMa v4 installation"));
448
522
  console.log(` Directory: ${installDir}`);
449
523
  console.log(` Current version: ${currentVersion}`);
450
524
  console.log(` Available version: ${newVersion}`);
451
- console.log(` Installed: ${new Date(state.manifest.installed_at).toLocaleDateString()}`);
525
+ console.log(
526
+ ` Installed: ${new Date(state.manifest.installed_at).toLocaleDateString()}`,
527
+ );
452
528
 
453
529
  // Check file integrity
454
- spinner.start('Checking installation integrity...');
455
- const integrity = await fileManager.checkFileIntegrity(installDir, state.manifest);
530
+ spinner.start("Checking installation integrity...");
531
+ const integrity = await fileManager.checkFileIntegrity(
532
+ installDir,
533
+ state.manifest,
534
+ );
456
535
  spinner.stop();
457
536
 
458
537
  const hasMissingFiles = integrity.missing.length > 0;
@@ -460,27 +539,33 @@ class Installer {
460
539
  const hasIntegrityIssues = hasMissingFiles || hasModifiedFiles;
461
540
 
462
541
  if (hasIntegrityIssues) {
463
- console.log(chalk.red('\n⚠️ Installation issues detected:'));
542
+ console.log(chalk.red("\n⚠️ Installation issues detected:"));
464
543
  if (hasMissingFiles) {
465
544
  console.log(chalk.red(` Missing files: ${integrity.missing.length}`));
466
545
  if (integrity.missing.length <= 5) {
467
- for (const file of integrity.missing) console.log(chalk.dim(` - ${file}`));
546
+ for (const file of integrity.missing)
547
+ console.log(chalk.dim(` - ${file}`));
468
548
  }
469
549
  }
470
550
  if (hasModifiedFiles) {
471
- console.log(chalk.yellow(` Modified files: ${integrity.modified.length}`));
551
+ console.log(
552
+ chalk.yellow(` Modified files: ${integrity.modified.length}`),
553
+ );
472
554
  if (integrity.modified.length <= 5) {
473
- for (const file of integrity.modified) console.log(chalk.dim(` - ${file}`));
555
+ for (const file of integrity.modified)
556
+ console.log(chalk.dim(` - ${file}`));
474
557
  }
475
558
  }
476
559
  }
477
560
 
478
561
  // Show existing expansion packs
479
562
  if (Object.keys(state.expansionPacks).length > 0) {
480
- console.log(chalk.cyan('\n📦 Installed expansion packs:'));
563
+ console.log(chalk.cyan("\n📦 Installed expansion packs:"));
481
564
  for (const [packId, packInfo] of Object.entries(state.expansionPacks)) {
482
565
  if (packInfo.hasManifest && packInfo.manifest) {
483
- console.log(` - ${packId} (v${packInfo.manifest.version || 'unknown'})`);
566
+ console.log(
567
+ ` - ${packId} (v${packInfo.manifest.version || "unknown"})`,
568
+ );
484
569
  } else {
485
570
  console.log(` - ${packId} (no manifest)`);
486
571
  }
@@ -490,72 +575,86 @@ class Installer {
490
575
  let choices = [];
491
576
 
492
577
  if (versionCompare < 0) {
493
- console.log(chalk.cyan('\n⬆️ Upgrade available for XiaoMa-Cli core'));
578
+ console.log(chalk.cyan("\n⬆️ Upgrade available for XiaoMa-Cli core"));
494
579
  choices.push({
495
580
  name: `Upgrade XiaoMa-Cli core (v${currentVersion} → v${newVersion})`,
496
- value: 'upgrade',
581
+ value: "upgrade",
497
582
  });
498
583
  } else if (versionCompare === 0) {
499
584
  if (hasIntegrityIssues) {
500
585
  // Offer repair option when files are missing or modified
501
586
  choices.push({
502
- name: 'Repair installation (restore missing/modified files)',
503
- value: 'repair',
587
+ name: "Repair installation (restore missing/modified files)",
588
+ value: "repair",
504
589
  });
505
590
  }
506
- console.log(chalk.yellow('\n⚠️ Same version already installed'));
591
+ console.log(chalk.yellow("\n⚠️ Same version already installed"));
507
592
  choices.push({
508
593
  name: `Force reinstall XiaoMa-Cli core (v${currentVersion} - reinstall)`,
509
- value: 'reinstall',
594
+ value: "reinstall",
510
595
  });
511
596
  } else {
512
- console.log(chalk.yellow('\n⬇️ Installed version is newer than available'));
597
+ console.log(
598
+ chalk.yellow("\n⬇️ Installed version is newer than available"),
599
+ );
513
600
  choices.push({
514
601
  name: `Downgrade XiaoMa-Cli core (v${currentVersion} → v${newVersion})`,
515
- value: 'reinstall',
602
+ value: "reinstall",
516
603
  });
517
604
  }
518
605
 
519
606
  choices.push(
520
- { name: 'Add/update expansion packs only', value: 'expansions' },
521
- { name: 'Cancel', value: 'cancel' },
607
+ { name: "Add/update expansion packs only", value: "expansions" },
608
+ { name: "Cancel", value: "cancel" },
522
609
  );
523
610
 
524
611
  const { action } = await inquirer.prompt([
525
612
  {
526
- type: 'list',
527
- name: 'action',
528
- message: 'What would you like to do?',
613
+ type: "list",
614
+ name: "action",
615
+ message: "What would you like to do?",
529
616
  choices: choices,
530
617
  },
531
618
  ]);
532
619
 
533
620
  switch (action) {
534
- case 'upgrade': {
535
- return await this.performUpdate(config, installDir, state.manifest, spinner);
621
+ case "upgrade": {
622
+ return await this.performUpdate(
623
+ config,
624
+ installDir,
625
+ state.manifest,
626
+ spinner,
627
+ );
536
628
  }
537
- case 'repair': {
629
+ case "repair": {
538
630
  // For repair, restore missing/modified files while backing up modified ones
539
- return await this.performRepair(config, installDir, state.manifest, integrity, spinner);
631
+ return await this.performRepair(
632
+ config,
633
+ installDir,
634
+ state.manifest,
635
+ integrity,
636
+ spinner,
637
+ );
540
638
  }
541
- case 'reinstall': {
639
+ case "reinstall": {
542
640
  // For reinstall, don't check for modifications - just overwrite
543
641
  return await this.performReinstall(config, installDir, spinner);
544
642
  }
545
- case 'expansions': {
643
+ case "expansions": {
546
644
  // Ask which expansion packs to install
547
- const availableExpansionPacks = await resourceLocator.getExpansionPacks();
645
+ const availableExpansionPacks =
646
+ await resourceLocator.getExpansionPacks();
548
647
 
549
648
  if (availableExpansionPacks.length === 0) {
550
- console.log(chalk.yellow('No expansion packs available.'));
649
+ console.log(chalk.yellow("No expansion packs available."));
551
650
  return;
552
651
  }
553
652
 
554
653
  const { selectedPacks } = await inquirer.prompt([
555
654
  {
556
- type: 'checkbox',
557
- name: 'selectedPacks',
558
- message: 'Select expansion packs to install/update:',
655
+ type: "checkbox",
656
+ name: "selectedPacks",
657
+ message: "Select expansion packs to install/update:",
559
658
  choices: availableExpansionPacks.map((pack) => ({
560
659
  name: `${pack.name} (v${pack.version}) .${pack.id}`,
561
660
  value: pack.id,
@@ -565,28 +664,28 @@ class Installer {
565
664
  ]);
566
665
 
567
666
  if (selectedPacks.length === 0) {
568
- console.log(chalk.yellow('No expansion packs selected.'));
667
+ console.log(chalk.yellow("No expansion packs selected."));
569
668
  return;
570
669
  }
571
670
 
572
- spinner.start('Installing expansion packs...');
671
+ spinner.start("Installing expansion packs...");
573
672
  const expansionFiles = await this.installExpansionPacks(
574
673
  installDir,
575
674
  selectedPacks,
576
675
  spinner,
577
676
  { ides: config.ides || [] },
578
677
  );
579
- spinner.succeed('Expansion packs installed successfully!');
678
+ spinner.succeed("Expansion packs installed successfully!");
580
679
 
581
- console.log(chalk.green('\n✓ Installation complete!'));
680
+ console.log(chalk.green("\n✓ Installation complete!"));
582
681
  console.log(chalk.green(`✓ Expansion packs installed/updated:`));
583
682
  for (const packId of selectedPacks) {
584
683
  console.log(chalk.green(` - ${packId} → .${packId}/`));
585
684
  }
586
685
  return;
587
686
  }
588
- case 'cancel': {
589
- console.log('Installation cancelled.');
687
+ case "cancel": {
688
+ console.log("Installation cancelled.");
590
689
  return;
591
690
  }
592
691
  }
@@ -595,37 +694,39 @@ class Installer {
595
694
  async handleV3Installation(config, installDir, state, spinner) {
596
695
  spinner.stop();
597
696
 
598
- console.log(chalk.yellow('\n🔍 Found XiaoMa v3 installation (bmad-agent/ directory)'));
697
+ console.log(
698
+ chalk.yellow("\n🔍 Found XiaoMa v3 installation (bmad-agent/ directory)"),
699
+ );
599
700
  console.log(` Directory: ${installDir}`);
600
701
 
601
702
  const { action } = await inquirer.prompt([
602
703
  {
603
- type: 'list',
604
- name: 'action',
605
- message: 'What would you like to do?',
704
+ type: "list",
705
+ name: "action",
706
+ message: "What would you like to do?",
606
707
  choices: [
607
- { name: 'Upgrade from v3 to v4 (recommended)', value: 'upgrade' },
608
- { name: 'Install v4 alongside v3', value: 'alongside' },
609
- { name: 'Cancel', value: 'cancel' },
708
+ { name: "Upgrade from v3 to v4 (recommended)", value: "upgrade" },
709
+ { name: "Install v4 alongside v3", value: "alongside" },
710
+ { name: "Cancel", value: "cancel" },
610
711
  ],
611
712
  },
612
713
  ]);
613
714
 
614
715
  switch (action) {
615
- case 'upgrade': {
616
- console.log(chalk.cyan('\n📦 Starting v3 to v4 upgrade process...'));
617
- const V3ToV4Upgrader = require('../../upgraders/v3-to-v4-upgrader');
716
+ case "upgrade": {
717
+ console.log(chalk.cyan("\n📦 Starting v3 to v4 upgrade process..."));
718
+ const V3ToV4Upgrader = require("../../upgraders/v3-to-v4-upgrader");
618
719
  const upgrader = new V3ToV4Upgrader();
619
720
  return await upgrader.upgrade({
620
721
  projectPath: installDir,
621
722
  ides: config.ides || [], // Pass IDE selections from initial config
622
723
  });
623
724
  }
624
- case 'alongside': {
725
+ case "alongside": {
625
726
  return await this.performFreshInstall(config, installDir, spinner);
626
727
  }
627
- case 'cancel': {
628
- console.log('Installation cancelled.');
728
+ case "cancel": {
729
+ console.log("Installation cancelled.");
629
730
  return;
630
731
  }
631
732
  }
@@ -634,54 +735,54 @@ class Installer {
634
735
  async handleUnknownInstallation(config, installDir, state, spinner) {
635
736
  spinner.stop();
636
737
 
637
- console.log(chalk.yellow('\n⚠️ Directory contains existing files'));
738
+ console.log(chalk.yellow("\n⚠️ Directory contains existing files"));
638
739
  console.log(` Directory: ${installDir}`);
639
740
 
640
741
  if (state.hasBmadCore) {
641
- console.log(' Found: .xiaoma-core directory (but no manifest)');
742
+ console.log(" Found: .xiaoma-core directory (but no manifest)");
642
743
  }
643
744
  if (state.hasOtherFiles) {
644
- console.log(' Found: Other files in directory');
745
+ console.log(" Found: Other files in directory");
645
746
  }
646
747
 
647
748
  const { action } = await inquirer.prompt([
648
749
  {
649
- type: 'list',
650
- name: 'action',
651
- message: 'What would you like to do?',
750
+ type: "list",
751
+ name: "action",
752
+ message: "What would you like to do?",
652
753
  choices: [
653
- { name: 'Install anyway (may overwrite files)', value: 'force' },
654
- { name: 'Choose different directory', value: 'different' },
655
- { name: 'Cancel', value: 'cancel' },
754
+ { name: "Install anyway (may overwrite files)", value: "force" },
755
+ { name: "Choose different directory", value: "different" },
756
+ { name: "Cancel", value: "cancel" },
656
757
  ],
657
758
  },
658
759
  ]);
659
760
 
660
761
  switch (action) {
661
- case 'force': {
762
+ case "force": {
662
763
  return await this.performFreshInstall(config, installDir, spinner);
663
764
  }
664
- case 'different': {
765
+ case "different": {
665
766
  const { newDir } = await inquirer.prompt([
666
767
  {
667
- type: 'input',
668
- name: 'newDir',
669
- message: 'Enter new installation directory:',
670
- default: path.join(path.dirname(installDir), 'bmad-project'),
768
+ type: "input",
769
+ name: "newDir",
770
+ message: "Enter new installation directory:",
771
+ default: path.join(path.dirname(installDir), "bmad-project"),
671
772
  },
672
773
  ]);
673
774
  config.directory = newDir;
674
775
  return await this.install(config);
675
776
  }
676
- case 'cancel': {
677
- console.log('Installation cancelled.');
777
+ case "cancel": {
778
+ console.log("Installation cancelled.");
678
779
  return;
679
780
  }
680
781
  }
681
782
  }
682
783
 
683
784
  async performUpdate(newConfig, installDir, manifest, spinner) {
684
- spinner.start('Checking for updates...');
785
+ spinner.start("Checking for updates...");
685
786
 
686
787
  try {
687
788
  // Get current and new versions
@@ -692,47 +793,53 @@ class Installer {
692
793
  // Only check for modified files if it's an actual version upgrade
693
794
  let modifiedFiles = [];
694
795
  if (versionCompare !== 0) {
695
- spinner.text = 'Checking for modified files...';
696
- modifiedFiles = await fileManager.checkModifiedFiles(installDir, manifest);
796
+ spinner.text = "Checking for modified files...";
797
+ modifiedFiles = await fileManager.checkModifiedFiles(
798
+ installDir,
799
+ manifest,
800
+ );
697
801
  }
698
802
 
699
803
  if (modifiedFiles.length > 0) {
700
- spinner.warn('Found modified files');
701
- console.log(chalk.yellow('\nThe following files have been modified:'));
804
+ spinner.warn("Found modified files");
805
+ console.log(chalk.yellow("\nThe following files have been modified:"));
702
806
  for (const file of modifiedFiles) {
703
807
  console.log(` - ${file}`);
704
808
  }
705
809
 
706
810
  const { action } = await inquirer.prompt([
707
811
  {
708
- type: 'list',
709
- name: 'action',
710
- message: 'How would you like to proceed?',
812
+ type: "list",
813
+ name: "action",
814
+ message: "How would you like to proceed?",
711
815
  choices: [
712
- { name: 'Backup and overwrite modified files', value: 'backup' },
713
- { name: 'Skip modified files', value: 'skip' },
714
- { name: 'Cancel update', value: 'cancel' },
816
+ { name: "Backup and overwrite modified files", value: "backup" },
817
+ { name: "Skip modified files", value: "skip" },
818
+ { name: "Cancel update", value: "cancel" },
715
819
  ],
716
820
  },
717
821
  ]);
718
822
 
719
- if (action === 'cancel') {
720
- console.log('Update cancelled.');
823
+ if (action === "cancel") {
824
+ console.log("Update cancelled.");
721
825
  return;
722
826
  }
723
827
 
724
- if (action === 'backup') {
725
- spinner.start('Backing up modified files...');
828
+ if (action === "backup") {
829
+ spinner.start("Backing up modified files...");
726
830
  for (const file of modifiedFiles) {
727
831
  const filePath = path.join(installDir, file);
728
832
  const backupPath = await fileManager.backupFile(filePath);
729
- console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
833
+ console.log(
834
+ chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`),
835
+ );
730
836
  }
731
837
  }
732
838
  }
733
839
 
734
840
  // Perform update by re-running installation
735
- spinner.text = versionCompare === 0 ? 'Reinstalling files...' : 'Updating files...';
841
+ spinner.text =
842
+ versionCompare === 0 ? "Reinstalling files..." : "Updating files...";
736
843
  const config = {
737
844
  installType: manifest.install_type,
738
845
  agent: manifest.agent,
@@ -740,56 +847,62 @@ class Installer {
740
847
  ides: newConfig?.ides || manifest.ides_setup || [],
741
848
  };
742
849
 
743
- await this.performFreshInstall(config, installDir, spinner, { isUpdate: true });
850
+ await this.performFreshInstall(config, installDir, spinner, {
851
+ isUpdate: true,
852
+ });
744
853
 
745
854
  // Clean up .yml files that now have .yaml counterparts
746
- spinner.text = 'Cleaning up legacy .yml files...';
855
+ spinner.text = "Cleaning up legacy .yml files...";
747
856
  await this.cleanupLegacyYmlFiles(installDir, spinner);
748
857
  } catch (error) {
749
- spinner.fail('Update failed');
858
+ spinner.fail("Update failed");
750
859
  throw error;
751
860
  }
752
861
  }
753
862
 
754
863
  async performRepair(config, installDir, manifest, integrity, spinner) {
755
- spinner.start('Preparing to repair installation...');
864
+ spinner.start("Preparing to repair installation...");
756
865
 
757
866
  try {
758
867
  // Back up modified files
759
868
  if (integrity.modified.length > 0) {
760
- spinner.text = 'Backing up modified files...';
869
+ spinner.text = "Backing up modified files...";
761
870
  for (const file of integrity.modified) {
762
871
  const filePath = path.join(installDir, file);
763
872
  if (await fileManager.pathExists(filePath)) {
764
873
  const backupPath = await fileManager.backupFile(filePath);
765
- console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
874
+ console.log(
875
+ chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`),
876
+ );
766
877
  }
767
878
  }
768
879
  }
769
880
 
770
881
  // Restore missing and modified files
771
- spinner.text = 'Restoring files...';
882
+ spinner.text = "Restoring files...";
772
883
  const sourceBase = resourceLocator.getBmadCorePath();
773
884
  const filesToRestore = [...integrity.missing, ...integrity.modified];
774
885
 
775
886
  for (const file of filesToRestore) {
776
887
  // Skip the manifest file itself
777
- if (file.endsWith('install-manifest.yaml')) continue;
888
+ if (file.endsWith("install-manifest.yaml")) continue;
778
889
 
779
- const relativePath = file.replace('.xiaoma-core/', '');
890
+ const relativePath = file.replace(".xiaoma-core/", "");
780
891
  const destinationPath = path.join(installDir, file);
781
892
 
782
893
  // Check if this is a common/ file that needs special processing
783
- const commonBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
784
- const commonSourcePath = path.join(commonBase, 'common', relativePath);
894
+ const commonBase = path.dirname(
895
+ path.dirname(path.dirname(path.dirname(__filename))),
896
+ );
897
+ const commonSourcePath = path.join(commonBase, "common", relativePath);
785
898
 
786
899
  if (await fileManager.pathExists(commonSourcePath)) {
787
900
  // This is a common/ file - needs template processing
788
- const fs = require('node:fs').promises;
789
- const content = await fs.readFile(commonSourcePath, 'utf8');
790
- const updatedContent = content.replaceAll('{root}', '.xiaoma-core');
901
+ const fs = require("node:fs").promises;
902
+ const content = await fs.readFile(commonSourcePath, "utf8");
903
+ const updatedContent = content.replaceAll("{root}", ".xiaoma-core");
791
904
  await fileManager.ensureDirectory(path.dirname(destinationPath));
792
- await fs.writeFile(destinationPath, updatedContent, 'utf8');
905
+ await fs.writeFile(destinationPath, updatedContent, "utf8");
793
906
  spinner.text = `Restored: ${file}`;
794
907
  } else {
795
908
  // Regular file from xiaoma-core
@@ -799,95 +912,120 @@ class Installer {
799
912
  spinner.text = `Restored: ${file}`;
800
913
 
801
914
  // If this is a .yaml file, check for and remove corresponding .yml file
802
- if (file.endsWith('.yaml')) {
803
- const ymlFile = file.replace(/\.yaml$/, '.yml');
915
+ if (file.endsWith(".yaml")) {
916
+ const ymlFile = file.replace(/\.yaml$/, ".yml");
804
917
  const ymlPath = path.join(installDir, ymlFile);
805
918
  if (await fileManager.pathExists(ymlPath)) {
806
- const fs = require('node:fs').promises;
919
+ const fs = require("node:fs").promises;
807
920
  await fs.unlink(ymlPath);
808
- console.log(chalk.dim(` Removed legacy: ${ymlFile} (replaced by ${file})`));
921
+ console.log(
922
+ chalk.dim(
923
+ ` Removed legacy: ${ymlFile} (replaced by ${file})`,
924
+ ),
925
+ );
809
926
  }
810
927
  }
811
928
  } else {
812
- console.warn(chalk.yellow(` Warning: Source file not found: ${file}`));
929
+ console.warn(
930
+ chalk.yellow(` Warning: Source file not found: ${file}`),
931
+ );
813
932
  }
814
933
  }
815
934
  }
816
935
 
817
936
  // Clean up .yml files that now have .yaml counterparts
818
- spinner.text = 'Cleaning up legacy .yml files...';
937
+ spinner.text = "Cleaning up legacy .yml files...";
819
938
  await this.cleanupLegacyYmlFiles(installDir, spinner);
820
939
 
821
- spinner.succeed('Repair completed successfully!');
940
+ spinner.succeed("Repair completed successfully!");
822
941
 
823
942
  // Show summary
824
- console.log(chalk.green('\n✓ Installation repaired!'));
943
+ console.log(chalk.green("\n✓ Installation repaired!"));
825
944
  if (integrity.missing.length > 0) {
826
- console.log(chalk.green(` Restored ${integrity.missing.length} missing files`));
945
+ console.log(
946
+ chalk.green(` Restored ${integrity.missing.length} missing files`),
947
+ );
827
948
  }
828
949
  if (integrity.modified.length > 0) {
829
950
  console.log(
830
- chalk.green(` Restored ${integrity.modified.length} modified files (backups created)`),
951
+ chalk.green(
952
+ ` Restored ${integrity.modified.length} modified files (backups created)`,
953
+ ),
831
954
  );
832
955
  }
833
956
 
834
957
  // Warning for Cursor custom modes if agents were repaired
835
958
  const ides = manifest.ides_setup || [];
836
- if (ides.includes('cursor')) {
837
- console.log(chalk.yellow.bold('\n⚠️ IMPORTANT: Cursor Custom Modes Update Required'));
959
+ if (ides.includes("cursor")) {
960
+ console.log(
961
+ chalk.yellow.bold(
962
+ "\n⚠️ IMPORTANT: Cursor Custom Modes Update Required",
963
+ ),
964
+ );
838
965
  console.log(
839
966
  chalk.yellow(
840
- 'Since agent files have been repaired, you need to update any custom agent modes configured in the Cursor custom agent GUI per the Cursor docs.',
967
+ "Since agent files have been repaired, you need to update any custom agent modes configured in the Cursor custom agent GUI per the Cursor docs.",
841
968
  ),
842
969
  );
843
970
  }
844
971
  } catch (error) {
845
- spinner.fail('Repair failed');
972
+ spinner.fail("Repair failed");
846
973
  throw error;
847
974
  }
848
975
  }
849
976
 
850
977
  async performReinstall(config, installDir, spinner) {
851
- spinner.start('Preparing to reinstall XiaoMa-Cli...');
978
+ spinner.start("Preparing to reinstall XiaoMa-Cli...");
852
979
 
853
980
  // Remove existing .xiaoma-core
854
- const bmadCorePath = path.join(installDir, '.xiaoma-core');
981
+ const bmadCorePath = path.join(installDir, ".xiaoma-core");
855
982
  if (await fileManager.pathExists(bmadCorePath)) {
856
- spinner.text = 'Removing existing installation...';
983
+ spinner.text = "Removing existing installation...";
857
984
  await fileManager.removeDirectory(bmadCorePath);
858
985
  }
859
986
 
860
- spinner.text = 'Installing fresh copy...';
861
- const result = await this.performFreshInstall(config, installDir, spinner, { isUpdate: true });
987
+ spinner.text = "Installing fresh copy...";
988
+ const result = await this.performFreshInstall(config, installDir, spinner, {
989
+ isUpdate: true,
990
+ });
862
991
 
863
992
  // Clean up .yml files that now have .yaml counterparts
864
- spinner.text = 'Cleaning up legacy .yml files...';
993
+ spinner.text = "Cleaning up legacy .yml files...";
865
994
  await this.cleanupLegacyYmlFiles(installDir, spinner);
866
995
 
867
996
  return result;
868
997
  }
869
998
 
870
999
  showSuccessMessage(config, installDir, options = {}) {
871
- console.log(chalk.green('\n✓ XiaoMa-Cli installed successfully!\n'));
1000
+ console.log(chalk.green("\n✓ XiaoMa-Cli installed successfully!\n"));
872
1001
 
873
1002
  const ides = config.ides || (config.ide ? [config.ide] : []);
874
1003
  if (ides.length > 0) {
875
1004
  for (const ide of ides) {
876
1005
  const ideConfig = configLoader.getIdeConfiguration(ide);
877
1006
  if (ideConfig?.instructions) {
878
- console.log(chalk.bold(`To use XiaoMa-Cli agents in ${ideConfig.name}:`));
1007
+ console.log(
1008
+ chalk.bold(`To use XiaoMa-Cli agents in ${ideConfig.name}:`),
1009
+ );
879
1010
  console.log(ideConfig.instructions);
880
1011
  }
881
1012
  }
882
1013
  } else {
883
- console.log(chalk.yellow('No IDE configuration was set up.'));
884
- console.log('You can manually configure your IDE using the agent files in:', installDir);
1014
+ console.log(chalk.yellow("No IDE configuration was set up."));
1015
+ console.log(
1016
+ "You can manually configure your IDE using the agent files in:",
1017
+ installDir,
1018
+ );
885
1019
  }
886
1020
 
887
1021
  // Information about installation components
888
- console.log(chalk.bold('\n🎯 Installation Summary:'));
889
- if (config.installType !== 'expansion-only') {
890
- console.log(chalk.green('✓ .xiaoma-core framework installed with all agents and workflows'));
1022
+ console.log(chalk.bold("\n🎯 Installation Summary:"));
1023
+ if (config.installType !== "expansion-only") {
1024
+ console.log(
1025
+ chalk.green(
1026
+ "✓ .xiaoma-core framework installed with all agents and workflows",
1027
+ ),
1028
+ );
891
1029
  }
892
1030
 
893
1031
  if (config.expansionPacks && config.expansionPacks.length > 0) {
@@ -900,12 +1038,15 @@ class Installer {
900
1038
  if (config.includeWebBundles && config.webBundlesDirectory) {
901
1039
  const bundleInfo = this.getWebBundleInfo(config);
902
1040
  // Resolve the web bundles directory for display
903
- const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
1041
+ const originalCwd =
1042
+ process.env.INIT_CWD || process.env.PWD || process.cwd();
904
1043
  const resolvedWebBundlesDir = path.isAbsolute(config.webBundlesDirectory)
905
1044
  ? config.webBundlesDirectory
906
1045
  : path.resolve(originalCwd, config.webBundlesDirectory);
907
1046
  console.log(
908
- chalk.green(`✓ Web bundles (${bundleInfo}) installed to: ${resolvedWebBundlesDir}`),
1047
+ chalk.green(
1048
+ `✓ Web bundles (${bundleInfo}) installed to: ${resolvedWebBundlesDir}`,
1049
+ ),
909
1050
  );
910
1051
  }
911
1052
 
@@ -915,30 +1056,48 @@ class Installer {
915
1056
  const ideConfig = configLoader.getIdeConfiguration(ide);
916
1057
  return ideConfig?.name || ide;
917
1058
  })
918
- .join(', ');
919
- console.log(chalk.green(`✓ IDE rules and configurations set up for: ${ideNames}`));
1059
+ .join(", ");
1060
+ console.log(
1061
+ chalk.green(`✓ IDE rules and configurations set up for: ${ideNames}`),
1062
+ );
920
1063
  }
921
1064
 
922
1065
  // Information about web bundles
923
1066
  if (!config.includeWebBundles) {
924
- console.log(chalk.bold('\n📦 Web Bundles Available:'));
925
- console.log('Pre-built web bundles are available and can be added later:');
926
- console.log(chalk.cyan(' Run the installer again to add them to your project'));
927
- console.log('These bundles work independently and can be shared, moved, or used');
928
- console.log('in other projects as standalone files.');
1067
+ console.log(chalk.bold("\n📦 Web Bundles Available:"));
1068
+ console.log(
1069
+ "Pre-built web bundles are available and can be added later:",
1070
+ );
1071
+ console.log(
1072
+ chalk.cyan(" Run the installer again to add them to your project"),
1073
+ );
1074
+ console.log(
1075
+ "These bundles work independently and can be shared, moved, or used",
1076
+ );
1077
+ console.log("in other projects as standalone files.");
929
1078
  }
930
1079
 
931
- if (config.installType === 'single-agent') {
932
- console.log(chalk.dim('\nNeed other agents? Run: npx xiaoma-cli install --agent=<name>'));
933
- console.log(chalk.dim('Need everything? Run: npx xiaoma-cli install --full'));
1080
+ if (config.installType === "single-agent") {
1081
+ console.log(
1082
+ chalk.dim(
1083
+ "\nNeed other agents? Run: npx xiaoma-cli install --agent=<name>",
1084
+ ),
1085
+ );
1086
+ console.log(
1087
+ chalk.dim("Need everything? Run: npx xiaoma-cli install --full"),
1088
+ );
934
1089
  }
935
1090
 
936
1091
  // Warning for Cursor custom modes if agents were updated
937
- if (options.isUpdate && ides.includes('cursor')) {
938
- console.log(chalk.yellow.bold('\n⚠️ IMPORTANT: Cursor Custom Modes Update Required'));
1092
+ if (options.isUpdate && ides.includes("cursor")) {
1093
+ console.log(
1094
+ chalk.yellow.bold(
1095
+ "\n⚠️ IMPORTANT: Cursor Custom Modes Update Required",
1096
+ ),
1097
+ );
939
1098
  console.log(
940
1099
  chalk.yellow(
941
- 'Since agents have been updated, you need to update any custom agent modes configured in the Cursor custom agent GUI per the Cursor docs.',
1100
+ "Since agents have been updated, you need to update any custom agent modes configured in the Cursor custom agent GUI per the Cursor docs.",
942
1101
  ),
943
1102
  );
944
1103
  }
@@ -946,12 +1105,12 @@ class Installer {
946
1105
  // Important notice to read the user guide
947
1106
  console.log(
948
1107
  chalk.red.bold(
949
- '\n📖 IMPORTANT: Please read the user guide at docs/user-guide.md (also installed at .xiaoma-core/user-guide.md)',
1108
+ "\n📖 IMPORTANT: Please read the user guide at docs/user-guide.md (also installed at .xiaoma-core/user-guide.md)",
950
1109
  ),
951
1110
  );
952
1111
  console.log(
953
1112
  chalk.red(
954
- 'This guide contains essential information about the XiaoMa-Cli workflow and how to use the agents effectively.',
1113
+ "This guide contains essential information about the XiaoMa-Cli workflow and how to use the agents effectively.",
955
1114
  ),
956
1115
  );
957
1116
  }
@@ -966,68 +1125,81 @@ class Installer {
966
1125
  const installDir = await this.findInstallation();
967
1126
  if (installDir) {
968
1127
  const config = {
969
- installType: 'full',
1128
+ installType: "full",
970
1129
  directory: path.dirname(installDir),
971
1130
  ide: null,
972
1131
  };
973
1132
  return await this.install(config);
974
1133
  }
975
- console.log(chalk.red('No XiaoMa installation found.'));
1134
+ console.log(chalk.red("No XiaoMa installation found."));
976
1135
  }
977
1136
 
978
1137
  async listAgents() {
979
1138
  const agents = await resourceLocator.getAvailableAgents();
980
1139
 
981
- console.log(chalk.bold('\nAvailable XiaoMa Agents:\n'));
1140
+ console.log(chalk.bold("\nAvailable XiaoMa Agents:\n"));
982
1141
 
983
1142
  for (const agent of agents) {
984
1143
  console.log(chalk.cyan(` ${agent.id.padEnd(20)}`), agent.description);
985
1144
  }
986
1145
 
987
- console.log(chalk.dim('\nInstall with: npx xiaoma-cli install --agent=<id>\n'));
1146
+ console.log(
1147
+ chalk.dim("\nInstall with: npx xiaoma-cli install --agent=<id>\n"),
1148
+ );
988
1149
  }
989
1150
 
990
1151
  async listExpansionPacks() {
991
1152
  const expansionPacks = await resourceLocator.getExpansionPacks();
992
1153
 
993
- console.log(chalk.bold('\nAvailable XiaoMa Expansion Packs:\n'));
1154
+ console.log(chalk.bold("\nAvailable XiaoMa Expansion Packs:\n"));
994
1155
 
995
1156
  if (expansionPacks.length === 0) {
996
- console.log(chalk.yellow('No expansion packs found.'));
1157
+ console.log(chalk.yellow("No expansion packs found."));
997
1158
  return;
998
1159
  }
999
1160
 
1000
1161
  for (const pack of expansionPacks) {
1001
- console.log(chalk.cyan(` ${pack.id.padEnd(20)}`), `${pack.name} v${pack.version}`);
1002
- console.log(chalk.dim(` ${' '.repeat(22)}${pack.description}`));
1003
- if (pack.author && pack.author !== 'Unknown') {
1004
- console.log(chalk.dim(` ${' '.repeat(22)}by ${pack.author}`));
1162
+ console.log(
1163
+ chalk.cyan(` ${pack.id.padEnd(20)}`),
1164
+ `${pack.name} v${pack.version}`,
1165
+ );
1166
+ console.log(chalk.dim(` ${" ".repeat(22)}${pack.description}`));
1167
+ if (pack.author && pack.author !== "Unknown") {
1168
+ console.log(chalk.dim(` ${" ".repeat(22)}by ${pack.author}`));
1005
1169
  }
1006
1170
  console.log();
1007
1171
  }
1008
1172
 
1009
- console.log(chalk.dim('Install with: npx xiaoma-cli install --full --expansion-packs <id>\n'));
1173
+ console.log(
1174
+ chalk.dim(
1175
+ "Install with: npx xiaoma-cli install --full --expansion-packs <id>\n",
1176
+ ),
1177
+ );
1010
1178
  }
1011
1179
 
1012
1180
  async showStatus() {
1013
1181
  const installDir = await this.findInstallation();
1014
1182
 
1015
1183
  if (!installDir) {
1016
- console.log(chalk.yellow('No XiaoMa installation found in current directory tree'));
1184
+ console.log(
1185
+ chalk.yellow("No XiaoMa installation found in current directory tree"),
1186
+ );
1017
1187
  return;
1018
1188
  }
1019
1189
 
1020
1190
  const manifest = await fileManager.readManifest(installDir);
1021
1191
 
1022
1192
  if (!manifest) {
1023
- console.log(chalk.red('Invalid installation - manifest not found'));
1193
+ console.log(chalk.red("Invalid installation - manifest not found"));
1024
1194
  return;
1025
1195
  }
1026
1196
 
1027
- console.log(chalk.bold('\nXiaoMa-Cli Installation Status:\n'));
1197
+ console.log(chalk.bold("\nXiaoMa-Cli Installation Status:\n"));
1028
1198
  console.log(` Directory: ${installDir}`);
1029
1199
  console.log(` Version: ${manifest.version}`);
1030
- console.log(` Installed: ${new Date(manifest.installed_at).toLocaleDateString()}`);
1200
+ console.log(
1201
+ ` Installed: ${new Date(manifest.installed_at).toLocaleDateString()}`,
1202
+ );
1031
1203
  console.log(` Type: ${manifest.install_type}`);
1032
1204
 
1033
1205
  if (manifest.agent) {
@@ -1035,18 +1207,21 @@ class Installer {
1035
1207
  }
1036
1208
 
1037
1209
  if (manifest.ides_setup && manifest.ides_setup.length > 0) {
1038
- console.log(` IDE Setup: ${manifest.ides_setup.join(', ')}`);
1210
+ console.log(` IDE Setup: ${manifest.ides_setup.join(", ")}`);
1039
1211
  }
1040
1212
 
1041
1213
  console.log(` Total Files: ${manifest.files.length}`);
1042
1214
 
1043
1215
  // Check for modifications
1044
- const modifiedFiles = await fileManager.checkModifiedFiles(installDir, manifest);
1216
+ const modifiedFiles = await fileManager.checkModifiedFiles(
1217
+ installDir,
1218
+ manifest,
1219
+ );
1045
1220
  if (modifiedFiles.length > 0) {
1046
1221
  console.log(chalk.yellow(` Modified Files: ${modifiedFiles.length}`));
1047
1222
  }
1048
1223
 
1049
- console.log('');
1224
+ console.log("");
1050
1225
  }
1051
1226
 
1052
1227
  async getAvailableAgents() {
@@ -1082,74 +1257,105 @@ class Installer {
1082
1257
 
1083
1258
  // Check if expansion pack already exists
1084
1259
  let expansionDotFolder = path.join(installDir, `.${packId}`);
1085
- const existingManifestPath = path.join(expansionDotFolder, 'install-manifest.yaml');
1260
+ const existingManifestPath = path.join(
1261
+ expansionDotFolder,
1262
+ "install-manifest.yaml",
1263
+ );
1086
1264
 
1087
1265
  if (await fileManager.pathExists(existingManifestPath)) {
1088
1266
  spinner.stop();
1089
- const existingManifest = await fileManager.readExpansionPackManifest(installDir, packId);
1267
+ const existingManifest = await fileManager.readExpansionPackManifest(
1268
+ installDir,
1269
+ packId,
1270
+ );
1090
1271
 
1091
- console.log(chalk.yellow(`\n🔍 Found existing ${pack.name} installation`));
1092
- console.log(` Current version: ${existingManifest.version || 'unknown'}`);
1272
+ console.log(
1273
+ chalk.yellow(`\n🔍 Found existing ${pack.name} installation`),
1274
+ );
1275
+ console.log(
1276
+ ` Current version: ${existingManifest.version || "unknown"}`,
1277
+ );
1093
1278
  console.log(` New version: ${pack.version}`);
1094
1279
 
1095
1280
  // Check integrity of existing expansion pack
1096
- const packIntegrity = await fileManager.checkFileIntegrity(installDir, existingManifest);
1281
+ const packIntegrity = await fileManager.checkFileIntegrity(
1282
+ installDir,
1283
+ existingManifest,
1284
+ );
1097
1285
  const hasPackIntegrityIssues =
1098
- packIntegrity.missing.length > 0 || packIntegrity.modified.length > 0;
1286
+ packIntegrity.missing.length > 0 ||
1287
+ packIntegrity.modified.length > 0;
1099
1288
 
1100
1289
  if (hasPackIntegrityIssues) {
1101
- console.log(chalk.red(' ⚠️ Installation issues detected:'));
1290
+ console.log(chalk.red(" ⚠️ Installation issues detected:"));
1102
1291
  if (packIntegrity.missing.length > 0) {
1103
- console.log(chalk.red(` Missing files: ${packIntegrity.missing.length}`));
1292
+ console.log(
1293
+ chalk.red(
1294
+ ` Missing files: ${packIntegrity.missing.length}`,
1295
+ ),
1296
+ );
1104
1297
  }
1105
1298
  if (packIntegrity.modified.length > 0) {
1106
- console.log(chalk.yellow(` Modified files: ${packIntegrity.modified.length}`));
1299
+ console.log(
1300
+ chalk.yellow(
1301
+ ` Modified files: ${packIntegrity.modified.length}`,
1302
+ ),
1303
+ );
1107
1304
  }
1108
1305
  }
1109
1306
 
1110
1307
  const versionCompare = this.compareVersions(
1111
- existingManifest.version || '0.0.0',
1308
+ existingManifest.version || "0.0.0",
1112
1309
  pack.version,
1113
1310
  );
1114
1311
 
1115
1312
  if (versionCompare === 0) {
1116
- console.log(chalk.yellow(' ⚠️ Same version already installed'));
1313
+ console.log(chalk.yellow(" ⚠️ Same version already installed"));
1117
1314
 
1118
1315
  const choices = [];
1119
1316
  if (hasPackIntegrityIssues) {
1120
- choices.push({ name: 'Repair (restore missing/modified files)', value: 'repair' });
1317
+ choices.push({
1318
+ name: "Repair (restore missing/modified files)",
1319
+ value: "repair",
1320
+ });
1121
1321
  }
1122
1322
  choices.push(
1123
- { name: 'Force reinstall (overwrite)', value: 'overwrite' },
1124
- { name: 'Skip this expansion pack', value: 'skip' },
1125
- { name: 'Cancel installation', value: 'cancel' },
1323
+ { name: "Force reinstall (overwrite)", value: "overwrite" },
1324
+ { name: "Skip this expansion pack", value: "skip" },
1325
+ { name: "Cancel installation", value: "cancel" },
1126
1326
  );
1127
1327
 
1128
1328
  const { action } = await inquirer.prompt([
1129
1329
  {
1130
- type: 'list',
1131
- name: 'action',
1330
+ type: "list",
1331
+ name: "action",
1132
1332
  message: `${pack.name} v${pack.version} is already installed. What would you like to do?`,
1133
1333
  choices: choices,
1134
1334
  },
1135
1335
  ]);
1136
1336
 
1137
1337
  switch (action) {
1138
- case 'skip': {
1338
+ case "skip": {
1139
1339
  spinner.start();
1140
1340
  continue;
1141
1341
 
1142
1342
  break;
1143
1343
  }
1144
- case 'cancel': {
1145
- console.log('Installation cancelled.');
1344
+ case "cancel": {
1345
+ console.log("Installation cancelled.");
1146
1346
  process.exit(0);
1147
1347
 
1148
1348
  break;
1149
1349
  }
1150
- case 'repair': {
1350
+ case "repair": {
1151
1351
  // Repair the expansion pack
1152
- await this.repairExpansionPack(installDir, packId, pack, packIntegrity, spinner);
1352
+ await this.repairExpansionPack(
1353
+ installDir,
1354
+ packId,
1355
+ pack,
1356
+ packIntegrity,
1357
+ spinner,
1358
+ );
1153
1359
  continue;
1154
1360
 
1155
1361
  break;
@@ -1157,12 +1363,12 @@ class Installer {
1157
1363
  // No default
1158
1364
  }
1159
1365
  } else if (versionCompare < 0) {
1160
- console.log(chalk.cyan(' ⬆️ Upgrade available'));
1366
+ console.log(chalk.cyan(" ⬆️ Upgrade available"));
1161
1367
 
1162
1368
  const { proceed } = await inquirer.prompt([
1163
1369
  {
1164
- type: 'confirm',
1165
- name: 'proceed',
1370
+ type: "confirm",
1371
+ name: "proceed",
1166
1372
  message: `Upgrade ${pack.name} from v${existingManifest.version} to v${pack.version}?`,
1167
1373
  default: true,
1168
1374
  },
@@ -1173,26 +1379,33 @@ class Installer {
1173
1379
  continue;
1174
1380
  }
1175
1381
  } else {
1176
- console.log(chalk.yellow(' ⬇️ Installed version is newer than available version'));
1382
+ console.log(
1383
+ chalk.yellow(
1384
+ " ⬇️ Installed version is newer than available version",
1385
+ ),
1386
+ );
1177
1387
 
1178
1388
  const { action } = await inquirer.prompt([
1179
1389
  {
1180
- type: 'list',
1181
- name: 'action',
1182
- message: 'What would you like to do?',
1390
+ type: "list",
1391
+ name: "action",
1392
+ message: "What would you like to do?",
1183
1393
  choices: [
1184
- { name: 'Keep current version', value: 'skip' },
1185
- { name: 'Downgrade to available version', value: 'downgrade' },
1186
- { name: 'Cancel installation', value: 'cancel' },
1394
+ { name: "Keep current version", value: "skip" },
1395
+ {
1396
+ name: "Downgrade to available version",
1397
+ value: "downgrade",
1398
+ },
1399
+ { name: "Cancel installation", value: "cancel" },
1187
1400
  ],
1188
1401
  },
1189
1402
  ]);
1190
1403
 
1191
- if (action === 'skip') {
1404
+ if (action === "skip") {
1192
1405
  spinner.start();
1193
1406
  continue;
1194
- } else if (action === 'cancel') {
1195
- console.log('Installation cancelled.');
1407
+ } else if (action === "cancel") {
1408
+ console.log("Installation cancelled.");
1196
1409
  process.exit(0);
1197
1410
  }
1198
1411
  }
@@ -1210,15 +1423,15 @@ class Installer {
1210
1423
 
1211
1424
  // Define the folders to copy from expansion packs
1212
1425
  const foldersToSync = [
1213
- 'agents',
1214
- 'agent-teams',
1215
- 'templates',
1216
- 'tasks',
1217
- 'checklists',
1218
- 'workflows',
1219
- 'data',
1220
- 'utils',
1221
- 'schemas',
1426
+ "agents",
1427
+ "agent-teams",
1428
+ "templates",
1429
+ "tasks",
1430
+ "checklists",
1431
+ "workflows",
1432
+ "data",
1433
+ "utils",
1434
+ "schemas",
1222
1435
  ];
1223
1436
 
1224
1437
  // Copy each folder if it exists
@@ -1228,7 +1441,7 @@ class Installer {
1228
1441
  // Check if folder exists in expansion pack
1229
1442
  if (await fileManager.pathExists(sourceFolder)) {
1230
1443
  // Get all files in this folder
1231
- const files = await resourceLocator.findFiles('**/*', {
1444
+ const files = await resourceLocator.findFiles("**/*", {
1232
1445
  cwd: sourceFolder,
1233
1446
  nodir: true,
1234
1447
  });
@@ -1236,14 +1449,24 @@ class Installer {
1236
1449
  // Copy each file to the expansion pack's dot folder with {root} replacement
1237
1450
  for (const file of files) {
1238
1451
  const sourcePath = path.join(sourceFolder, file);
1239
- const destinationPath = path.join(expansionDotFolder, folder, file);
1452
+ const destinationPath = path.join(
1453
+ expansionDotFolder,
1454
+ folder,
1455
+ file,
1456
+ );
1240
1457
 
1241
1458
  const needsRootReplacement =
1242
- file.endsWith('.md') || file.endsWith('.yaml') || file.endsWith('.yml');
1459
+ file.endsWith(".md") ||
1460
+ file.endsWith(".yaml") ||
1461
+ file.endsWith(".yml");
1243
1462
  let success = false;
1244
1463
 
1245
1464
  success = await (needsRootReplacement
1246
- ? fileManager.copyFileWithRootReplacement(sourcePath, destinationPath, `.${packId}`)
1465
+ ? fileManager.copyFileWithRootReplacement(
1466
+ sourcePath,
1467
+ destinationPath,
1468
+ `.${packId}`,
1469
+ )
1247
1470
  : fileManager.copyFile(sourcePath, destinationPath));
1248
1471
 
1249
1472
  if (success) {
@@ -1254,9 +1477,12 @@ class Installer {
1254
1477
  }
1255
1478
 
1256
1479
  // Copy config.yaml with {root} replacement
1257
- const configPath = path.join(expansionPackDir, 'config.yaml');
1480
+ const configPath = path.join(expansionPackDir, "config.yaml");
1258
1481
  if (await fileManager.pathExists(configPath)) {
1259
- const configDestinationPath = path.join(expansionDotFolder, 'config.yaml');
1482
+ const configDestinationPath = path.join(
1483
+ expansionDotFolder,
1484
+ "config.yaml",
1485
+ );
1260
1486
  if (
1261
1487
  await fileManager.copyFileWithRootReplacement(
1262
1488
  configPath,
@@ -1264,14 +1490,17 @@ class Installer {
1264
1490
  `.${packId}`,
1265
1491
  )
1266
1492
  ) {
1267
- installedFiles.push(path.join(`.${packId}`, 'config.yaml'));
1493
+ installedFiles.push(path.join(`.${packId}`, "config.yaml"));
1268
1494
  }
1269
1495
  }
1270
1496
 
1271
1497
  // Copy README if it exists with {root} replacement
1272
- const readmePath = path.join(expansionPackDir, 'README.md');
1498
+ const readmePath = path.join(expansionPackDir, "README.md");
1273
1499
  if (await fileManager.pathExists(readmePath)) {
1274
- const readmeDestinationPath = path.join(expansionDotFolder, 'README.md');
1500
+ const readmeDestinationPath = path.join(
1501
+ expansionDotFolder,
1502
+ "README.md",
1503
+ );
1275
1504
  if (
1276
1505
  await fileManager.copyFileWithRootReplacement(
1277
1506
  readmePath,
@@ -1279,7 +1508,7 @@ class Installer {
1279
1508
  `.${packId}`,
1280
1509
  )
1281
1510
  ) {
1282
- installedFiles.push(path.join(`.${packId}`, 'README.md'));
1511
+ installedFiles.push(path.join(`.${packId}`, "README.md"));
1283
1512
  }
1284
1513
  }
1285
1514
 
@@ -1297,12 +1526,17 @@ class Installer {
1297
1526
  );
1298
1527
 
1299
1528
  // Check and resolve core agents referenced by teams
1300
- await this.resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner);
1529
+ await this.resolveExpansionPackCoreAgents(
1530
+ installDir,
1531
+ expansionDotFolder,
1532
+ packId,
1533
+ spinner,
1534
+ );
1301
1535
 
1302
1536
  // Create manifest for this expansion pack
1303
1537
  spinner.text = `Creating manifest for ${packId}...`;
1304
1538
  const expansionConfig = {
1305
- installType: 'expansion-pack',
1539
+ installType: "expansion-pack",
1306
1540
  expansionPackId: packId,
1307
1541
  expansionPackName: pack.name,
1308
1542
  expansionPackVersion: pack.version,
@@ -1310,11 +1544,13 @@ class Installer {
1310
1544
  };
1311
1545
 
1312
1546
  // Get all files installed in this expansion pack
1313
- const foundFiles = await resourceLocator.findFiles('**/*', {
1547
+ const foundFiles = await resourceLocator.findFiles("**/*", {
1314
1548
  cwd: expansionDotFolder,
1315
1549
  nodir: true,
1316
1550
  });
1317
- const expansionPackFiles = foundFiles.map((f) => path.join(`.${packId}`, f));
1551
+ const expansionPackFiles = foundFiles.map((f) =>
1552
+ path.join(`.${packId}`, f),
1553
+ );
1318
1554
 
1319
1555
  await fileManager.createExpansionPackManifest(
1320
1556
  installDir,
@@ -1323,9 +1559,15 @@ class Installer {
1323
1559
  expansionPackFiles,
1324
1560
  );
1325
1561
 
1326
- console.log(chalk.green(`✓ Installed expansion pack: ${pack.name} to ${`.${packId}`}`));
1562
+ console.log(
1563
+ chalk.green(
1564
+ `✓ Installed expansion pack: ${pack.name} to ${`.${packId}`}`,
1565
+ ),
1566
+ );
1327
1567
  } catch (error) {
1328
- console.error(`Failed to install expansion pack ${packId}: ${error.message}`);
1568
+ console.error(
1569
+ `Failed to install expansion pack ${packId}: ${error.message}`,
1570
+ );
1329
1571
  console.error(`Stack trace: ${error.stack}`);
1330
1572
  }
1331
1573
  }
@@ -1340,17 +1582,17 @@ class Installer {
1340
1582
  pack,
1341
1583
  spinner,
1342
1584
  ) {
1343
- const yaml = require('js-yaml');
1344
- const fs = require('node:fs').promises;
1585
+ const yaml = require("js-yaml");
1586
+ const fs = require("node:fs").promises;
1345
1587
 
1346
1588
  // Find all agent files in the expansion pack
1347
- const agentFiles = await resourceLocator.findFiles('agents/*.md', {
1589
+ const agentFiles = await resourceLocator.findFiles("agents/*.md", {
1348
1590
  cwd: expansionDotFolder,
1349
1591
  });
1350
1592
 
1351
1593
  for (const agentFile of agentFiles) {
1352
1594
  const agentPath = path.join(expansionDotFolder, agentFile);
1353
- const agentContent = await fs.readFile(agentPath, 'utf8');
1595
+ const agentContent = await fs.readFile(agentPath, "utf8");
1354
1596
 
1355
1597
  // Extract YAML frontmatter to check dependencies
1356
1598
  const yamlContent = extractYamlFromAgent(agentContent);
@@ -1361,39 +1603,55 @@ class Installer {
1361
1603
 
1362
1604
  // Check for core dependencies (those that don't exist in the expansion pack)
1363
1605
  for (const depType of [
1364
- 'tasks',
1365
- 'templates',
1366
- 'checklists',
1367
- 'workflows',
1368
- 'utils',
1369
- 'data',
1606
+ "tasks",
1607
+ "templates",
1608
+ "checklists",
1609
+ "workflows",
1610
+ "utils",
1611
+ "data",
1370
1612
  ]) {
1371
1613
  const deps = dependencies[depType] || [];
1372
1614
 
1373
1615
  for (const dep of deps) {
1374
1616
  const depFileName =
1375
- dep.endsWith('.md') || dep.endsWith('.yaml')
1617
+ dep.endsWith(".md") || dep.endsWith(".yaml")
1376
1618
  ? dep
1377
- : depType === 'templates'
1619
+ : depType === "templates"
1378
1620
  ? `${dep}.yaml`
1379
1621
  : `${dep}.md`;
1380
- const expansionDepPath = path.join(expansionDotFolder, depType, depFileName);
1622
+ const expansionDepPath = path.join(
1623
+ expansionDotFolder,
1624
+ depType,
1625
+ depFileName,
1626
+ );
1381
1627
 
1382
1628
  // Check if dependency exists in expansion pack dot folder
1383
1629
  if (!(await fileManager.pathExists(expansionDepPath))) {
1384
1630
  // Try to find it in expansion pack source
1385
- const sourceDepPath = path.join(pack.path, depType, depFileName);
1631
+ const sourceDepPath = path.join(
1632
+ pack.path,
1633
+ depType,
1634
+ depFileName,
1635
+ );
1386
1636
 
1387
1637
  if (await fileManager.pathExists(sourceDepPath)) {
1388
1638
  // Copy from expansion pack source
1389
1639
  spinner.text = `Copying ${packId} dependency ${dep}...`;
1390
- const destinationPath = path.join(expansionDotFolder, depType, depFileName);
1640
+ const destinationPath = path.join(
1641
+ expansionDotFolder,
1642
+ depType,
1643
+ depFileName,
1644
+ );
1391
1645
  await fileManager.copyFileWithRootReplacement(
1392
1646
  sourceDepPath,
1393
1647
  destinationPath,
1394
1648
  `.${packId}`,
1395
1649
  );
1396
- console.log(chalk.dim(` Added ${packId} dependency: ${depType}/${depFileName}`));
1650
+ console.log(
1651
+ chalk.dim(
1652
+ ` Added ${packId} dependency: ${depType}/${depFileName}`,
1653
+ ),
1654
+ );
1397
1655
  } else {
1398
1656
  // Try to find it in core
1399
1657
  const coreDepPath = path.join(
@@ -1406,14 +1664,22 @@ class Installer {
1406
1664
  spinner.text = `Copying core dependency ${dep} for ${packId}...`;
1407
1665
 
1408
1666
  // Copy from core to expansion pack dot folder with {root} replacement
1409
- const destinationPath = path.join(expansionDotFolder, depType, depFileName);
1667
+ const destinationPath = path.join(
1668
+ expansionDotFolder,
1669
+ depType,
1670
+ depFileName,
1671
+ );
1410
1672
  await fileManager.copyFileWithRootReplacement(
1411
1673
  coreDepPath,
1412
1674
  destinationPath,
1413
1675
  `.${packId}`,
1414
1676
  );
1415
1677
 
1416
- console.log(chalk.dim(` Added core dependency: ${depType}/${depFileName}`));
1678
+ console.log(
1679
+ chalk.dim(
1680
+ ` Added core dependency: ${depType}/${depFileName}`,
1681
+ ),
1682
+ );
1417
1683
  } else {
1418
1684
  console.warn(
1419
1685
  chalk.yellow(
@@ -1426,43 +1692,50 @@ class Installer {
1426
1692
  }
1427
1693
  }
1428
1694
  } catch (error) {
1429
- console.warn(` Warning: Could not parse agent dependencies: ${error.message}`);
1695
+ console.warn(
1696
+ ` Warning: Could not parse agent dependencies: ${error.message}`,
1697
+ );
1430
1698
  }
1431
1699
  }
1432
1700
  }
1433
1701
  }
1434
1702
 
1435
- async resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner) {
1436
- const yaml = require('js-yaml');
1437
- const fs = require('node:fs').promises;
1703
+ async resolveExpansionPackCoreAgents(
1704
+ installDir,
1705
+ expansionDotFolder,
1706
+ packId,
1707
+ spinner,
1708
+ ) {
1709
+ const yaml = require("js-yaml");
1710
+ const fs = require("node:fs").promises;
1438
1711
 
1439
1712
  // Find all team files in the expansion pack
1440
- const teamFiles = await resourceLocator.findFiles('agent-teams/*.yaml', {
1713
+ const teamFiles = await resourceLocator.findFiles("agent-teams/*.yaml", {
1441
1714
  cwd: expansionDotFolder,
1442
1715
  });
1443
1716
 
1444
1717
  // Also get existing agents in the expansion pack
1445
1718
  const existingAgents = new Set();
1446
- const agentFiles = await resourceLocator.findFiles('agents/*.md', {
1719
+ const agentFiles = await resourceLocator.findFiles("agents/*.md", {
1447
1720
  cwd: expansionDotFolder,
1448
1721
  });
1449
1722
  for (const agentFile of agentFiles) {
1450
- const agentName = path.basename(agentFile, '.md');
1723
+ const agentName = path.basename(agentFile, ".md");
1451
1724
  existingAgents.add(agentName);
1452
1725
  }
1453
1726
 
1454
1727
  // Process each team file
1455
1728
  for (const teamFile of teamFiles) {
1456
1729
  const teamPath = path.join(expansionDotFolder, teamFile);
1457
- const teamContent = await fs.readFile(teamPath, 'utf8');
1730
+ const teamContent = await fs.readFile(teamPath, "utf8");
1458
1731
 
1459
1732
  try {
1460
1733
  const teamConfig = yaml.load(teamContent);
1461
1734
  const agents = teamConfig.agents || [];
1462
1735
 
1463
1736
  // Add bmad-orchestrator if not present (required for all teams)
1464
- if (!agents.includes('bmad-orchestrator')) {
1465
- agents.unshift('bmad-orchestrator');
1737
+ if (!agents.includes("bmad-orchestrator")) {
1738
+ agents.unshift("bmad-orchestrator");
1466
1739
  }
1467
1740
 
1468
1741
  // Check each agent in the team
@@ -1471,7 +1744,7 @@ class Installer {
1471
1744
  // Agent not in expansion pack, try to get from core
1472
1745
  const coreAgentPath = path.join(
1473
1746
  resourceLocator.getBmadCorePath(),
1474
- 'agents',
1747
+ "agents",
1475
1748
  `${agentId}.md`,
1476
1749
  );
1477
1750
 
@@ -1479,7 +1752,11 @@ class Installer {
1479
1752
  spinner.text = `Copying core agent ${agentId} for ${packId}...`;
1480
1753
 
1481
1754
  // Copy agent file with {root} replacement
1482
- const destinationPath = path.join(expansionDotFolder, 'agents', `${agentId}.md`);
1755
+ const destinationPath = path.join(
1756
+ expansionDotFolder,
1757
+ "agents",
1758
+ `${agentId}.md`,
1759
+ );
1483
1760
  await fileManager.copyFileWithRootReplacement(
1484
1761
  coreAgentPath,
1485
1762
  destinationPath,
@@ -1490,7 +1767,7 @@ class Installer {
1490
1767
  console.log(chalk.dim(` Added core agent: ${agentId}`));
1491
1768
 
1492
1769
  // Now resolve this agent's dependencies too
1493
- const agentContent = await fs.readFile(coreAgentPath, 'utf8');
1770
+ const agentContent = await fs.readFile(coreAgentPath, "utf8");
1494
1771
  const yamlContent = extractYamlFromAgent(agentContent, true);
1495
1772
 
1496
1773
  if (yamlContent) {
@@ -1500,23 +1777,27 @@ class Installer {
1500
1777
 
1501
1778
  // Copy all dependencies for this agent
1502
1779
  for (const depType of [
1503
- 'tasks',
1504
- 'templates',
1505
- 'checklists',
1506
- 'workflows',
1507
- 'utils',
1508
- 'data',
1780
+ "tasks",
1781
+ "templates",
1782
+ "checklists",
1783
+ "workflows",
1784
+ "utils",
1785
+ "data",
1509
1786
  ]) {
1510
1787
  const deps = dependencies[depType] || [];
1511
1788
 
1512
1789
  for (const dep of deps) {
1513
1790
  const depFileName =
1514
- dep.endsWith('.md') || dep.endsWith('.yaml')
1791
+ dep.endsWith(".md") || dep.endsWith(".yaml")
1515
1792
  ? dep
1516
- : depType === 'templates'
1793
+ : depType === "templates"
1517
1794
  ? `${dep}.yaml`
1518
1795
  : `${dep}.md`;
1519
- const expansionDepPath = path.join(expansionDotFolder, depType, depFileName);
1796
+ const expansionDepPath = path.join(
1797
+ expansionDotFolder,
1798
+ depType,
1799
+ depFileName,
1800
+ );
1520
1801
 
1521
1802
  // Check if dependency exists in expansion pack
1522
1803
  if (!(await fileManager.pathExists(expansionDepPath))) {
@@ -1539,16 +1820,20 @@ class Installer {
1539
1820
  `.${packId}`,
1540
1821
  );
1541
1822
  console.log(
1542
- chalk.dim(` Added agent dependency: ${depType}/${depFileName}`),
1823
+ chalk.dim(
1824
+ ` Added agent dependency: ${depType}/${depFileName}`,
1825
+ ),
1543
1826
  );
1544
1827
  } else {
1545
1828
  // Try common folder
1546
1829
  const sourceBase = path.dirname(
1547
- path.dirname(path.dirname(path.dirname(__filename))),
1830
+ path.dirname(
1831
+ path.dirname(path.dirname(__filename)),
1832
+ ),
1548
1833
  ); // Go up to project root
1549
1834
  const commonDepPath = path.join(
1550
1835
  sourceBase,
1551
- 'common',
1836
+ "common",
1552
1837
  depType,
1553
1838
  depFileName,
1554
1839
  );
@@ -1558,7 +1843,10 @@ class Installer {
1558
1843
  depType,
1559
1844
  depFileName,
1560
1845
  );
1561
- await fileManager.copyFile(commonDepPath, destinationDepPath);
1846
+ await fileManager.copyFile(
1847
+ commonDepPath,
1848
+ destinationDepPath,
1849
+ );
1562
1850
  console.log(
1563
1851
  chalk.dim(
1564
1852
  ` Added agent dependency from common: ${depType}/${depFileName}`,
@@ -1578,45 +1866,50 @@ class Installer {
1578
1866
  } else {
1579
1867
  console.warn(
1580
1868
  chalk.yellow(
1581
- ` Warning: Core agent ${agentId} not found for team ${path.basename(teamFile, '.yaml')}`,
1869
+ ` Warning: Core agent ${agentId} not found for team ${path.basename(teamFile, ".yaml")}`,
1582
1870
  ),
1583
1871
  );
1584
1872
  }
1585
1873
  }
1586
1874
  }
1587
1875
  } catch (error) {
1588
- console.warn(` Warning: Could not parse team file ${teamFile}: ${error.message}`);
1876
+ console.warn(
1877
+ ` Warning: Could not parse team file ${teamFile}: ${error.message}`,
1878
+ );
1589
1879
  }
1590
1880
  }
1591
1881
  }
1592
1882
 
1593
1883
  getWebBundleInfo(config) {
1594
- const webBundleType = config.webBundleType || 'all';
1884
+ const webBundleType = config.webBundleType || "all";
1595
1885
 
1596
1886
  switch (webBundleType) {
1597
- case 'all': {
1598
- return 'all bundles';
1887
+ case "all": {
1888
+ return "all bundles";
1599
1889
  }
1600
- case 'agents': {
1601
- return 'individual agents only';
1890
+ case "agents": {
1891
+ return "individual agents only";
1602
1892
  }
1603
- case 'teams': {
1893
+ case "teams": {
1604
1894
  return config.selectedWebBundleTeams
1605
- ? `teams: ${config.selectedWebBundleTeams.join(', ')}`
1606
- : 'selected teams';
1895
+ ? `teams: ${config.selectedWebBundleTeams.join(", ")}`
1896
+ : "selected teams";
1607
1897
  }
1608
- case 'custom': {
1898
+ case "custom": {
1609
1899
  const parts = [];
1610
- if (config.selectedWebBundleTeams && config.selectedWebBundleTeams.length > 0) {
1611
- parts.push(`teams: ${config.selectedWebBundleTeams.join(', ')}`);
1900
+ if (
1901
+ config.selectedWebBundleTeams &&
1902
+ config.selectedWebBundleTeams.length > 0
1903
+ ) {
1904
+ parts.push(`teams: ${config.selectedWebBundleTeams.join(", ")}`);
1612
1905
  }
1613
1906
  if (config.includeIndividualAgents) {
1614
- parts.push('individual agents');
1907
+ parts.push("individual agents");
1615
1908
  }
1616
- return parts.length > 0 ? parts.join(' + ') : 'custom selection';
1909
+ return parts.length > 0 ? parts.join(" + ") : "custom selection";
1617
1910
  }
1618
1911
  default: {
1619
- return 'selected bundles';
1912
+ return "selected bundles";
1620
1913
  }
1621
1914
  }
1622
1915
  }
@@ -1627,29 +1920,33 @@ class Installer {
1627
1920
  const distDir = configLoader.getDistPath();
1628
1921
 
1629
1922
  if (!(await fileManager.pathExists(distDir))) {
1630
- console.warn('Web bundles not found. Run "npm run build" to generate them.');
1923
+ console.warn(
1924
+ 'Web bundles not found. Run "npm run build" to generate them.',
1925
+ );
1631
1926
  return;
1632
1927
  }
1633
1928
 
1634
1929
  // Ensure web bundles directory exists
1635
1930
  await fileManager.ensureDirectory(webBundlesDirectory);
1636
1931
 
1637
- const webBundleType = config.webBundleType || 'all';
1932
+ const webBundleType = config.webBundleType || "all";
1638
1933
 
1639
- if (webBundleType === 'all') {
1934
+ if (webBundleType === "all") {
1640
1935
  // Copy the entire dist directory structure
1641
1936
  await fileManager.copyDirectory(distDir, webBundlesDirectory);
1642
- console.log(chalk.green(`✓ Installed all web bundles to: ${webBundlesDirectory}`));
1937
+ console.log(
1938
+ chalk.green(`✓ Installed all web bundles to: ${webBundlesDirectory}`),
1939
+ );
1643
1940
  } else {
1644
1941
  let copiedCount = 0;
1645
1942
 
1646
1943
  // Copy specific selections based on type
1647
1944
  if (
1648
- webBundleType === 'agents' ||
1649
- (webBundleType === 'custom' && config.includeIndividualAgents)
1945
+ webBundleType === "agents" ||
1946
+ (webBundleType === "custom" && config.includeIndividualAgents)
1650
1947
  ) {
1651
- const agentsSource = path.join(distDir, 'agents');
1652
- const agentsTarget = path.join(webBundlesDirectory, 'agents');
1948
+ const agentsSource = path.join(distDir, "agents");
1949
+ const agentsTarget = path.join(webBundlesDirectory, "agents");
1653
1950
  if (await fileManager.pathExists(agentsSource)) {
1654
1951
  await fileManager.copyDirectory(agentsSource, agentsTarget);
1655
1952
  console.log(chalk.green(`✓ Copied individual agent bundles`));
@@ -1658,12 +1955,12 @@ class Installer {
1658
1955
  }
1659
1956
 
1660
1957
  if (
1661
- (webBundleType === 'teams' || webBundleType === 'custom') &&
1958
+ (webBundleType === "teams" || webBundleType === "custom") &&
1662
1959
  config.selectedWebBundleTeams &&
1663
1960
  config.selectedWebBundleTeams.length > 0
1664
1961
  ) {
1665
- const teamsSource = path.join(distDir, 'teams');
1666
- const teamsTarget = path.join(webBundlesDirectory, 'teams');
1962
+ const teamsSource = path.join(distDir, "teams");
1963
+ const teamsTarget = path.join(webBundlesDirectory, "teams");
1667
1964
  await fileManager.ensureDirectory(teamsTarget);
1668
1965
 
1669
1966
  for (const teamId of config.selectedWebBundleTeams) {
@@ -1680,15 +1977,20 @@ class Installer {
1680
1977
  }
1681
1978
 
1682
1979
  // Always copy expansion packs if they exist
1683
- const expansionSource = path.join(distDir, 'expansion-packs');
1684
- const expansionTarget = path.join(webBundlesDirectory, 'expansion-packs');
1980
+ const expansionSource = path.join(distDir, "expansion-packs");
1981
+ const expansionTarget = path.join(
1982
+ webBundlesDirectory,
1983
+ "expansion-packs",
1984
+ );
1685
1985
  if (await fileManager.pathExists(expansionSource)) {
1686
1986
  await fileManager.copyDirectory(expansionSource, expansionTarget);
1687
1987
  console.log(chalk.green(`✓ Copied expansion pack bundles`));
1688
1988
  }
1689
1989
 
1690
1990
  console.log(
1691
- chalk.green(`✓ Installed ${copiedCount} selected web bundles to: ${webBundlesDirectory}`),
1991
+ chalk.green(
1992
+ `✓ Installed ${copiedCount} selected web bundles to: ${webBundlesDirectory}`,
1993
+ ),
1692
1994
  );
1693
1995
  }
1694
1996
  } catch (error) {
@@ -1697,20 +1999,22 @@ class Installer {
1697
1999
  }
1698
2000
 
1699
2001
  async copyCommonItems(installDir, targetSubdir, spinner) {
1700
- const fs = require('node:fs').promises;
1701
- const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root
1702
- const commonPath = path.join(sourceBase, 'common');
2002
+ const fs = require("node:fs").promises;
2003
+ const sourceBase = path.dirname(
2004
+ path.dirname(path.dirname(path.dirname(__filename))),
2005
+ ); // Go up to project root
2006
+ const commonPath = path.join(sourceBase, "common");
1703
2007
  const targetPath = path.join(installDir, targetSubdir);
1704
2008
  const copiedFiles = [];
1705
2009
 
1706
2010
  // Check if common/ exists
1707
2011
  if (!(await fileManager.pathExists(commonPath))) {
1708
- console.warn('Warning: common/ folder not found');
2012
+ console.warn("Warning: common/ folder not found");
1709
2013
  return copiedFiles;
1710
2014
  }
1711
2015
 
1712
2016
  // Copy all items from common/ to target
1713
- const commonItems = await resourceLocator.findFiles('**/*', {
2017
+ const commonItems = await resourceLocator.findFiles("**/*", {
1714
2018
  cwd: commonPath,
1715
2019
  nodir: true,
1716
2020
  });
@@ -1720,16 +2024,16 @@ class Installer {
1720
2024
  const destinationPath = path.join(targetPath, item);
1721
2025
 
1722
2026
  // Read the file content
1723
- const content = await fs.readFile(sourcePath, 'utf8');
2027
+ const content = await fs.readFile(sourcePath, "utf8");
1724
2028
 
1725
2029
  // Replace {root} with the target subdirectory
1726
- const updatedContent = content.replaceAll('{root}', targetSubdir);
2030
+ const updatedContent = content.replaceAll("{root}", targetSubdir);
1727
2031
 
1728
2032
  // Ensure directory exists
1729
2033
  await fileManager.ensureDirectory(path.dirname(destinationPath));
1730
2034
 
1731
2035
  // Write the updated content
1732
- await fs.writeFile(destinationPath, updatedContent, 'utf8');
2036
+ await fs.writeFile(destinationPath, updatedContent, "utf8");
1733
2037
  copiedFiles.push(path.join(targetSubdir, item));
1734
2038
  }
1735
2039
 
@@ -1738,22 +2042,24 @@ class Installer {
1738
2042
  }
1739
2043
 
1740
2044
  async copyDocsItems(installDir, targetSubdir, spinner) {
1741
- const fs = require('node:fs').promises;
1742
- const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root
1743
- const docsPath = path.join(sourceBase, 'docs');
2045
+ const fs = require("node:fs").promises;
2046
+ const sourceBase = path.dirname(
2047
+ path.dirname(path.dirname(path.dirname(__filename))),
2048
+ ); // Go up to project root
2049
+ const docsPath = path.join(sourceBase, "docs");
1744
2050
  const targetPath = path.join(installDir, targetSubdir);
1745
2051
  const copiedFiles = [];
1746
2052
 
1747
2053
  // Specific documentation files to copy
1748
2054
  const documentFiles = [
1749
- 'enhanced-ide-development-workflow.md',
1750
- 'user-guide.md',
1751
- 'working-in-the-brownfield.md',
2055
+ "enhanced-ide-development-workflow.md",
2056
+ "user-guide.md",
2057
+ "working-in-the-brownfield.md",
1752
2058
  ];
1753
2059
 
1754
2060
  // Check if docs/ exists
1755
2061
  if (!(await fileManager.pathExists(docsPath))) {
1756
- console.warn('Warning: docs/ folder not found');
2062
+ console.warn("Warning: docs/ folder not found");
1757
2063
  return copiedFiles;
1758
2064
  }
1759
2065
 
@@ -1765,34 +2071,36 @@ class Installer {
1765
2071
  // Check if the source file exists
1766
2072
  if (await fileManager.pathExists(sourcePath)) {
1767
2073
  // Read the file content
1768
- const content = await fs.readFile(sourcePath, 'utf8');
2074
+ const content = await fs.readFile(sourcePath, "utf8");
1769
2075
 
1770
2076
  // Replace {root} with the target subdirectory
1771
- const updatedContent = content.replaceAll('{root}', targetSubdir);
2077
+ const updatedContent = content.replaceAll("{root}", targetSubdir);
1772
2078
 
1773
2079
  // Ensure directory exists
1774
2080
  await fileManager.ensureDirectory(path.dirname(destinationPath));
1775
2081
 
1776
2082
  // Write the updated content
1777
- await fs.writeFile(destinationPath, updatedContent, 'utf8');
2083
+ await fs.writeFile(destinationPath, updatedContent, "utf8");
1778
2084
  copiedFiles.push(path.join(targetSubdir, documentFile));
1779
2085
  }
1780
2086
  }
1781
2087
 
1782
2088
  if (copiedFiles.length > 0) {
1783
- console.log(chalk.dim(` Added ${copiedFiles.length} documentation files`));
2089
+ console.log(
2090
+ chalk.dim(` Added ${copiedFiles.length} documentation files`),
2091
+ );
1784
2092
  }
1785
2093
  return copiedFiles;
1786
2094
  }
1787
2095
 
1788
2096
  async detectExpansionPacks(installDir) {
1789
2097
  const expansionPacks = {};
1790
- const glob = require('glob');
2098
+ const glob = require("glob");
1791
2099
 
1792
2100
  // Find all dot folders that might be expansion packs
1793
- const dotFolders = glob.sync('.*', {
2101
+ const dotFolders = glob.sync(".*", {
1794
2102
  cwd: installDir,
1795
- ignore: ['.git', '.git/**', '.xiaoma-core', '.xiaoma-core/**'],
2103
+ ignore: [".git", ".git/**", ".xiaoma-core", ".xiaoma-core/**"],
1796
2104
  });
1797
2105
 
1798
2106
  for (const folder of dotFolders) {
@@ -1801,9 +2109,12 @@ class Installer {
1801
2109
 
1802
2110
  if (stats) {
1803
2111
  // Check if it has a manifest
1804
- const manifestPath = path.join(folderPath, 'install-manifest.yaml');
2112
+ const manifestPath = path.join(folderPath, "install-manifest.yaml");
1805
2113
  if (await fileManager.pathExists(manifestPath)) {
1806
- const manifest = await fileManager.readExpansionPackManifest(installDir, folder.slice(1));
2114
+ const manifest = await fileManager.readExpansionPackManifest(
2115
+ installDir,
2116
+ folder.slice(1),
2117
+ );
1807
2118
  if (manifest) {
1808
2119
  expansionPacks[folder.slice(1)] = {
1809
2120
  path: folderPath,
@@ -1813,7 +2124,7 @@ class Installer {
1813
2124
  }
1814
2125
  } else {
1815
2126
  // Check if it has a config.yaml (expansion pack without manifest)
1816
- const configPath = path.join(folderPath, 'config.yaml');
2127
+ const configPath = path.join(folderPath, "config.yaml");
1817
2128
  if (await fileManager.pathExists(configPath)) {
1818
2129
  expansionPacks[folder.slice(1)] = {
1819
2130
  path: folderPath,
@@ -1836,46 +2147,52 @@ class Installer {
1836
2147
 
1837
2148
  // Back up modified files
1838
2149
  if (integrity.modified.length > 0) {
1839
- spinner.text = 'Backing up modified files...';
2150
+ spinner.text = "Backing up modified files...";
1840
2151
  for (const file of integrity.modified) {
1841
2152
  const filePath = path.join(installDir, file);
1842
2153
  if (await fileManager.pathExists(filePath)) {
1843
2154
  const backupPath = await fileManager.backupFile(filePath);
1844
- console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
2155
+ console.log(
2156
+ chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`),
2157
+ );
1845
2158
  }
1846
2159
  }
1847
2160
  }
1848
2161
 
1849
2162
  // Restore missing and modified files
1850
- spinner.text = 'Restoring files...';
2163
+ spinner.text = "Restoring files...";
1851
2164
  const filesToRestore = [...integrity.missing, ...integrity.modified];
1852
2165
 
1853
2166
  for (const file of filesToRestore) {
1854
2167
  // Skip the manifest file itself
1855
- if (file.endsWith('install-manifest.yaml')) continue;
2168
+ if (file.endsWith("install-manifest.yaml")) continue;
1856
2169
 
1857
- const relativePath = file.replace(`.${packId}/`, '');
2170
+ const relativePath = file.replace(`.${packId}/`, "");
1858
2171
  const sourcePath = path.join(pack.path, relativePath);
1859
2172
  const destinationPath = path.join(installDir, file);
1860
2173
 
1861
2174
  // Check if this is a common/ file that needs special processing
1862
- const commonBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
1863
- const commonSourcePath = path.join(commonBase, 'common', relativePath);
2175
+ const commonBase = path.dirname(
2176
+ path.dirname(path.dirname(path.dirname(__filename))),
2177
+ );
2178
+ const commonSourcePath = path.join(commonBase, "common", relativePath);
1864
2179
 
1865
2180
  if (await fileManager.pathExists(commonSourcePath)) {
1866
2181
  // This is a common/ file - needs template processing
1867
- const fs = require('node:fs').promises;
1868
- const content = await fs.readFile(commonSourcePath, 'utf8');
1869
- const updatedContent = content.replaceAll('{root}', `.${packId}`);
2182
+ const fs = require("node:fs").promises;
2183
+ const content = await fs.readFile(commonSourcePath, "utf8");
2184
+ const updatedContent = content.replaceAll("{root}", `.${packId}`);
1870
2185
  await fileManager.ensureDirectory(path.dirname(destinationPath));
1871
- await fs.writeFile(destinationPath, updatedContent, 'utf8');
2186
+ await fs.writeFile(destinationPath, updatedContent, "utf8");
1872
2187
  spinner.text = `Restored: ${file}`;
1873
2188
  } else if (await fileManager.pathExists(sourcePath)) {
1874
2189
  // Regular file from expansion pack
1875
2190
  await fileManager.copyFile(sourcePath, destinationPath);
1876
2191
  spinner.text = `Restored: ${file}`;
1877
2192
  } else {
1878
- console.warn(chalk.yellow(` Warning: Source file not found: ${file}`));
2193
+ console.warn(
2194
+ chalk.yellow(` Warning: Source file not found: ${file}`),
2195
+ );
1879
2196
  }
1880
2197
  }
1881
2198
 
@@ -1884,11 +2201,15 @@ class Installer {
1884
2201
  // Show summary
1885
2202
  console.log(chalk.green(`\n✓ ${pack.name} repaired!`));
1886
2203
  if (integrity.missing.length > 0) {
1887
- console.log(chalk.green(` Restored ${integrity.missing.length} missing files`));
2204
+ console.log(
2205
+ chalk.green(` Restored ${integrity.missing.length} missing files`),
2206
+ );
1888
2207
  }
1889
2208
  if (integrity.modified.length > 0) {
1890
2209
  console.log(
1891
- chalk.green(` Restored ${integrity.modified.length} modified files (backups created)`),
2210
+ chalk.green(
2211
+ ` Restored ${integrity.modified.length} modified files (backups created)`,
2212
+ ),
1892
2213
  );
1893
2214
  }
1894
2215
  } catch (error) {
@@ -1899,8 +2220,8 @@ class Installer {
1899
2220
 
1900
2221
  compareVersions(v1, v2) {
1901
2222
  // Simple semver comparison
1902
- const parts1 = v1.split('.').map(Number);
1903
- const parts2 = v2.split('.').map(Number);
2223
+ const parts1 = v1.split(".").map(Number);
2224
+ const parts2 = v2.split(".").map(Number);
1904
2225
 
1905
2226
  for (let index = 0; index < 3; index++) {
1906
2227
  const part1 = parts1[index] || 0;
@@ -1914,21 +2235,21 @@ class Installer {
1914
2235
  }
1915
2236
 
1916
2237
  async cleanupLegacyYmlFiles(installDir, spinner) {
1917
- const glob = require('glob');
1918
- const fs = require('node:fs').promises;
2238
+ const glob = require("glob");
2239
+ const fs = require("node:fs").promises;
1919
2240
 
1920
2241
  try {
1921
2242
  // Find all .yml files in the installation directory
1922
- const ymlFiles = glob.sync('**/*.yml', {
2243
+ const ymlFiles = glob.sync("**/*.yml", {
1923
2244
  cwd: installDir,
1924
- ignore: ['**/node_modules/**', '**/.git/**'],
2245
+ ignore: ["**/node_modules/**", "**/.git/**"],
1925
2246
  });
1926
2247
 
1927
2248
  let deletedCount = 0;
1928
2249
 
1929
2250
  for (const ymlFile of ymlFiles) {
1930
2251
  // Check if corresponding .yaml file exists
1931
- const yamlFile = ymlFile.replace(/\.yml$/, '.yaml');
2252
+ const yamlFile = ymlFile.replace(/\.yml$/, ".yaml");
1932
2253
  const ymlPath = path.join(installDir, ymlFile);
1933
2254
  const yamlPath = path.join(installDir, yamlFile);
1934
2255
 
@@ -1936,15 +2257,21 @@ class Installer {
1936
2257
  // .yaml counterpart exists, delete the .yml file
1937
2258
  await fs.unlink(ymlPath);
1938
2259
  deletedCount++;
1939
- console.log(chalk.dim(` Removed legacy: ${ymlFile} (replaced by ${yamlFile})`));
2260
+ console.log(
2261
+ chalk.dim(` Removed legacy: ${ymlFile} (replaced by ${yamlFile})`),
2262
+ );
1940
2263
  }
1941
2264
  }
1942
2265
 
1943
2266
  if (deletedCount > 0) {
1944
- console.log(chalk.green(`✓ Cleaned up ${deletedCount} legacy .yml files`));
2267
+ console.log(
2268
+ chalk.green(`✓ Cleaned up ${deletedCount} legacy .yml files`),
2269
+ );
1945
2270
  }
1946
2271
  } catch (error) {
1947
- console.warn(`Warning: Could not cleanup legacy .yml files: ${error.message}`);
2272
+ console.warn(
2273
+ `Warning: Could not cleanup legacy .yml files: ${error.message}`,
2274
+ );
1948
2275
  }
1949
2276
  }
1950
2277
 
@@ -1953,8 +2280,8 @@ class Installer {
1953
2280
  let currentDir = process.cwd();
1954
2281
 
1955
2282
  while (currentDir !== path.dirname(currentDir)) {
1956
- const bmadDir = path.join(currentDir, '.xiaoma-core');
1957
- const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
2283
+ const bmadDir = path.join(currentDir, ".xiaoma-core");
2284
+ const manifestPath = path.join(bmadDir, "install-manifest.yaml");
1958
2285
 
1959
2286
  if (await fileManager.pathExists(manifestPath)) {
1960
2287
  return currentDir; // Return parent directory, not .xiaoma-core itself
@@ -1964,8 +2291,8 @@ class Installer {
1964
2291
  }
1965
2292
 
1966
2293
  // Also check if we're inside a .xiaoma-core directory
1967
- if (path.basename(process.cwd()) === '.xiaoma-core') {
1968
- const manifestPath = path.join(process.cwd(), 'install-manifest.yaml');
2294
+ if (path.basename(process.cwd()) === ".xiaoma-core") {
2295
+ const manifestPath = path.join(process.cwd(), "install-manifest.yaml");
1969
2296
  if (await fileManager.pathExists(manifestPath)) {
1970
2297
  return path.dirname(process.cwd()); // Return parent directory
1971
2298
  }
@@ -1975,23 +2302,29 @@ class Installer {
1975
2302
  }
1976
2303
 
1977
2304
  async flatten(options) {
1978
- const { spawn } = require('node:child_process');
1979
- const flattenerPath = path.join(__dirname, '..', '..', 'flattener', 'main.js');
2305
+ const { spawn } = require("node:child_process");
2306
+ const flattenerPath = path.join(
2307
+ __dirname,
2308
+ "..",
2309
+ "..",
2310
+ "flattener",
2311
+ "main.js",
2312
+ );
1980
2313
 
1981
2314
  const arguments_ = [];
1982
2315
  if (options.input) {
1983
- arguments_.push('--input', options.input);
2316
+ arguments_.push("--input", options.input);
1984
2317
  }
1985
2318
  if (options.output) {
1986
- arguments_.push('--output', options.output);
2319
+ arguments_.push("--output", options.output);
1987
2320
  }
1988
2321
 
1989
- const child = spawn('node', [flattenerPath, ...arguments_], {
1990
- stdio: 'inherit',
2322
+ const child = spawn("node", [flattenerPath, ...arguments_], {
2323
+ stdio: "inherit",
1991
2324
  cwd: process.cwd(),
1992
2325
  });
1993
2326
 
1994
- child.on('exit', (code) => {
2327
+ child.on("exit", (code) => {
1995
2328
  process.exit(code);
1996
2329
  });
1997
2330
  }