bmad-method 4.31.0 → 4.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.vscode/settings.json +1 -7
  2. package/CHANGELOG.md +108 -173
  3. package/README.md +40 -0
  4. package/bmad-core/agents/analyst.md +1 -1
  5. package/bmad-core/agents/architect.md +2 -3
  6. package/bmad-core/agents/bmad-master.md +0 -1
  7. package/bmad-core/agents/bmad-orchestrator.md +9 -10
  8. package/bmad-core/agents/dev.md +1 -2
  9. package/bmad-core/agents/pm.md +3 -1
  10. package/bmad-core/agents/po.md +1 -1
  11. package/bmad-core/agents/qa.md +1 -1
  12. package/bmad-core/agents/sm.md +1 -1
  13. package/bmad-core/agents/ux-expert.md +1 -1
  14. package/bmad-core/bmad-core/user-guide.md +0 -0
  15. package/bmad-core/data/bmad-kb.md +12 -2
  16. package/bmad-core/data/elicitation-methods.md +20 -0
  17. package/bmad-core/enhanced-ide-development-workflow.md +43 -0
  18. package/bmad-core/tasks/advanced-elicitation.md +2 -0
  19. package/bmad-core/tasks/create-brownfield-story.md +20 -3
  20. package/bmad-core/tasks/document-project.md +19 -13
  21. package/bmad-core/tasks/facilitate-brainstorming-session.md +1 -1
  22. package/bmad-core/tasks/index-docs.md +0 -1
  23. package/bmad-core/tasks/kb-mode-interaction.md +3 -3
  24. package/bmad-core/tasks/review-story.md +18 -1
  25. package/bmad-core/user-guide.md +7 -6
  26. package/bmad-core/working-in-the-brownfield.md +39 -36
  27. package/dist/agents/analyst.txt +6 -6
  28. package/dist/agents/architect.txt +8 -3
  29. package/dist/agents/bmad-master.txt +2 -1
  30. package/dist/agents/pm.txt +9 -2
  31. package/dist/agents/po.txt +2 -318
  32. package/dist/agents/qa.txt +0 -1
  33. package/dist/agents/sm.txt +3 -3
  34. package/dist/agents/ux-expert.txt +2 -297
  35. package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +6 -6
  36. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +4047 -0
  37. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +1520 -185
  38. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +214 -1229
  39. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +537 -373
  40. package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +6917 -2140
  41. package/dist/teams/team-all.txt +30 -25
  42. package/dist/teams/team-fullstack.txt +27 -21
  43. package/dist/teams/team-ide-minimal.txt +5 -322
  44. package/dist/teams/team-no-ui.txt +25 -16
  45. package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +3 -1
  46. package/expansion-packs/bmad-2d-unity-game-dev/agent-teams/unity-2d-game-team.yaml +1 -0
  47. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.md +80 -0
  48. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.md +21 -16
  49. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +25 -25
  50. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.md +15 -14
  51. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-architect-checklist.md +396 -0
  52. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-change-checklist.md +203 -0
  53. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-design-checklist.md +1 -1
  54. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-story-dod-checklist.md +93 -121
  55. package/expansion-packs/bmad-2d-unity-game-dev/config.yaml +1 -1
  56. package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +593 -68
  57. package/expansion-packs/bmad-2d-unity-game-dev/tasks/correct-course-game.md +151 -0
  58. package/expansion-packs/bmad-2d-unity-game-dev/tasks/create-game-story.md +165 -198
  59. package/expansion-packs/bmad-2d-unity-game-dev/tasks/validate-game-story.md +200 -0
  60. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-architecture-tmpl.yaml +938 -453
  61. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +3 -3
  62. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +517 -155
  63. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +12 -12
  64. package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +11 -11
  65. package/package.json +79 -76
  66. package/tools/cli.js +9 -0
  67. package/tools/flattener/main.js +559 -0
  68. package/tools/installer/lib/installer.js +1 -1
  69. package/tools/installer/package.json +1 -1
  70. package/.husky/pre-commit +0 -2
  71. package/.prettierignore +0 -21
  72. package/.prettierrc +0 -23
@@ -0,0 +1,559 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs-extra');
5
+ const path = require('node:path');
6
+ const { glob } = require('glob');
7
+ const { minimatch } = require('minimatch');
8
+
9
+ /**
10
+ * Recursively discover all files in a directory
11
+ * @param {string} rootDir - The root directory to scan
12
+ * @returns {Promise<string[]>} Array of file paths
13
+ */
14
+ async function discoverFiles(rootDir) {
15
+ try {
16
+ const gitignorePath = path.join(rootDir, '.gitignore');
17
+ const gitignorePatterns = await parseGitignore(gitignorePath);
18
+
19
+ // Common gitignore patterns that should always be ignored
20
+ const commonIgnorePatterns = [
21
+ // Version control
22
+ '.git/**',
23
+ '.svn/**',
24
+ '.hg/**',
25
+ '.bzr/**',
26
+
27
+ // Dependencies
28
+ 'node_modules/**',
29
+ 'bower_components/**',
30
+ 'vendor/**',
31
+ 'packages/**',
32
+
33
+ // Build outputs
34
+ 'build/**',
35
+ 'dist/**',
36
+ 'out/**',
37
+ 'target/**',
38
+ 'bin/**',
39
+ 'obj/**',
40
+ 'release/**',
41
+ 'debug/**',
42
+
43
+ // Environment and config
44
+ '.env',
45
+ '.env.*',
46
+ '*.env',
47
+ '.config',
48
+
49
+ // Logs
50
+ 'logs/**',
51
+ '*.log',
52
+ 'npm-debug.log*',
53
+ 'yarn-debug.log*',
54
+ 'yarn-error.log*',
55
+ 'lerna-debug.log*',
56
+
57
+ // Coverage and testing
58
+ 'coverage/**',
59
+ '.nyc_output/**',
60
+ '.coverage/**',
61
+ 'test-results/**',
62
+ 'junit.xml',
63
+
64
+ // Cache directories
65
+ '.cache/**',
66
+ '.tmp/**',
67
+ '.temp/**',
68
+ 'tmp/**',
69
+ 'temp/**',
70
+ '.sass-cache/**',
71
+ '.eslintcache',
72
+ '.stylelintcache',
73
+
74
+ // OS generated files
75
+ '.DS_Store',
76
+ '.DS_Store?',
77
+ '._*',
78
+ '.Spotlight-V100',
79
+ '.Trashes',
80
+ 'ehthumbs.db',
81
+ 'Thumbs.db',
82
+ 'desktop.ini',
83
+
84
+ // IDE and editor files
85
+ '.vscode/**',
86
+ '.idea/**',
87
+ '*.swp',
88
+ '*.swo',
89
+ '*~',
90
+ '.project',
91
+ '.classpath',
92
+ '.settings/**',
93
+ '*.sublime-project',
94
+ '*.sublime-workspace',
95
+
96
+ // Package manager files
97
+ 'package-lock.json',
98
+ 'yarn.lock',
99
+ 'pnpm-lock.yaml',
100
+ 'composer.lock',
101
+ 'Pipfile.lock',
102
+
103
+ // Runtime and compiled files
104
+ '*.pyc',
105
+ '*.pyo',
106
+ '*.pyd',
107
+ '__pycache__/**',
108
+ '*.class',
109
+ '*.jar',
110
+ '*.war',
111
+ '*.ear',
112
+ '*.o',
113
+ '*.so',
114
+ '*.dll',
115
+ '*.exe',
116
+
117
+ // Documentation build
118
+ '_site/**',
119
+ '.jekyll-cache/**',
120
+ '.jekyll-metadata',
121
+
122
+ // Flattener specific outputs
123
+ 'flattened-codebase.xml',
124
+ 'repomix-output.xml'
125
+ ];
126
+
127
+ const combinedIgnores = [
128
+ ...gitignorePatterns,
129
+ ...commonIgnorePatterns
130
+ ];
131
+
132
+ // Use glob to recursively find all files, excluding common ignore patterns
133
+ const files = await glob('**/*', {
134
+ cwd: rootDir,
135
+ nodir: true, // Only files, not directories
136
+ dot: true, // Include hidden files
137
+ follow: false, // Don't follow symbolic links
138
+ ignore: combinedIgnores
139
+ });
140
+
141
+ return files.map(file => path.resolve(rootDir, file));
142
+ } catch (error) {
143
+ console.error('Error discovering files:', error.message);
144
+ return [];
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Parse .gitignore file and return ignore patterns
150
+ * @param {string} gitignorePath - Path to .gitignore file
151
+ * @returns {Promise<string[]>} Array of ignore patterns
152
+ */
153
+ async function parseGitignore(gitignorePath) {
154
+ try {
155
+ if (!await fs.pathExists(gitignorePath)) {
156
+ return [];
157
+ }
158
+
159
+ const content = await fs.readFile(gitignorePath, 'utf8');
160
+ return content
161
+ .split('\n')
162
+ .map(line => line.trim())
163
+ .filter(line => line && !line.startsWith('#')) // Remove empty lines and comments
164
+ .map(pattern => {
165
+ // Convert gitignore patterns to glob patterns
166
+ if (pattern.endsWith('/')) {
167
+ return pattern + '**';
168
+ }
169
+ return pattern;
170
+ });
171
+ } catch (error) {
172
+ console.error('Error parsing .gitignore:', error.message);
173
+ return [];
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Check if a file is binary using file command and heuristics
179
+ * @param {string} filePath - Path to the file
180
+ * @returns {Promise<boolean>} True if file is binary
181
+ */
182
+ async function isBinaryFile(filePath) {
183
+ try {
184
+ // First check by file extension
185
+ const binaryExtensions = [
186
+ '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg',
187
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
188
+ '.zip', '.tar', '.gz', '.rar', '.7z',
189
+ '.exe', '.dll', '.so', '.dylib',
190
+ '.mp3', '.mp4', '.avi', '.mov', '.wav',
191
+ '.ttf', '.otf', '.woff', '.woff2',
192
+ '.bin', '.dat', '.db', '.sqlite'
193
+ ];
194
+
195
+ const ext = path.extname(filePath).toLowerCase();
196
+ if (binaryExtensions.includes(ext)) {
197
+ return true;
198
+ }
199
+
200
+ // For files without clear extensions, try to read a small sample
201
+ const stats = await fs.stat(filePath);
202
+ if (stats.size === 0) {
203
+ return false; // Empty files are considered text
204
+ }
205
+
206
+ // Read first 1024 bytes to check for null bytes
207
+ const sampleSize = Math.min(1024, stats.size);
208
+ const buffer = await fs.readFile(filePath, { encoding: null, flag: 'r' });
209
+ const sample = buffer.slice(0, sampleSize);
210
+ // If we find null bytes, it's likely binary
211
+ return sample.includes(0);
212
+ } catch (error) {
213
+ console.warn(`Warning: Could not determine if file is binary: ${filePath} - ${error.message}`);
214
+ return false; // Default to text if we can't determine
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Read and aggregate content from text files
220
+ * @param {string[]} files - Array of file paths
221
+ * @param {string} rootDir - The root directory
222
+ * @param {Object} spinner - Optional spinner instance for progress display
223
+ * @returns {Promise<Object>} Object containing file contents and metadata
224
+ */
225
+ async function aggregateFileContents(files, rootDir, spinner = null) {
226
+ const results = {
227
+ textFiles: [],
228
+ binaryFiles: [],
229
+ errors: [],
230
+ totalFiles: files.length,
231
+ processedFiles: 0
232
+ };
233
+
234
+ for (const filePath of files) {
235
+ try {
236
+ const relativePath = path.relative(rootDir, filePath);
237
+
238
+ // Update progress indicator
239
+ if (spinner) {
240
+ spinner.text = `Processing file ${results.processedFiles + 1}/${results.totalFiles}: ${relativePath}`;
241
+ }
242
+
243
+ const isBinary = await isBinaryFile(filePath);
244
+
245
+ if (isBinary) {
246
+ results.binaryFiles.push({
247
+ path: relativePath,
248
+ absolutePath: filePath,
249
+ size: (await fs.stat(filePath)).size
250
+ });
251
+ } else {
252
+ // Read text file content
253
+ const content = await fs.readFile(filePath, 'utf8');
254
+ results.textFiles.push({
255
+ path: relativePath,
256
+ absolutePath: filePath,
257
+ content: content,
258
+ size: content.length,
259
+ lines: content.split('\n').length
260
+ });
261
+ }
262
+
263
+ results.processedFiles++;
264
+ } catch (error) {
265
+ const relativePath = path.relative(rootDir, filePath);
266
+ const errorInfo = {
267
+ path: relativePath,
268
+ absolutePath: filePath,
269
+ error: error.message
270
+ };
271
+
272
+ results.errors.push(errorInfo);
273
+
274
+ // Log warning without interfering with spinner
275
+ if (spinner) {
276
+ spinner.warn(`Warning: Could not read file ${relativePath}: ${error.message}`);
277
+ } else {
278
+ console.warn(`Warning: Could not read file ${relativePath}: ${error.message}`);
279
+ }
280
+
281
+ results.processedFiles++;
282
+ }
283
+ }
284
+
285
+ return results;
286
+ }
287
+
288
+ /**
289
+ * Generate XML output with aggregated file contents using streaming
290
+ * @param {Object} aggregatedContent - The aggregated content object
291
+ * @param {string} outputPath - The output file path
292
+ * @returns {Promise<void>} Promise that resolves when writing is complete
293
+ */
294
+ async function generateXMLOutput(aggregatedContent, outputPath) {
295
+ const { textFiles } = aggregatedContent;
296
+
297
+ // Create write stream for efficient memory usage
298
+ const writeStream = fs.createWriteStream(outputPath, { encoding: 'utf8' });
299
+
300
+ return new Promise((resolve, reject) => {
301
+ writeStream.on('error', reject);
302
+ writeStream.on('finish', resolve);
303
+
304
+ // Write XML header
305
+ writeStream.write('<?xml version="1.0" encoding="UTF-8"?>\n');
306
+ writeStream.write('<files>\n');
307
+
308
+ // Process files one by one to minimize memory usage
309
+ let fileIndex = 0;
310
+
311
+ const writeNextFile = () => {
312
+ if (fileIndex >= textFiles.length) {
313
+ // All files processed, close XML and stream
314
+ writeStream.write('</files>\n');
315
+ writeStream.end();
316
+ return;
317
+ }
318
+
319
+ const file = textFiles[fileIndex];
320
+ fileIndex++;
321
+
322
+ // Write file opening tag
323
+ writeStream.write(` <file path="${escapeXml(file.path)}">`);
324
+
325
+ // Use CDATA for code content, handling CDATA end sequences properly
326
+ if (file.content?.trim()) {
327
+ const indentedContent = indentFileContent(file.content);
328
+ if (file.content.includes(']]>')) {
329
+ // If content contains ]]>, split it and wrap each part in CDATA
330
+ writeStream.write(splitAndWrapCDATA(indentedContent));
331
+ } else {
332
+ writeStream.write(`<![CDATA[\n${indentedContent}\n ]]>`);
333
+ }
334
+ } else if (file.content) {
335
+ // Handle empty or whitespace-only content
336
+ const indentedContent = indentFileContent(file.content);
337
+ writeStream.write(`<![CDATA[\n${indentedContent}\n ]]>`);
338
+ }
339
+
340
+ // Write file closing tag
341
+ writeStream.write('</file>\n');
342
+
343
+ // Continue with next file on next tick to avoid stack overflow
344
+ setImmediate(writeNextFile);
345
+ };
346
+
347
+ // Start processing files
348
+ writeNextFile();
349
+ });
350
+ }
351
+
352
+ /**
353
+ * Escape XML special characters for attributes
354
+ * @param {string} str - String to escape
355
+ * @returns {string} Escaped string
356
+ */
357
+ function escapeXml(str) {
358
+ if (typeof str !== 'string') {
359
+ return String(str);
360
+ }
361
+ return str
362
+ .replace(/&/g, '&amp;')
363
+ .replace(/</g, '&lt;')
364
+ .replace(/>/g, '&gt;')
365
+ .replace(/"/g, '&quot;')
366
+ .replace(/'/g, '&apos;');
367
+ }
368
+
369
+ /**
370
+ * Indent file content with 4 spaces for each line
371
+ * @param {string} content - Content to indent
372
+ * @returns {string} Indented content
373
+ */
374
+ function indentFileContent(content) {
375
+ if (typeof content !== 'string') {
376
+ return String(content);
377
+ }
378
+
379
+ // Split content into lines and add 4 spaces of indentation to each line
380
+ return content.split('\n').map(line => ` ${line}`).join('\n');
381
+ }
382
+
383
+ /**
384
+ * Split content containing ]]> and wrap each part in CDATA
385
+ * @param {string} content - Content to process
386
+ * @returns {string} Content with properly wrapped CDATA sections
387
+ */
388
+ function splitAndWrapCDATA(content) {
389
+ if (typeof content !== 'string') {
390
+ return String(content);
391
+ }
392
+
393
+ // Replace ]]> with ]]]]><![CDATA[> to escape it within CDATA
394
+ const escapedContent = content.replace(/]]>/g, ']]]]><![CDATA[>');
395
+ return `<![CDATA[
396
+ ${escapedContent}
397
+ ]]>`;
398
+ }
399
+
400
+ /**
401
+ * Calculate statistics for the processed files
402
+ * @param {Object} aggregatedContent - The aggregated content object
403
+ * @param {number} xmlFileSize - The size of the generated XML file in bytes
404
+ * @returns {Object} Statistics object
405
+ */
406
+ function calculateStatistics(aggregatedContent, xmlFileSize) {
407
+ const { textFiles, binaryFiles, errors } = aggregatedContent;
408
+
409
+ // Calculate total file size in bytes
410
+ const totalTextSize = textFiles.reduce((sum, file) => sum + file.size, 0);
411
+ const totalBinarySize = binaryFiles.reduce((sum, file) => sum + file.size, 0);
412
+ const totalSize = totalTextSize + totalBinarySize;
413
+
414
+ // Calculate total lines of code
415
+ const totalLines = textFiles.reduce((sum, file) => sum + file.lines, 0);
416
+
417
+ // Estimate token count (rough approximation: 1 token ā‰ˆ 4 characters)
418
+ const estimatedTokens = Math.ceil(xmlFileSize / 4);
419
+
420
+ // Format file size
421
+ const formatSize = (bytes) => {
422
+ if (bytes < 1024) return `${bytes} B`;
423
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
424
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
425
+ };
426
+
427
+ return {
428
+ totalFiles: textFiles.length + binaryFiles.length,
429
+ textFiles: textFiles.length,
430
+ binaryFiles: binaryFiles.length,
431
+ errorFiles: errors.length,
432
+ totalSize: formatSize(totalSize),
433
+ xmlSize: formatSize(xmlFileSize),
434
+ totalLines,
435
+ estimatedTokens: estimatedTokens.toLocaleString()
436
+ };
437
+ }
438
+
439
+ /**
440
+ * Filter files based on .gitignore patterns
441
+ * @param {string[]} files - Array of file paths
442
+ * @param {string} rootDir - The root directory
443
+ * @returns {Promise<string[]>} Filtered array of file paths
444
+ */
445
+ async function filterFiles(files, rootDir) {
446
+ const gitignorePath = path.join(rootDir, '.gitignore');
447
+ const ignorePatterns = await parseGitignore(gitignorePath);
448
+
449
+ if (ignorePatterns.length === 0) {
450
+ return files;
451
+ }
452
+
453
+ // Convert absolute paths to relative for pattern matching
454
+ const relativeFiles = files.map(file => path.relative(rootDir, file));
455
+
456
+ // Separate positive and negative patterns
457
+ const positivePatterns = ignorePatterns.filter(p => !p.startsWith('!'));
458
+ const negativePatterns = ignorePatterns.filter(p => p.startsWith('!')).map(p => p.slice(1));
459
+
460
+ // Filter out files that match ignore patterns
461
+ const filteredRelative = [];
462
+
463
+ for (const file of relativeFiles) {
464
+ let shouldIgnore = false;
465
+
466
+ // First check positive patterns (ignore these files)
467
+ for (const pattern of positivePatterns) {
468
+ if (minimatch(file, pattern)) {
469
+ shouldIgnore = true;
470
+ break;
471
+ }
472
+ }
473
+
474
+ // Then check negative patterns (don't ignore these files even if they match positive patterns)
475
+ if (shouldIgnore) {
476
+ for (const pattern of negativePatterns) {
477
+ if (minimatch(file, pattern)) {
478
+ shouldIgnore = false;
479
+ break;
480
+ }
481
+ }
482
+ }
483
+
484
+ if (!shouldIgnore) {
485
+ filteredRelative.push(file);
486
+ }
487
+ }
488
+
489
+ // Convert back to absolute paths
490
+ return filteredRelative.map(file => path.resolve(rootDir, file));
491
+ }
492
+
493
+ const program = new Command();
494
+
495
+ program
496
+ .name('bmad-flatten')
497
+ .description('BMad-Method codebase flattener tool')
498
+ .version('1.0.0')
499
+ .option('-o, --output <path>', 'Output file path', 'flattened-codebase.xml')
500
+ .action(async (options) => {
501
+ console.log(`Flattening codebase to: ${options.output}`);
502
+
503
+ try {
504
+ // Import ora dynamically
505
+ const { default: ora } = await import('ora');
506
+
507
+ // Start file discovery with spinner
508
+ const discoverySpinner = ora('šŸ” Discovering files...').start();
509
+ const files = await discoverFiles(process.cwd());
510
+ const filteredFiles = await filterFiles(files, process.cwd());
511
+ discoverySpinner.succeed(`šŸ“ Found ${filteredFiles.length} files to include`);
512
+
513
+ // Process files with progress tracking
514
+ console.log('Reading file contents');
515
+ const processingSpinner = ora('šŸ“„ Processing files...').start();
516
+ const aggregatedContent = await aggregateFileContents(filteredFiles, process.cwd(), processingSpinner);
517
+ processingSpinner.succeed(`āœ… Processed ${aggregatedContent.processedFiles}/${filteredFiles.length} files`);
518
+
519
+ // Log processing results for test validation
520
+ console.log(`Processed ${aggregatedContent.processedFiles}/${filteredFiles.length} files`);
521
+ if (aggregatedContent.errors.length > 0) {
522
+ console.log(`Errors: ${aggregatedContent.errors.length}`);
523
+ }
524
+ console.log(`Text files: ${aggregatedContent.textFiles.length}`);
525
+ if (aggregatedContent.binaryFiles.length > 0) {
526
+ console.log(`Binary files: ${aggregatedContent.binaryFiles.length}`);
527
+ }
528
+
529
+ // Generate XML output using streaming
530
+ const xmlSpinner = ora('šŸ”§ Generating XML output...').start();
531
+ await generateXMLOutput(aggregatedContent, options.output);
532
+ xmlSpinner.succeed('šŸ“ XML generation completed');
533
+
534
+ // Calculate and display statistics
535
+ const outputStats = await fs.stat(options.output);
536
+ const stats = calculateStatistics(aggregatedContent, outputStats.size);
537
+
538
+ // Display completion summary
539
+ console.log('\nšŸ“Š Completion Summary:');
540
+ console.log(`āœ… Successfully processed ${filteredFiles.length} files into ${options.output}`);
541
+ console.log(`šŸ“ Output file: ${path.resolve(options.output)}`);
542
+ console.log(`šŸ“ Total source size: ${stats.totalSize}`);
543
+ console.log(`šŸ“„ Generated XML size: ${stats.xmlSize}`);
544
+ console.log(`šŸ“ Total lines of code: ${stats.totalLines.toLocaleString()}`);
545
+ console.log(`šŸ”¢ Estimated tokens: ${stats.estimatedTokens}`);
546
+ console.log(`šŸ“Š File breakdown: ${stats.textFiles} text, ${stats.binaryFiles} binary, ${stats.errorFiles} errors`);
547
+
548
+ } catch (error) {
549
+ console.error('āŒ Critical error:', error.message);
550
+ console.error('An unexpected error occurred.');
551
+ process.exit(1);
552
+ }
553
+ });
554
+
555
+ if (require.main === module) {
556
+ program.parse();
557
+ }
558
+
559
+ module.exports = program;
@@ -893,7 +893,7 @@ class Installer {
893
893
  }
894
894
 
895
895
  // Important notice to read the user guide
896
- console.log(chalk.red.bold("\nšŸ“– IMPORTANT: Please read the user guide installed at docs/user-guide.md"));
896
+ console.log(chalk.red.bold("\nšŸ“– IMPORTANT: Please read the user guide installed at .bmad-core/user-guide.md"));
897
897
  console.log(chalk.red("This guide contains essential information about the BMad workflow and how to use the agents effectively."));
898
898
  }
899
899
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmad-method",
3
- "version": "4.31.0",
3
+ "version": "4.32.0",
4
4
  "description": "BMad Method installer - AI-powered Agile development framework",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {
package/.husky/pre-commit DELETED
@@ -1,2 +0,0 @@
1
- # Run lint-staged to format and lint YAML files
2
- npx lint-staged
package/.prettierignore DELETED
@@ -1,21 +0,0 @@
1
- # Dependencies
2
- node_modules/
3
- package-lock.json
4
-
5
- # Build outputs
6
- dist/
7
-
8
- # Generated files
9
- *.log
10
- *.lock
11
-
12
- # BMad core files (have their own formatting)
13
- bmad-core/**/*.md
14
-
15
- # Specific files that need custom formatting
16
- .roomodes
17
- CHANGELOG.md
18
-
19
- # IDE files
20
- .vscode/
21
- .idea/
package/.prettierrc DELETED
@@ -1,23 +0,0 @@
1
- {
2
- "printWidth": 100,
3
- "tabWidth": 2,
4
- "useTabs": false,
5
- "semi": true,
6
- "singleQuote": false,
7
- "quoteProps": "as-needed",
8
- "trailingComma": "es5",
9
- "bracketSpacing": true,
10
- "bracketSameLine": false,
11
- "arrowParens": "always",
12
- "proseWrap": "preserve",
13
- "endOfLine": "lf",
14
- "overrides": [
15
- {
16
- "files": "*.md",
17
- "options": {
18
- "proseWrap": "preserve",
19
- "printWidth": 120
20
- }
21
- }
22
- ]
23
- }