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.
- package/.claude/skills/changelog-social/SKILL.md +178 -0
- package/.claude/skills/changelog-social/examples/discord-example.md +53 -0
- package/.claude/skills/changelog-social/examples/linkedin-example.md +49 -0
- package/.claude/skills/changelog-social/examples/twitter-example.md +55 -0
- package/.claude/skills/changelog-social.skill +0 -0
- package/.claude/skills/draft-changelog/SKILL.md +2 -2
- package/.claude/skills/draft-changelog/prompts/instructions.md +25 -1
- package/.claude/skills/gh-triage/SKILL.md +1 -1
- package/.claude/skills/release-module/SKILL.md +1 -1
- package/.claude/skills/release-module/prompts/instructions.md +0 -4
- package/.github/workflows/quality.yaml +3 -0
- package/.vscode/settings.json +1 -2
- package/CHANGELOG.md +65 -1
- package/docs/bmgd/game-types.md +1 -0
- package/docs/bmgd/index.md +1 -1
- package/docs/bmgd/quick-flow-workflows.md +1 -0
- package/docs/reference/agents.md +22 -0
- package/docs/reference/commands.md +34 -0
- package/docs/reference/testing.md +21 -0
- package/docs/reference/workflow-map.md +1 -1
- package/package.json +5 -2
- package/src/bmm/agents/analyst.agent.yaml +11 -5
- package/src/bmm/agents/dev.agent.yaml +0 -1
- package/src/bmm/agents/pm.agent.yaml +3 -6
- package/src/bmm/agents/{quinn.agent.yaml → qa.agent.yaml} +3 -3
- package/src/bmm/module-help.csv +13 -20
- package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +0 -1
- package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -0
- package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -0
- package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/domain-complexity.csv +2 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +63 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +65 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +0 -1
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +0 -1
- package/src/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +2 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +0 -1
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +0 -1
- package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +0 -3
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +0 -4
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +0 -4
- package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +0 -4
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +0 -3
- package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +0 -4
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +0 -6
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +0 -1
- package/src/bmm/workflows/document-project/workflow.yaml +0 -8
- package/src/bmm/workflows/qa/automate/workflow.yaml +0 -2
- package/src/core/tasks/editorial-review-prose.xml +14 -12
- package/src/core/tasks/editorial-review-structure.xml +28 -28
- package/src/core/tasks/help.md +36 -16
- package/src/core/tasks/index-docs.xml +1 -1
- package/src/core/tasks/review-adversarial-general.xml +2 -2
- package/src/core/tasks/shard-doc.xml +1 -2
- package/src/core/tasks/workflow.xml +1 -1
- package/src/core/workflows/advanced-elicitation/workflow.xml +1 -1
- package/test/test-installation-components.js +1 -1
- package/tools/build-docs.js +21 -1
- package/tools/cli/bmad-cli.js +42 -3
- package/tools/cli/installers/lib/core/config-collector.js +5 -1
- package/tools/cli/installers/lib/core/dependency-resolver.js +1 -1
- package/tools/cli/installers/lib/core/installer.js +1 -6
- package/tools/cli/installers/lib/core/manifest-generator.js +149 -125
- package/tools/cli/installers/lib/custom/handler.js +1 -1
- package/tools/cli/installers/lib/ide/_base-ide.js +25 -15
- package/tools/cli/installers/lib/ide/_config-driven.js +79 -9
- package/tools/cli/installers/lib/ide/codex.js +7 -1
- package/tools/cli/installers/lib/ide/manager.js +8 -2
- package/tools/cli/installers/lib/ide/platform-codes.yaml +0 -3
- package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +2 -2
- package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +14 -3
- package/tools/cli/installers/lib/ide/shared/path-utils.js +9 -2
- package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +126 -29
- package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +2 -2
- package/tools/cli/installers/lib/ide/templates/combined/default-task.md +10 -0
- package/tools/cli/installers/lib/ide/templates/combined/default-tool.md +10 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml +11 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml +11 -0
- package/tools/cli/installers/lib/modules/manager.js +3 -6
- package/tools/cli/lib/agent/installer.js +1 -1
- package/tools/cli/lib/prompts.js +192 -6
- package/tools/cli/lib/ui.js +124 -72
- package/tools/schema/agent.js +0 -1
- package/tools/validate-file-refs.js +480 -0
- package/website/astro.config.mjs +1 -35
- package/src/bmm/workflows/1-analysis/research/workflow.md +0 -173
- package/src/bmm/workflows/2-plan-workflows/create-prd/validation-report-prd-workflow.md +0 -433
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow.md +0 -150
- package/src/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-library.json +0 -90
- package/src/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-templates.yaml +0 -127
- package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/checklist.md +0 -39
- package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/instructions.md +0 -130
- package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml +0 -27
- package/src/bmm/workflows/excalidraw-diagrams/create-diagram/checklist.md +0 -43
- package/src/bmm/workflows/excalidraw-diagrams/create-diagram/instructions.md +0 -141
- package/src/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml +0 -27
- package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/checklist.md +0 -49
- package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/instructions.md +0 -241
- package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml +0 -27
- package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/checklist.md +0 -38
- package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/instructions.md +0 -133
- package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml +0 -27
- package/src/core/resources/excalidraw/README.md +0 -160
- package/src/core/resources/excalidraw/excalidraw-helpers.md +0 -127
- package/src/core/resources/excalidraw/library-loader.md +0 -50
- package/src/core/resources/excalidraw/validate-json-instructions.md +0 -79
- package/tools/flattener/aggregate.js +0 -76
- package/tools/flattener/binary.js +0 -80
- package/tools/flattener/discovery.js +0 -71
- package/tools/flattener/files.js +0 -35
- package/tools/flattener/ignoreRules.js +0 -172
- package/tools/flattener/main.js +0 -483
- package/tools/flattener/projectRoot.js +0 -201
- package/tools/flattener/prompts.js +0 -44
- package/tools/flattener/stats.helpers.js +0 -368
- package/tools/flattener/stats.js +0 -75
- package/tools/flattener/test-matrix.js +0 -409
- 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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
417
|
-
standalone =
|
|
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
|
|
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
|
-
|
|
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
|
|
506
|
-
standalone =
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
|
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
|
-
|
|
814
|
-
|
|
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 [,
|
|
820
|
-
|
|
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,
|
|
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
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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
|
|
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,
|
|
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 [,
|
|
874
|
-
|
|
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,
|
|
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
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
|
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,
|
|
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 [,
|
|
928
|
-
|
|
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,
|
|
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 =
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
451
|
-
const
|
|
452
|
-
standalone =
|
|
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
|
-
//
|
|
455
|
-
const frontmatterMatch = content.match(/^---\
|
|
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
|
|
469
|
+
standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
|
|
461
470
|
} catch {
|
|
462
|
-
//
|
|
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,
|
|
468
|
-
standalone =
|
|
477
|
+
// If we can't read the file, default to standalone
|
|
478
|
+
standalone = true;
|
|
469
479
|
}
|
|
470
480
|
|
|
471
481
|
files.push({
|