bmad-method 4.35.2 → 4.36.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 (32) hide show
  1. package/.github/workflows/discord.yaml +16 -0
  2. package/CHANGELOG.md +14 -2
  3. package/README.md +42 -9
  4. package/dist/agents/dev.txt +9 -9
  5. package/dist/teams/team-all.txt +9 -9
  6. package/dist/teams/team-ide-minimal.txt +9 -9
  7. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/Complete AI Agent System - Flowchart.svg +102 -0
  8. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.1 Google Cloud Project Setup/1.1.1 - Initial Project Configuration - bash copy.txt +13 -0
  9. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.1 Google Cloud Project Setup/1.1.1 - Initial Project Configuration - bash.txt +13 -0
  10. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.2 Agent Development Kit Installation/1.2.2 - Basic Project Structure - txt.txt +25 -0
  11. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.3 Core Configuration Files/1.3.1 - settings.py +34 -0
  12. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.3 Core Configuration Files/1.3.2 - main.py - Base Application.py +70 -0
  13. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.4 Deployment Configuration/1.4.2 - cloudbuild.yaml +26 -0
  14. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/README.md +109 -0
  15. package/package.json +2 -2
  16. package/tools/flattener/aggregate.js +76 -0
  17. package/tools/flattener/binary.js +53 -0
  18. package/tools/flattener/discovery.js +70 -0
  19. package/tools/flattener/files.js +35 -0
  20. package/tools/flattener/ignoreRules.js +176 -0
  21. package/tools/flattener/main.js +113 -466
  22. package/tools/flattener/projectRoot.js +45 -0
  23. package/tools/flattener/prompts.js +44 -0
  24. package/tools/flattener/stats.js +30 -0
  25. package/tools/flattener/xml.js +86 -0
  26. package/tools/installer/lib/installer.js +63 -1
  27. package/tools/installer/package.json +1 -1
  28. package/tools/shared/bannerArt.js +105 -0
  29. package/tools/installer/package-lock.json +0 -906
  30. /package/{bmad-core → docs}/enhanced-ide-development-workflow.md +0 -0
  31. /package/{bmad-core → docs}/user-guide.md +0 -0
  32. /package/{bmad-core → docs}/working-in-the-brownfield.md +0 -0
@@ -0,0 +1,45 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("node:path");
3
+
4
+ /**
5
+ * Attempt to find the project root by walking up from startDir
6
+ * Looks for common project markers like .git, package.json, pyproject.toml, etc.
7
+ * @param {string} startDir
8
+ * @returns {Promise<string|null>} project root directory or null if not found
9
+ */
10
+ async function findProjectRoot(startDir) {
11
+ try {
12
+ let dir = path.resolve(startDir);
13
+ const root = path.parse(dir).root;
14
+ const markers = [
15
+ ".git",
16
+ "package.json",
17
+ "pnpm-workspace.yaml",
18
+ "yarn.lock",
19
+ "pnpm-lock.yaml",
20
+ "pyproject.toml",
21
+ "requirements.txt",
22
+ "go.mod",
23
+ "Cargo.toml",
24
+ "composer.json",
25
+ ".hg",
26
+ ".svn",
27
+ ];
28
+
29
+ while (true) {
30
+ const exists = await Promise.all(
31
+ markers.map((m) => fs.pathExists(path.join(dir, m))),
32
+ );
33
+ if (exists.some(Boolean)) {
34
+ return dir;
35
+ }
36
+ if (dir === root) break;
37
+ dir = path.dirname(dir);
38
+ }
39
+ return null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ module.exports = { findProjectRoot };
@@ -0,0 +1,44 @@
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+ const readline = require("node:readline");
4
+ const process = require("node:process");
5
+
6
+ function expandHome(p) {
7
+ if (!p) return p;
8
+ if (p.startsWith("~")) return path.join(os.homedir(), p.slice(1));
9
+ return p;
10
+ }
11
+
12
+ function createRl() {
13
+ return readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout,
16
+ });
17
+ }
18
+
19
+ function promptQuestion(question) {
20
+ return new Promise((resolve) => {
21
+ const rl = createRl();
22
+ rl.question(question, (answer) => {
23
+ rl.close();
24
+ resolve(answer);
25
+ });
26
+ });
27
+ }
28
+
29
+ async function promptYesNo(question, defaultYes = true) {
30
+ const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
31
+ const ans = (await promptQuestion(`${question}${suffix}`)).trim().toLowerCase();
32
+ if (!ans) return defaultYes;
33
+ if (["y", "yes"].includes(ans)) return true;
34
+ if (["n", "no"].includes(ans)) return false;
35
+ return promptYesNo(question, defaultYes);
36
+ }
37
+
38
+ async function promptPath(question, defaultValue) {
39
+ const prompt = `${question}${defaultValue ? ` (default: ${defaultValue})` : ""}: `;
40
+ const ans = (await promptQuestion(prompt)).trim();
41
+ return expandHome(ans || defaultValue);
42
+ }
43
+
44
+ module.exports = { promptYesNo, promptPath, promptQuestion, expandHome };
@@ -0,0 +1,30 @@
1
+ function calculateStatistics(aggregatedContent, xmlFileSize) {
2
+ const { textFiles, binaryFiles, errors } = aggregatedContent;
3
+
4
+ const totalTextSize = textFiles.reduce((sum, file) => sum + file.size, 0);
5
+ const totalBinarySize = binaryFiles.reduce((sum, file) => sum + file.size, 0);
6
+ const totalSize = totalTextSize + totalBinarySize;
7
+
8
+ const totalLines = textFiles.reduce((sum, file) => sum + file.lines, 0);
9
+
10
+ const estimatedTokens = Math.ceil(xmlFileSize / 4);
11
+
12
+ const formatSize = (bytes) => {
13
+ if (bytes < 1024) return `${bytes} B`;
14
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
15
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
16
+ };
17
+
18
+ return {
19
+ totalFiles: textFiles.length + binaryFiles.length,
20
+ textFiles: textFiles.length,
21
+ binaryFiles: binaryFiles.length,
22
+ errorFiles: errors.length,
23
+ totalSize: formatSize(totalSize),
24
+ xmlSize: formatSize(xmlFileSize),
25
+ totalLines,
26
+ estimatedTokens: estimatedTokens.toLocaleString(),
27
+ };
28
+ }
29
+
30
+ module.exports = { calculateStatistics };
@@ -0,0 +1,86 @@
1
+ const fs = require("fs-extra");
2
+
3
+ function escapeXml(str) {
4
+ if (typeof str !== "string") {
5
+ return String(str);
6
+ }
7
+ return str
8
+ .replace(/&/g, "&amp;")
9
+ .replace(/</g, "&lt;")
10
+ .replace(/'/g, "&apos;");
11
+ }
12
+
13
+ function indentFileContent(content) {
14
+ if (typeof content !== "string") {
15
+ return String(content);
16
+ }
17
+ return content.split("\n").map((line) => ` ${line}`);
18
+ }
19
+
20
+ function generateXMLOutput(aggregatedContent, outputPath) {
21
+ const { textFiles } = aggregatedContent;
22
+ const writeStream = fs.createWriteStream(outputPath, { encoding: "utf8" });
23
+
24
+ return new Promise((resolve, reject) => {
25
+ writeStream.on("error", reject);
26
+ writeStream.on("finish", resolve);
27
+
28
+ writeStream.write('<?xml version="1.0" encoding="UTF-8"?>\n');
29
+ writeStream.write("<files>\n");
30
+
31
+ // Sort files by path for deterministic order
32
+ const filesSorted = [...textFiles].sort((a, b) =>
33
+ a.path.localeCompare(b.path)
34
+ );
35
+ let index = 0;
36
+
37
+ const writeNext = () => {
38
+ if (index >= filesSorted.length) {
39
+ writeStream.write("</files>\n");
40
+ writeStream.end();
41
+ return;
42
+ }
43
+
44
+ const file = filesSorted[index++];
45
+ const p = escapeXml(file.path);
46
+ const content = typeof file.content === "string" ? file.content : "";
47
+
48
+ if (content.length === 0) {
49
+ writeStream.write(`\t<file path='${p}'/>\n`);
50
+ setTimeout(writeNext, 0);
51
+ return;
52
+ }
53
+
54
+ const needsCdata = content.includes("<") || content.includes("&") ||
55
+ content.includes("]]>");
56
+ if (needsCdata) {
57
+ // Open tag and CDATA on their own line with tab indent; content lines indented with two tabs
58
+ writeStream.write(`\t<file path='${p}'><![CDATA[\n`);
59
+ // Safely split any occurrences of "]]>" inside content, trim trailing newlines, indent each line with two tabs
60
+ const safe = content.replace(/]]>/g, "]]]]><![CDATA[>");
61
+ const trimmed = safe.replace(/[\r\n]+$/, "");
62
+ const indented = trimmed.length > 0
63
+ ? trimmed.split("\n").map((line) => `\t\t${line}`).join("\n")
64
+ : "";
65
+ writeStream.write(indented);
66
+ // Close CDATA and attach closing tag directly after the last content line
67
+ writeStream.write("]]></file>\n");
68
+ } else {
69
+ // Write opening tag then newline; indent content with two tabs; attach closing tag directly after last content char
70
+ writeStream.write(`\t<file path='${p}'>\n`);
71
+ const trimmed = content.replace(/[\r\n]+$/, "");
72
+ const indented = trimmed.length > 0
73
+ ? trimmed.split("\n").map((line) => `\t\t${line}`).join("\n")
74
+ : "";
75
+ writeStream.write(indented);
76
+ writeStream.write(`</file>\n`);
77
+ }
78
+
79
+ setTimeout(writeNext, 0);
80
+ };
81
+
82
+ writeNext();
83
+ });
84
+ }
85
+
86
+ module.exports = { generateXMLOutput };
@@ -237,6 +237,10 @@ class Installer {
237
237
  // Copy common/ items to .bmad-core
238
238
  spinner.text = "Copying common utilities...";
239
239
  await this.copyCommonItems(installDir, ".bmad-core", spinner);
240
+
241
+ // Copy documentation files from docs/ to .bmad-core
242
+ spinner.text = "Copying documentation files...";
243
+ await this.copyDocsItems(installDir, ".bmad-core", spinner);
240
244
 
241
245
  // Get list of all files for manifest
242
246
  const foundFiles = await resourceLocator.findFiles("**/*", {
@@ -308,6 +312,11 @@ class Installer {
308
312
  spinner.text = "Copying common utilities...";
309
313
  const commonFiles = await this.copyCommonItems(installDir, ".bmad-core", spinner);
310
314
  files.push(...commonFiles);
315
+
316
+ // Copy documentation files from docs/ to .bmad-core
317
+ spinner.text = "Copying documentation files...";
318
+ const docFiles = await this.copyDocsItems(installDir, ".bmad-core", spinner);
319
+ files.push(...docFiles);
311
320
  } else if (config.installType === "team") {
312
321
  // Team installation
313
322
  spinner.text = `Installing ${config.team} team...`;
@@ -353,6 +362,11 @@ class Installer {
353
362
  spinner.text = "Copying common utilities...";
354
363
  const commonFiles = await this.copyCommonItems(installDir, ".bmad-core", spinner);
355
364
  files.push(...commonFiles);
365
+
366
+ // Copy documentation files from docs/ to .bmad-core
367
+ spinner.text = "Copying documentation files...";
368
+ const docFiles = await this.copyDocsItems(installDir, ".bmad-core", spinner);
369
+ files.push(...docFiles);
356
370
  } else if (config.installType === "expansion-only") {
357
371
  // Expansion-only installation - DO NOT create .bmad-core
358
372
  // Only install expansion packs
@@ -896,7 +910,7 @@ class Installer {
896
910
  }
897
911
 
898
912
  // Important notice to read the user guide
899
- console.log(chalk.red.bold("\n📖 IMPORTANT: Please read the user guide installed at .bmad-core/user-guide.md"));
913
+ console.log(chalk.red.bold("\n📖 IMPORTANT: Please read the user guide at docs/user-guide.md (also installed at .bmad-core/user-guide.md)"));
900
914
  console.log(chalk.red("This guide contains essential information about the BMad workflow and how to use the agents effectively."));
901
915
  }
902
916
 
@@ -1557,6 +1571,54 @@ class Installer {
1557
1571
  return copiedFiles;
1558
1572
  }
1559
1573
 
1574
+ async copyDocsItems(installDir, targetSubdir, spinner) {
1575
+ const fs = require('fs').promises;
1576
+ const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root
1577
+ const docsPath = path.join(sourceBase, 'docs');
1578
+ const targetPath = path.join(installDir, targetSubdir);
1579
+ const copiedFiles = [];
1580
+
1581
+ // Specific documentation files to copy
1582
+ const docFiles = [
1583
+ 'enhanced-ide-development-workflow.md',
1584
+ 'user-guide.md',
1585
+ 'working-in-the-brownfield.md'
1586
+ ];
1587
+
1588
+ // Check if docs/ exists
1589
+ if (!(await fileManager.pathExists(docsPath))) {
1590
+ console.warn('Warning: docs/ folder not found');
1591
+ return copiedFiles;
1592
+ }
1593
+
1594
+ // Copy specific documentation files from docs/ to target
1595
+ for (const docFile of docFiles) {
1596
+ const sourcePath = path.join(docsPath, docFile);
1597
+ const destPath = path.join(targetPath, docFile);
1598
+
1599
+ // Check if the source file exists
1600
+ if (await fileManager.pathExists(sourcePath)) {
1601
+ // Read the file content
1602
+ const content = await fs.readFile(sourcePath, 'utf8');
1603
+
1604
+ // Replace {root} with the target subdirectory
1605
+ const updatedContent = content.replace(/\{root\}/g, targetSubdir);
1606
+
1607
+ // Ensure directory exists
1608
+ await fileManager.ensureDirectory(path.dirname(destPath));
1609
+
1610
+ // Write the updated content
1611
+ await fs.writeFile(destPath, updatedContent, 'utf8');
1612
+ copiedFiles.push(path.join(targetSubdir, docFile));
1613
+ }
1614
+ }
1615
+
1616
+ if (copiedFiles.length > 0) {
1617
+ console.log(chalk.dim(` Added ${copiedFiles.length} documentation files`));
1618
+ }
1619
+ return copiedFiles;
1620
+ }
1621
+
1560
1622
  async detectExpansionPacks(installDir) {
1561
1623
  const expansionPacks = {};
1562
1624
  const glob = require("glob");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmad-method",
3
- "version": "4.35.2",
3
+ "version": "4.36.0",
4
4
  "description": "BMad Method installer - AI-powered Agile development framework",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {
@@ -0,0 +1,105 @@
1
+ // ASCII banner art definitions extracted from banners.js to separate art from logic
2
+
3
+ const BMAD_TITLE = "BMAD-METHOD";
4
+ const FLATTENER_TITLE = "FLATTENER";
5
+ const INSTALLER_TITLE = "INSTALLER";
6
+
7
+ // Large ASCII blocks (block-style fonts)
8
+ const BMAD_LARGE = `
9
+ ██████╗ ███╗ ███╗ █████╗ ██████╗ ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗
10
+ ██╔══██╗████╗ ████║██╔══██╗██╔══██╗ ████╗ ████║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗
11
+ ██████╔╝██╔████╔██║███████║██║ ██║█████╗██╔████╔██║█████╗ ██║ ███████║██║ ██║██║ ██║
12
+ ██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║╚════╝██║╚██╔╝██║██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║
13
+ ██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝ ██║ ╚═╝ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝
14
+ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝
15
+ `;
16
+
17
+ const FLATTENER_LARGE = `
18
+ ███████╗██╗ █████╗ ████████╗████████╗███████╗███╗ ██╗███████╗██████╗
19
+ ██╔════╝██║ ██╔══██╗╚══██╔══╝╚══██╔══╝██╔════╝████╗ ██║██╔════╝██╔══██╗
20
+ █████╗ ██║ ███████║ ██║ ██║ █████╗ ██╔██╗ ██║█████╗ ██████╔╝
21
+ ██╔══╝ ██║ ██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║██╔══╝ ██╔══██╗
22
+ ██║ ███████║██║ ██║ ██║ ██║ ███████╗██║ ╚████║███████╗██║ ██║
23
+ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝
24
+ `;
25
+
26
+ const INSTALLER_LARGE = `
27
+ ██╗███╗ ██╗███████╗████████╗ █████╗ ██╗ ██╗ ███████╗██████╗
28
+ ██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██║ ██║ ██╔════╝██╔══██╗
29
+ ██║██╔██╗ ██║███████╗ ██║ ███████║██║ ██║ █████╗ ██████╔╝
30
+ ██║██║╚██╗██║╚════██║ ██║ ██╔══██║██║ ██║ ██╔══╝ ██╔══██╗
31
+ ██║██║ ╚████║███████║ ██║ ██║ ██║███████╗███████╗███████╗██║ ██║
32
+ ╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝
33
+ `;
34
+
35
+ // Curated medium/small/tiny variants (fixed art, no runtime scaling)
36
+ // Medium: bold framed title with heavy fill (high contrast, compact)
37
+ const BMAD_MEDIUM = `
38
+ ███╗ █╗ █╗ ██╗ ███╗ █╗ █╗███╗█████╗█╗ █╗ ██╗ ███╗
39
+ █╔═█╗██╗ ██║█╔═█╗█╔═█╗ ██╗ ██║█╔═╝╚═█╔═╝█║ █║█╔═█╗█╔═█╗
40
+ ███╔╝█╔███╔█║████║█║ █║██╗█╔███╔█║██╗ █║ ████║█║ █║█║ █║
41
+ █╔═█╗█║ █╔╝█║█╔═█║█║ █║╚═╝█║ █╔╝█║█╔╝ █║ █╔═█║█║ █║█║ █║
42
+ ███╔╝█║ ╚╝ █║█║ █║███╔╝ █║ ╚╝ █║███╗ █║ █║ █║╚██╔╝███╔╝
43
+ ╚══╝ ╚╝ ╚╝╚╝ ╚╝╚══╝ ╚╝ ╚╝╚══╝ ╚╝ ╚╝ ╚╝ ╚═╝ ╚══╝
44
+ `;
45
+
46
+ const FLATTENER_MEDIUM = `
47
+ ███╗█╗ ██╗ █████╗█████╗███╗█╗ █╗███╗███╗
48
+ █╔═╝█║ █╔═█╗╚═█╔═╝╚═█╔═╝█╔═╝██╗ █║█╔═╝█╔═█╗
49
+ ██╗ █║ ████║ █║ █║ ██╗ █╔█╗█║██╗ ███╔╝
50
+ █╔╝ █║ █╔═█║ █║ █║ █╔╝ █║ ██║█╔╝ █╔═█╗
51
+ █║ ███║█║ █║ █║ █║ ███╗█║ █║███╗█║ █║
52
+ ╚╝ ╚══╝╚╝ ╚╝ ╚╝ ╚╝ ╚══╝╚╝ ╚╝╚══╝╚╝ ╚╝
53
+ `;
54
+
55
+ const INSTALLER_MEDIUM = `
56
+ █╗█╗ █╗████╗█████╗ ██╗ █╗ █╗ ███╗███╗
57
+ █║██╗ █║█╔══╝╚═█╔═╝█╔═█╗█║ █║ █╔═╝█╔═█╗
58
+ █║█╔█╗█║████╗ █║ ████║█║ █║ ██╗ ███╔╝
59
+ █║█║ ██║╚══█║ █║ █╔═█║█║ █║ █╔╝ █╔═█╗
60
+ █║█║ █║████║ █║ █║ █║███╗███╗███╗█║ █║
61
+ ╚╝╚╝ ╚╝╚═══╝ ╚╝ ╚╝ ╚╝╚══╝╚══╝╚══╝╚╝ ╚╝
62
+ `;
63
+
64
+ // Small: rounded box with bold rule
65
+ // Width: 30 columns total (28 inner)
66
+ const BMAD_SMALL = `
67
+ ╭──────────────────────────╮
68
+ │ BMAD-METHOD │
69
+ ╰──────────────────────────╯
70
+ `;
71
+
72
+ const FLATTENER_SMALL = `
73
+ ╭──────────────────────────╮
74
+ │ FLATTENER │
75
+ ╰──────────────────────────╯
76
+ `;
77
+
78
+ const INSTALLER_SMALL = `
79
+ ╭──────────────────────────╮
80
+ │ INSTALLER │
81
+ ╰──────────────────────────╯
82
+ `;
83
+
84
+ // Tiny (compact brackets)
85
+ const BMAD_TINY = `[ BMAD-METHOD ]`;
86
+ const FLATTENER_TINY = `[ FLATTENER ]`;
87
+ const INSTALLER_TINY = `[ INSTALLER ]`;
88
+
89
+ module.exports = {
90
+ BMAD_TITLE,
91
+ FLATTENER_TITLE,
92
+ INSTALLER_TITLE,
93
+ BMAD_LARGE,
94
+ FLATTENER_LARGE,
95
+ INSTALLER_LARGE,
96
+ BMAD_MEDIUM,
97
+ FLATTENER_MEDIUM,
98
+ INSTALLER_MEDIUM,
99
+ BMAD_SMALL,
100
+ FLATTENER_SMALL,
101
+ INSTALLER_SMALL,
102
+ BMAD_TINY,
103
+ FLATTENER_TINY,
104
+ INSTALLER_TINY,
105
+ };