bmad-method 6.0.0-Beta.5 → 6.0.0-Beta.7

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 (119) hide show
  1. package/.claude/skills/changelog-social/SKILL.md +178 -0
  2. package/.claude/skills/changelog-social/examples/discord-example.md +53 -0
  3. package/.claude/skills/changelog-social/examples/linkedin-example.md +49 -0
  4. package/.claude/skills/changelog-social/examples/twitter-example.md +55 -0
  5. package/.claude/skills/changelog-social.skill +0 -0
  6. package/.claude/skills/draft-changelog/SKILL.md +2 -2
  7. package/.claude/skills/draft-changelog/prompts/instructions.md +25 -1
  8. package/.claude/skills/gh-triage/SKILL.md +1 -1
  9. package/.claude/skills/release-module/SKILL.md +1 -1
  10. package/.claude/skills/release-module/prompts/instructions.md +0 -4
  11. package/.github/workflows/quality.yaml +3 -0
  12. package/.vscode/settings.json +1 -2
  13. package/CHANGELOG.md +65 -1
  14. package/docs/bmgd/game-types.md +1 -0
  15. package/docs/bmgd/index.md +1 -1
  16. package/docs/bmgd/quick-flow-workflows.md +1 -0
  17. package/docs/reference/agents.md +22 -0
  18. package/docs/reference/commands.md +34 -0
  19. package/docs/reference/testing.md +21 -0
  20. package/docs/reference/workflow-map.md +1 -1
  21. package/package.json +5 -2
  22. package/src/bmm/agents/analyst.agent.yaml +11 -5
  23. package/src/bmm/agents/dev.agent.yaml +0 -1
  24. package/src/bmm/agents/pm.agent.yaml +3 -6
  25. package/src/bmm/agents/{quinn.agent.yaml → qa.agent.yaml} +3 -3
  26. package/src/bmm/module-help.csv +13 -20
  27. package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +0 -1
  28. package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -0
  29. package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -0
  30. package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -0
  31. package/src/bmm/workflows/2-plan-workflows/create-prd/data/domain-complexity.csv +2 -0
  32. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +63 -0
  33. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -0
  34. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +65 -0
  35. package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +0 -1
  36. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +0 -1
  37. package/src/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +2 -0
  38. package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +0 -1
  39. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +0 -1
  40. package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +0 -3
  41. package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +0 -4
  42. package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +0 -4
  43. package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +0 -4
  44. package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +0 -3
  45. package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +0 -4
  46. package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +0 -6
  47. package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +0 -1
  48. package/src/bmm/workflows/document-project/workflow.yaml +0 -8
  49. package/src/bmm/workflows/qa/automate/workflow.yaml +0 -2
  50. package/src/core/tasks/editorial-review-prose.xml +14 -12
  51. package/src/core/tasks/editorial-review-structure.xml +28 -28
  52. package/src/core/tasks/help.md +36 -16
  53. package/src/core/tasks/index-docs.xml +1 -1
  54. package/src/core/tasks/review-adversarial-general.xml +2 -2
  55. package/src/core/tasks/shard-doc.xml +1 -2
  56. package/src/core/tasks/workflow.xml +1 -1
  57. package/src/core/workflows/advanced-elicitation/workflow.xml +1 -1
  58. package/test/test-installation-components.js +1 -1
  59. package/tools/build-docs.js +21 -1
  60. package/tools/cli/bmad-cli.js +42 -3
  61. package/tools/cli/installers/lib/core/config-collector.js +5 -1
  62. package/tools/cli/installers/lib/core/dependency-resolver.js +1 -1
  63. package/tools/cli/installers/lib/core/installer.js +1 -6
  64. package/tools/cli/installers/lib/core/manifest-generator.js +149 -125
  65. package/tools/cli/installers/lib/custom/handler.js +1 -1
  66. package/tools/cli/installers/lib/ide/_base-ide.js +25 -15
  67. package/tools/cli/installers/lib/ide/_config-driven.js +79 -9
  68. package/tools/cli/installers/lib/ide/codex.js +7 -1
  69. package/tools/cli/installers/lib/ide/manager.js +8 -2
  70. package/tools/cli/installers/lib/ide/platform-codes.yaml +0 -3
  71. package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +2 -2
  72. package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +14 -3
  73. package/tools/cli/installers/lib/ide/shared/path-utils.js +9 -2
  74. package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +126 -29
  75. package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +2 -2
  76. package/tools/cli/installers/lib/ide/templates/combined/default-task.md +10 -0
  77. package/tools/cli/installers/lib/ide/templates/combined/default-tool.md +10 -0
  78. package/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml +11 -0
  79. package/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml +11 -0
  80. package/tools/cli/installers/lib/modules/manager.js +3 -6
  81. package/tools/cli/lib/agent/installer.js +1 -1
  82. package/tools/cli/lib/prompts.js +192 -6
  83. package/tools/cli/lib/ui.js +124 -72
  84. package/tools/schema/agent.js +0 -1
  85. package/tools/validate-file-refs.js +480 -0
  86. package/website/astro.config.mjs +1 -35
  87. package/src/bmm/workflows/1-analysis/research/workflow.md +0 -173
  88. package/src/bmm/workflows/2-plan-workflows/create-prd/validation-report-prd-workflow.md +0 -433
  89. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow.md +0 -150
  90. package/src/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-library.json +0 -90
  91. package/src/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-templates.yaml +0 -127
  92. package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/checklist.md +0 -39
  93. package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/instructions.md +0 -130
  94. package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml +0 -27
  95. package/src/bmm/workflows/excalidraw-diagrams/create-diagram/checklist.md +0 -43
  96. package/src/bmm/workflows/excalidraw-diagrams/create-diagram/instructions.md +0 -141
  97. package/src/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml +0 -27
  98. package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/checklist.md +0 -49
  99. package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/instructions.md +0 -241
  100. package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml +0 -27
  101. package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/checklist.md +0 -38
  102. package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/instructions.md +0 -133
  103. package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml +0 -27
  104. package/src/core/resources/excalidraw/README.md +0 -160
  105. package/src/core/resources/excalidraw/excalidraw-helpers.md +0 -127
  106. package/src/core/resources/excalidraw/library-loader.md +0 -50
  107. package/src/core/resources/excalidraw/validate-json-instructions.md +0 -79
  108. package/tools/flattener/aggregate.js +0 -76
  109. package/tools/flattener/binary.js +0 -80
  110. package/tools/flattener/discovery.js +0 -71
  111. package/tools/flattener/files.js +0 -35
  112. package/tools/flattener/ignoreRules.js +0 -172
  113. package/tools/flattener/main.js +0 -483
  114. package/tools/flattener/projectRoot.js +0 -201
  115. package/tools/flattener/prompts.js +0 -44
  116. package/tools/flattener/stats.helpers.js +0 -368
  117. package/tools/flattener/stats.js +0 -75
  118. package/tools/flattener/test-matrix.js +0 -409
  119. package/tools/flattener/xml.js +0 -82
@@ -2,6 +2,7 @@ const path = require('node:path');
2
2
  const fs = require('fs-extra');
3
3
  const yaml = require('yaml');
4
4
  const crypto = require('node:crypto');
5
+ const csv = require('csv-parse/sync');
5
6
  const { getSourcePath, getModulePath } = require('../../../lib/project-root');
6
7
 
7
8
  // Load package.json for version info
@@ -21,6 +22,19 @@ class ManifestGenerator {
21
22
  this.selectedIdes = [];
22
23
  }
23
24
 
25
+ /**
26
+ * Clean text for CSV output by normalizing whitespace and escaping quotes
27
+ * @param {string} text - Text to clean
28
+ * @returns {string} Cleaned text safe for CSV
29
+ */
30
+ cleanForCSV(text) {
31
+ if (!text) return '';
32
+ return text
33
+ .trim()
34
+ .replaceAll(/\s+/g, ' ') // Normalize all whitespace (including newlines) to single space
35
+ .replaceAll('"', '""'); // Escape quotes for CSV
36
+ }
37
+
24
38
  /**
25
39
  * Generate all manifests for the installation
26
40
  * @param {string} bmadDir - _bmad
@@ -145,7 +159,11 @@ class ManifestGenerator {
145
159
  // Recurse into subdirectories
146
160
  const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
147
161
  await findWorkflows(fullPath, newRelativePath);
148
- } else if (entry.name === 'workflow.yaml' || entry.name === 'workflow.md') {
162
+ } else if (
163
+ entry.name === 'workflow.yaml' ||
164
+ entry.name === 'workflow.md' ||
165
+ (entry.name.startsWith('workflow-') && entry.name.endsWith('.md'))
166
+ ) {
149
167
  // Parse workflow file (both YAML and MD formats)
150
168
  if (debug) {
151
169
  console.log(`[DEBUG] Found workflow file: ${fullPath}`);
@@ -161,7 +179,7 @@ class ManifestGenerator {
161
179
  workflow = yaml.parse(content);
162
180
  } else {
163
181
  // Parse MD workflow with YAML frontmatter
164
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
182
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
165
183
  if (!frontmatterMatch) {
166
184
  if (debug) {
167
185
  console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`);
@@ -201,7 +219,7 @@ class ManifestGenerator {
201
219
  // Workflows with standalone: false are filtered out above
202
220
  workflows.push({
203
221
  name: workflow.name,
204
- description: workflow.description.replaceAll('"', '""'), // Escape quotes for CSV
222
+ description: this.cleanForCSV(workflow.description),
205
223
  module: moduleName,
206
224
  path: installPath,
207
225
  });
@@ -319,24 +337,15 @@ class ManifestGenerator {
319
337
 
320
338
  const agentName = entry.name.replace('.md', '');
321
339
 
322
- // Helper function to clean and escape CSV content
323
- const cleanForCSV = (text) => {
324
- if (!text) return '';
325
- return text
326
- .trim()
327
- .replaceAll(/\s+/g, ' ') // Normalize whitespace
328
- .replaceAll('"', '""'); // Escape quotes for CSV
329
- };
330
-
331
340
  agents.push({
332
341
  name: agentName,
333
342
  displayName: nameMatch ? nameMatch[1] : agentName,
334
343
  title: titleMatch ? titleMatch[1] : '',
335
344
  icon: iconMatch ? iconMatch[1] : '',
336
- role: roleMatch ? cleanForCSV(roleMatch[1]) : '',
337
- identity: identityMatch ? cleanForCSV(identityMatch[1]) : '',
338
- communicationStyle: styleMatch ? cleanForCSV(styleMatch[1]) : '',
339
- principles: principlesMatch ? cleanForCSV(principlesMatch[1]) : '',
345
+ role: roleMatch ? this.cleanForCSV(roleMatch[1]) : '',
346
+ identity: identityMatch ? this.cleanForCSV(identityMatch[1]) : '',
347
+ communicationStyle: styleMatch ? this.cleanForCSV(styleMatch[1]) : '',
348
+ principles: principlesMatch ? this.cleanForCSV(principlesMatch[1]) : '',
340
349
  module: moduleName,
341
350
  path: installPath,
342
351
  });
@@ -385,6 +394,11 @@ class ManifestGenerator {
385
394
  const filePath = path.join(dirPath, file);
386
395
  const content = await fs.readFile(filePath, 'utf8');
387
396
 
397
+ // Skip internal/engine files (not user-facing tasks)
398
+ if (content.includes('internal="true"')) {
399
+ continue;
400
+ }
401
+
388
402
  let name = file.replace(/\.(xml|md)$/, '');
389
403
  let displayName = name;
390
404
  let description = '';
@@ -392,17 +406,21 @@ class ManifestGenerator {
392
406
 
393
407
  if (file.endsWith('.md')) {
394
408
  // Parse YAML frontmatter for .md tasks
395
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
409
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
396
410
  if (frontmatterMatch) {
397
411
  try {
398
412
  const frontmatter = yaml.parse(frontmatterMatch[1]);
399
413
  name = frontmatter.name || name;
400
414
  displayName = frontmatter.displayName || frontmatter.name || name;
401
- description = frontmatter.description || '';
402
- standalone = frontmatter.standalone === true || frontmatter.standalone === 'true';
415
+ description = this.cleanForCSV(frontmatter.description || '');
416
+ // Tasks are standalone by default unless explicitly false (internal=true is already filtered above)
417
+ standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
403
418
  } catch {
404
419
  // If YAML parsing fails, use defaults
420
+ standalone = true; // Default to standalone
405
421
  }
422
+ } else {
423
+ standalone = true; // No frontmatter means standalone
406
424
  }
407
425
  } else {
408
426
  // For .xml tasks, extract from tag attributes
@@ -411,10 +429,10 @@ class ManifestGenerator {
411
429
 
412
430
  const descMatch = content.match(/description="([^"]+)"/);
413
431
  const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
414
- description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '';
432
+ description = this.cleanForCSV(descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '');
415
433
 
416
- const standaloneMatch = content.match(/<task[^>]+standalone="true"/);
417
- standalone = !!standaloneMatch;
434
+ const standaloneFalseMatch = content.match(/<task[^>]+standalone="false"/);
435
+ standalone = !standaloneFalseMatch;
418
436
  }
419
437
 
420
438
  // Build relative path for installation
@@ -424,7 +442,7 @@ class ManifestGenerator {
424
442
  tasks.push({
425
443
  name: name,
426
444
  displayName: displayName,
427
- description: description.replaceAll('"', '""'),
445
+ description: description,
428
446
  module: moduleName,
429
447
  path: installPath,
430
448
  standalone: standalone,
@@ -474,6 +492,11 @@ class ManifestGenerator {
474
492
  const filePath = path.join(dirPath, file);
475
493
  const content = await fs.readFile(filePath, 'utf8');
476
494
 
495
+ // Skip internal tools (same as tasks)
496
+ if (content.includes('internal="true"')) {
497
+ continue;
498
+ }
499
+
477
500
  let name = file.replace(/\.(xml|md)$/, '');
478
501
  let displayName = name;
479
502
  let description = '';
@@ -481,17 +504,21 @@ class ManifestGenerator {
481
504
 
482
505
  if (file.endsWith('.md')) {
483
506
  // Parse YAML frontmatter for .md tools
484
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
507
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
485
508
  if (frontmatterMatch) {
486
509
  try {
487
510
  const frontmatter = yaml.parse(frontmatterMatch[1]);
488
511
  name = frontmatter.name || name;
489
512
  displayName = frontmatter.displayName || frontmatter.name || name;
490
- description = frontmatter.description || '';
491
- standalone = frontmatter.standalone === true || frontmatter.standalone === 'true';
513
+ description = this.cleanForCSV(frontmatter.description || '');
514
+ // Tools are standalone by default unless explicitly false (internal=true is already filtered above)
515
+ standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
492
516
  } catch {
493
517
  // If YAML parsing fails, use defaults
518
+ standalone = true; // Default to standalone
494
519
  }
520
+ } else {
521
+ standalone = true; // No frontmatter means standalone
495
522
  }
496
523
  } else {
497
524
  // For .xml tools, extract from tag attributes
@@ -500,10 +527,10 @@ class ManifestGenerator {
500
527
 
501
528
  const descMatch = content.match(/description="([^"]+)"/);
502
529
  const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
503
- description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '';
530
+ description = this.cleanForCSV(descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '');
504
531
 
505
- const standaloneMatch = content.match(/<tool[^>]+standalone="true"/);
506
- standalone = !!standaloneMatch;
532
+ const standaloneFalseMatch = content.match(/<tool[^>]+standalone="false"/);
533
+ standalone = !standaloneFalseMatch;
507
534
  }
508
535
 
509
536
  // Build relative path for installation
@@ -513,7 +540,7 @@ class ManifestGenerator {
513
540
  tools.push({
514
541
  name: name,
515
542
  displayName: displayName,
516
- description: description.replaceAll('"', '""'),
543
+ description: description,
517
544
  module: moduleName,
518
545
  path: installPath,
519
546
  standalone: standalone,
@@ -706,47 +733,15 @@ class ManifestGenerator {
706
733
  async writeWorkflowManifest(cfgDir) {
707
734
  const csvPath = path.join(cfgDir, 'workflow-manifest.csv');
708
735
  const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
709
- const parseCsvLine = (line) => {
710
- const columns = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
711
- return columns.map((c) => c.replaceAll(/^"|"$/g, ''));
712
- };
713
-
714
- // Read existing manifest to preserve entries
715
- const existingEntries = new Map();
716
- if (await fs.pathExists(csvPath)) {
717
- const content = await fs.readFile(csvPath, 'utf8');
718
- const lines = content.split('\n').filter((line) => line.trim());
719
-
720
- // Skip header
721
- for (let i = 1; i < lines.length; i++) {
722
- const line = lines[i];
723
- if (line) {
724
- const parts = parseCsvLine(line);
725
- if (parts.length >= 4) {
726
- const [name, description, module, workflowPath] = parts;
727
- existingEntries.set(`${module}:${name}`, {
728
- name,
729
- description,
730
- module,
731
- path: workflowPath,
732
- });
733
- }
734
- }
735
- }
736
- }
737
736
 
738
737
  // Create CSV header - standalone column removed, everything is canonicalized to 4 columns
739
738
  let csv = 'name,description,module,path\n';
740
739
 
741
- // Combine existing and new workflows
740
+ // Build workflows map from discovered workflows only
741
+ // Old entries are NOT preserved - the manifest reflects what actually exists on disk
742
742
  const allWorkflows = new Map();
743
743
 
744
- // Add existing entries
745
- for (const [key, value] of existingEntries) {
746
- allWorkflows.set(key, value);
747
- }
748
-
749
- // Add/update new workflows
744
+ // Only add workflows that were actually discovered in this scan
750
745
  for (const workflow of this.workflows) {
751
746
  const key = `${workflow.module}:${workflow.name}`;
752
747
  allWorkflows.set(key, {
@@ -773,30 +768,23 @@ class ManifestGenerator {
773
768
  */
774
769
  async writeAgentManifest(cfgDir) {
775
770
  const csvPath = path.join(cfgDir, 'agent-manifest.csv');
771
+ const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
776
772
 
777
773
  // Read existing manifest to preserve entries
778
774
  const existingEntries = new Map();
779
775
  if (await fs.pathExists(csvPath)) {
780
776
  const content = await fs.readFile(csvPath, 'utf8');
781
- const lines = content.split('\n').filter((line) => line.trim());
782
-
783
- // Skip header
784
- for (let i = 1; i < lines.length; i++) {
785
- const line = lines[i];
786
- if (line) {
787
- // Parse CSV (simple parsing assuming no commas in quoted fields)
788
- const parts = line.split('","');
789
- if (parts.length >= 11) {
790
- const name = parts[0].replace(/^"/, '');
791
- const module = parts[8];
792
- existingEntries.set(`${module}:${name}`, line);
793
- }
794
- }
777
+ const records = csv.parse(content, {
778
+ columns: true,
779
+ skip_empty_lines: true,
780
+ });
781
+ for (const record of records) {
782
+ existingEntries.set(`${record.module}:${record.name}`, record);
795
783
  }
796
784
  }
797
785
 
798
786
  // Create CSV header with persona fields
799
- let csv = 'name,displayName,title,icon,role,identity,communicationStyle,principles,module,path\n';
787
+ let csvContent = 'name,displayName,title,icon,role,identity,communicationStyle,principles,module,path\n';
800
788
 
801
789
  // Combine existing and new agents, preferring new data for duplicates
802
790
  const allAgents = new Map();
@@ -809,18 +797,38 @@ class ManifestGenerator {
809
797
  // Add/update new agents
810
798
  for (const agent of this.agents) {
811
799
  const key = `${agent.module}:${agent.name}`;
812
- allAgents.set(
813
- key,
814
- `"${agent.name}","${agent.displayName}","${agent.title}","${agent.icon}","${agent.role}","${agent.identity}","${agent.communicationStyle}","${agent.principles}","${agent.module}","${agent.path}"`,
815
- );
800
+ allAgents.set(key, {
801
+ name: agent.name,
802
+ displayName: agent.displayName,
803
+ title: agent.title,
804
+ icon: agent.icon,
805
+ role: agent.role,
806
+ identity: agent.identity,
807
+ communicationStyle: agent.communicationStyle,
808
+ principles: agent.principles,
809
+ module: agent.module,
810
+ path: agent.path,
811
+ });
816
812
  }
817
813
 
818
814
  // Write all agents
819
- for (const [, value] of allAgents) {
820
- csv += value + '\n';
815
+ for (const [, record] of allAgents) {
816
+ const row = [
817
+ escapeCsv(record.name),
818
+ escapeCsv(record.displayName),
819
+ escapeCsv(record.title),
820
+ escapeCsv(record.icon),
821
+ escapeCsv(record.role),
822
+ escapeCsv(record.identity),
823
+ escapeCsv(record.communicationStyle),
824
+ escapeCsv(record.principles),
825
+ escapeCsv(record.module),
826
+ escapeCsv(record.path),
827
+ ].join(',');
828
+ csvContent += row + '\n';
821
829
  }
822
830
 
823
- await fs.writeFile(csvPath, csv);
831
+ await fs.writeFile(csvPath, csvContent);
824
832
  return csvPath;
825
833
  }
826
834
 
@@ -830,30 +838,23 @@ class ManifestGenerator {
830
838
  */
831
839
  async writeTaskManifest(cfgDir) {
832
840
  const csvPath = path.join(cfgDir, 'task-manifest.csv');
841
+ const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
833
842
 
834
843
  // Read existing manifest to preserve entries
835
844
  const existingEntries = new Map();
836
845
  if (await fs.pathExists(csvPath)) {
837
846
  const content = await fs.readFile(csvPath, 'utf8');
838
- const lines = content.split('\n').filter((line) => line.trim());
839
-
840
- // Skip header
841
- for (let i = 1; i < lines.length; i++) {
842
- const line = lines[i];
843
- if (line) {
844
- // Parse CSV (simple parsing assuming no commas in quoted fields)
845
- const parts = line.split('","');
846
- if (parts.length >= 6) {
847
- const name = parts[0].replace(/^"/, '');
848
- const module = parts[3];
849
- existingEntries.set(`${module}:${name}`, line);
850
- }
851
- }
847
+ const records = csv.parse(content, {
848
+ columns: true,
849
+ skip_empty_lines: true,
850
+ });
851
+ for (const record of records) {
852
+ existingEntries.set(`${record.module}:${record.name}`, record);
852
853
  }
853
854
  }
854
855
 
855
856
  // Create CSV header with standalone column
856
- let csv = 'name,displayName,description,module,path,standalone\n';
857
+ let csvContent = 'name,displayName,description,module,path,standalone\n';
857
858
 
858
859
  // Combine existing and new tasks
859
860
  const allTasks = new Map();
@@ -866,15 +867,30 @@ class ManifestGenerator {
866
867
  // Add/update new tasks
867
868
  for (const task of this.tasks) {
868
869
  const key = `${task.module}:${task.name}`;
869
- allTasks.set(key, `"${task.name}","${task.displayName}","${task.description}","${task.module}","${task.path}","${task.standalone}"`);
870
+ allTasks.set(key, {
871
+ name: task.name,
872
+ displayName: task.displayName,
873
+ description: task.description,
874
+ module: task.module,
875
+ path: task.path,
876
+ standalone: task.standalone,
877
+ });
870
878
  }
871
879
 
872
880
  // Write all tasks
873
- for (const [, value] of allTasks) {
874
- csv += value + '\n';
881
+ for (const [, record] of allTasks) {
882
+ const row = [
883
+ escapeCsv(record.name),
884
+ escapeCsv(record.displayName),
885
+ escapeCsv(record.description),
886
+ escapeCsv(record.module),
887
+ escapeCsv(record.path),
888
+ escapeCsv(record.standalone),
889
+ ].join(',');
890
+ csvContent += row + '\n';
875
891
  }
876
892
 
877
- await fs.writeFile(csvPath, csv);
893
+ await fs.writeFile(csvPath, csvContent);
878
894
  return csvPath;
879
895
  }
880
896
 
@@ -884,30 +900,23 @@ class ManifestGenerator {
884
900
  */
885
901
  async writeToolManifest(cfgDir) {
886
902
  const csvPath = path.join(cfgDir, 'tool-manifest.csv');
903
+ const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
887
904
 
888
905
  // Read existing manifest to preserve entries
889
906
  const existingEntries = new Map();
890
907
  if (await fs.pathExists(csvPath)) {
891
908
  const content = await fs.readFile(csvPath, 'utf8');
892
- const lines = content.split('\n').filter((line) => line.trim());
893
-
894
- // Skip header
895
- for (let i = 1; i < lines.length; i++) {
896
- const line = lines[i];
897
- if (line) {
898
- // Parse CSV (simple parsing assuming no commas in quoted fields)
899
- const parts = line.split('","');
900
- if (parts.length >= 6) {
901
- const name = parts[0].replace(/^"/, '');
902
- const module = parts[3];
903
- existingEntries.set(`${module}:${name}`, line);
904
- }
905
- }
909
+ const records = csv.parse(content, {
910
+ columns: true,
911
+ skip_empty_lines: true,
912
+ });
913
+ for (const record of records) {
914
+ existingEntries.set(`${record.module}:${record.name}`, record);
906
915
  }
907
916
  }
908
917
 
909
918
  // Create CSV header with standalone column
910
- let csv = 'name,displayName,description,module,path,standalone\n';
919
+ let csvContent = 'name,displayName,description,module,path,standalone\n';
911
920
 
912
921
  // Combine existing and new tools
913
922
  const allTools = new Map();
@@ -920,15 +929,30 @@ class ManifestGenerator {
920
929
  // Add/update new tools
921
930
  for (const tool of this.tools) {
922
931
  const key = `${tool.module}:${tool.name}`;
923
- allTools.set(key, `"${tool.name}","${tool.displayName}","${tool.description}","${tool.module}","${tool.path}","${tool.standalone}"`);
932
+ allTools.set(key, {
933
+ name: tool.name,
934
+ displayName: tool.displayName,
935
+ description: tool.description,
936
+ module: tool.module,
937
+ path: tool.path,
938
+ standalone: tool.standalone,
939
+ });
924
940
  }
925
941
 
926
942
  // Write all tools
927
- for (const [, value] of allTools) {
928
- csv += value + '\n';
943
+ for (const [, record] of allTools) {
944
+ const row = [
945
+ escapeCsv(record.name),
946
+ escapeCsv(record.displayName),
947
+ escapeCsv(record.description),
948
+ escapeCsv(record.module),
949
+ escapeCsv(record.path),
950
+ escapeCsv(record.standalone),
951
+ ].join(',');
952
+ csvContent += row + '\n';
929
953
  }
930
954
 
931
- await fs.writeFile(csvPath, csv);
955
+ await fs.writeFile(csvPath, csvContent);
932
956
  return csvPath;
933
957
  }
934
958
 
@@ -297,7 +297,7 @@ class CustomHandler {
297
297
  const agentFiles = await this.findFilesRecursively(sourceAgentsPath, ['.agent.yaml']);
298
298
 
299
299
  for (const agentFile of agentFiles) {
300
- const relativePath = path.relative(sourceAgentsPath, agentFile);
300
+ const relativePath = path.relative(sourceAgentsPath, agentFile).split(path.sep).join('/');
301
301
  const targetDir = path.join(targetAgentsPath, path.dirname(relativePath));
302
302
 
303
303
  await fs.ensureDir(targetDir);
@@ -3,6 +3,7 @@ const fs = require('fs-extra');
3
3
  const chalk = require('chalk');
4
4
  const { XmlHandler } = require('../../../lib/xml-handler');
5
5
  const { getSourcePath } = require('../../../lib/project-root');
6
+ const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
6
7
 
7
8
  /**
8
9
  * Base class for IDE-specific setup
@@ -18,7 +19,7 @@ class BaseIdeSetup {
18
19
  this.configFile = null; // Override in subclasses when detection is file-based
19
20
  this.detectionPaths = []; // Additional paths that indicate the IDE is configured
20
21
  this.xmlHandler = new XmlHandler();
21
- this.bmadFolderName = 'bmad'; // Default, can be overridden
22
+ this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden
22
23
  }
23
24
 
24
25
  /**
@@ -57,7 +58,7 @@ class BaseIdeSetup {
57
58
  if (this.configDir) {
58
59
  const configPath = path.join(projectDir, this.configDir);
59
60
  if (await fs.pathExists(configPath)) {
60
- const bmadRulesPath = path.join(configPath, 'bmad');
61
+ const bmadRulesPath = path.join(configPath, BMAD_FOLDER_NAME);
61
62
  if (await fs.pathExists(bmadRulesPath)) {
62
63
  await fs.remove(bmadRulesPath);
63
64
  console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
@@ -351,13 +352,15 @@ class BaseIdeSetup {
351
352
  const workflowData = yaml.parse(content);
352
353
 
353
354
  if (workflowData && workflowData.name) {
355
+ // Workflows are standalone by default unless explicitly false
356
+ const standalone = workflowData.standalone !== false && workflowData.standalone !== 'false';
354
357
  workflows.push({
355
358
  name: workflowData.name,
356
359
  path: fullPath,
357
360
  relativePath: path.relative(dir, fullPath),
358
361
  filename: entry.name,
359
362
  description: workflowData.description || '',
360
- standalone: workflowData.standalone === true, // Check standalone property
363
+ standalone: standalone,
361
364
  });
362
365
  }
363
366
  } catch {
@@ -441,31 +444,38 @@ class BaseIdeSetup {
441
444
  const matchedExt = extensions.find((e) => entry.name.endsWith(e));
442
445
  if (matchedExt) {
443
446
  // Read file content to check for standalone attribute
444
- let standalone = false;
447
+ // All non-internal files are considered standalone by default
448
+ let standalone = true;
445
449
  try {
446
450
  const content = await fs.readFile(fullPath, 'utf8');
447
451
 
448
- // Check for standalone="true" in XML files
452
+ // Skip internal/engine files (not user-facing)
453
+ if (content.includes('internal="true"')) {
454
+ continue;
455
+ }
456
+
457
+ // Check for explicit standalone: false
449
458
  if (entry.name.endsWith('.xml')) {
450
- // Look for standalone="true" in the opening tag (task or tool)
451
- const standaloneMatch = content.match(/<(?:task|tool)[^>]+standalone="true"/);
452
- standalone = !!standaloneMatch;
459
+ // For XML files, check for standalone="false" attribute
460
+ const tagMatch = content.match(/<(task|tool)[^>]*standalone="false"/);
461
+ standalone = !tagMatch;
453
462
  } else if (entry.name.endsWith('.md')) {
454
- // Check for standalone: true in YAML frontmatter
455
- const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
463
+ // For MD files, parse YAML frontmatter
464
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
456
465
  if (frontmatterMatch) {
457
- const yaml = require('yaml');
458
466
  try {
467
+ const yaml = require('yaml');
459
468
  const frontmatter = yaml.parse(frontmatterMatch[1]);
460
- standalone = frontmatter.standalone === true;
469
+ standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
461
470
  } catch {
462
- // Ignore YAML parse errors
471
+ // If YAML parsing fails, default to standalone
463
472
  }
464
473
  }
474
+ // No frontmatter means standalone (default)
465
475
  }
466
476
  } catch {
467
- // If we can't read the file, assume not standalone
468
- standalone = false;
477
+ // If we can't read the file, default to standalone
478
+ standalone = true;
469
479
  }
470
480
 
471
481
  files.push({