Merge a69810afe5 into 04e3dc8f75
This commit is contained in:
commit
1cc8a53a8e
|
|
@ -146,7 +146,7 @@ class DependencyResolver {
|
||||||
const content = await fs.readFile(file.path, 'utf8');
|
const content = await fs.readFile(file.path, 'utf8');
|
||||||
|
|
||||||
// Parse YAML frontmatter for explicit dependencies
|
// Parse YAML frontmatter for explicit dependencies
|
||||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||||
if (frontmatterMatch) {
|
if (frontmatterMatch) {
|
||||||
try {
|
try {
|
||||||
// Pre-process to handle backticks in YAML values
|
// Pre-process to handle backticks in YAML values
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,7 @@ const { ManifestGenerator } = require('./manifest-generator');
|
||||||
const { IdeConfigManager } = require('./ide-config-manager');
|
const { IdeConfigManager } = require('./ide-config-manager');
|
||||||
const { CustomHandler } = require('../custom/handler');
|
const { CustomHandler } = require('../custom/handler');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
|
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
||||||
// BMAD installation folder name - this is constant and should never change
|
|
||||||
const BMAD_FOLDER_NAME = '_bmad';
|
|
||||||
|
|
||||||
class Installer {
|
class Installer {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ class ManifestGenerator {
|
||||||
workflow = yaml.parse(content);
|
workflow = yaml.parse(content);
|
||||||
} else {
|
} else {
|
||||||
// Parse MD workflow with YAML frontmatter
|
// Parse MD workflow with YAML frontmatter
|
||||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||||
if (!frontmatterMatch) {
|
if (!frontmatterMatch) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`);
|
console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`);
|
||||||
|
|
@ -392,7 +392,7 @@ class ManifestGenerator {
|
||||||
|
|
||||||
if (file.endsWith('.md')) {
|
if (file.endsWith('.md')) {
|
||||||
// Parse YAML frontmatter for .md tasks
|
// Parse YAML frontmatter for .md tasks
|
||||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||||
if (frontmatterMatch) {
|
if (frontmatterMatch) {
|
||||||
try {
|
try {
|
||||||
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
||||||
|
|
@ -481,7 +481,7 @@ class ManifestGenerator {
|
||||||
|
|
||||||
if (file.endsWith('.md')) {
|
if (file.endsWith('.md')) {
|
||||||
// Parse YAML frontmatter for .md tools
|
// Parse YAML frontmatter for .md tools
|
||||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||||
if (frontmatterMatch) {
|
if (frontmatterMatch) {
|
||||||
try {
|
try {
|
||||||
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { XmlHandler } = require('../../../lib/xml-handler');
|
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||||
const { getSourcePath } = require('../../../lib/project-root');
|
const { getSourcePath } = require('../../../lib/project-root');
|
||||||
|
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for IDE-specific setup
|
* Base class for IDE-specific setup
|
||||||
|
|
@ -18,7 +19,7 @@ class BaseIdeSetup {
|
||||||
this.configFile = null; // Override in subclasses when detection is file-based
|
this.configFile = null; // Override in subclasses when detection is file-based
|
||||||
this.detectionPaths = []; // Additional paths that indicate the IDE is configured
|
this.detectionPaths = []; // Additional paths that indicate the IDE is configured
|
||||||
this.xmlHandler = new XmlHandler();
|
this.xmlHandler = new XmlHandler();
|
||||||
this.bmadFolderName = 'bmad'; // Default, can be overridden
|
this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,7 +58,7 @@ class BaseIdeSetup {
|
||||||
if (this.configDir) {
|
if (this.configDir) {
|
||||||
const configPath = path.join(projectDir, this.configDir);
|
const configPath = path.join(projectDir, this.configDir);
|
||||||
if (await fs.pathExists(configPath)) {
|
if (await fs.pathExists(configPath)) {
|
||||||
const bmadRulesPath = path.join(configPath, 'bmad');
|
const bmadRulesPath = path.join(configPath, BMAD_FOLDER_NAME);
|
||||||
if (await fs.pathExists(bmadRulesPath)) {
|
if (await fs.pathExists(bmadRulesPath)) {
|
||||||
await fs.remove(bmadRulesPath);
|
await fs.remove(bmadRulesPath);
|
||||||
console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
|
console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
|
||||||
|
|
|
||||||
|
|
@ -86,10 +86,11 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
||||||
results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config);
|
results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install tasks and tools
|
// Install tasks and tools using template system (supports TOML for Gemini, MD for others)
|
||||||
if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) {
|
if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) {
|
||||||
const taskToolGen = new TaskToolCommandGenerator();
|
const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName);
|
||||||
const taskToolResult = await taskToolGen.generateDashTaskToolCommands(projectDir, bmadDir, targetPath);
|
const { artifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
|
||||||
|
const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config);
|
||||||
results.tasks = taskToolResult.tasks || 0;
|
results.tasks = taskToolResult.tasks || 0;
|
||||||
results.tools = taskToolResult.tools || 0;
|
results.tools = taskToolResult.tools || 0;
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +181,53 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write task/tool artifacts to target directory using templates
|
||||||
|
* @param {string} targetPath - Target directory path
|
||||||
|
* @param {Array} artifacts - Task/tool artifacts
|
||||||
|
* @param {string} templateType - Template type to use
|
||||||
|
* @param {Object} config - Installation configuration
|
||||||
|
* @returns {Promise<Object>} Counts of tasks and tools written
|
||||||
|
*/
|
||||||
|
async writeTaskToolArtifacts(targetPath, artifacts, templateType, config = {}) {
|
||||||
|
let taskCount = 0;
|
||||||
|
let toolCount = 0;
|
||||||
|
|
||||||
|
// Pre-load templates to avoid repeated file I/O in the loop
|
||||||
|
const taskTemplate = await this.loadTemplate(templateType, 'task', config, 'default-task');
|
||||||
|
const toolTemplate = await this.loadTemplate(templateType, 'tool', config, 'default-tool');
|
||||||
|
|
||||||
|
const { artifact_types } = config;
|
||||||
|
|
||||||
|
for (const artifact of artifacts) {
|
||||||
|
if (artifact.type !== 'task' && artifact.type !== 'tool') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if the specific artifact type is not requested in config
|
||||||
|
if (artifact_types) {
|
||||||
|
if (artifact.type === 'task' && !artifact_types.includes('tasks')) continue;
|
||||||
|
if (artifact.type === 'tool' && !artifact_types.includes('tools')) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use pre-loaded template based on artifact type
|
||||||
|
const { content: template, extension } = artifact.type === 'task' ? taskTemplate : toolTemplate;
|
||||||
|
|
||||||
|
const content = this.renderTemplate(template, artifact);
|
||||||
|
const filename = this.generateFilename(artifact, artifact.type, extension);
|
||||||
|
const filePath = path.join(targetPath, filename);
|
||||||
|
await this.writeFile(filePath, content);
|
||||||
|
|
||||||
|
if (artifact.type === 'task') {
|
||||||
|
taskCount++;
|
||||||
|
} else {
|
||||||
|
toolCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { tasks: taskCount, tools: toolCount };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load template based on type and configuration
|
* Load template based on type and configuration
|
||||||
* @param {string} templateType - Template type (claude, windsurf, etc.)
|
* @param {string} templateType - Template type (claude, windsurf, etc.)
|
||||||
|
|
@ -316,10 +364,24 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
renderTemplate(template, artifact) {
|
renderTemplate(template, artifact) {
|
||||||
// Use the appropriate path property based on artifact type
|
// Use the appropriate path property based on artifact type
|
||||||
let pathToUse = artifact.relativePath || '';
|
let pathToUse = artifact.relativePath || '';
|
||||||
if (artifact.type === 'agent-launcher') {
|
switch (artifact.type) {
|
||||||
|
case 'agent-launcher': {
|
||||||
pathToUse = artifact.agentPath || artifact.relativePath || '';
|
pathToUse = artifact.agentPath || artifact.relativePath || '';
|
||||||
} else if (artifact.type === 'workflow-command') {
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'workflow-command': {
|
||||||
pathToUse = artifact.workflowPath || artifact.relativePath || '';
|
pathToUse = artifact.workflowPath || artifact.relativePath || '';
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'task':
|
||||||
|
case 'tool': {
|
||||||
|
pathToUse = artifact.path || artifact.relativePath || '';
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// No default
|
||||||
}
|
}
|
||||||
|
|
||||||
let rendered = template
|
let rendered = template
|
||||||
|
|
@ -351,8 +413,9 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
// Reuse central logic to ensure consistent naming conventions
|
// Reuse central logic to ensure consistent naming conventions
|
||||||
const standardName = toDashPath(artifact.relativePath);
|
const standardName = toDashPath(artifact.relativePath);
|
||||||
|
|
||||||
// Clean up potential double extensions from source files (e.g. .yaml.md -> .md)
|
// Clean up potential double extensions from source files (e.g. .yaml.md, .xml.md -> .md)
|
||||||
const baseName = standardName.replace(/\.(yaml|yml)\.md$/, '.md');
|
// This handles any extensions that might slip through toDashPath()
|
||||||
|
const baseName = standardName.replace(/\.(md|yaml|yml|json|xml|toml)\.md$/i, '.md');
|
||||||
|
|
||||||
// If using default markdown, preserve the bmad-agent- prefix for agents
|
// If using default markdown, preserve the bmad-agent- prefix for agents
|
||||||
if (extension === '.md') {
|
if (extension === '.md') {
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,10 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
);
|
);
|
||||||
taskArtifacts.push({
|
taskArtifacts.push({
|
||||||
type: 'task',
|
type: 'task',
|
||||||
|
name: task.name,
|
||||||
|
displayName: task.name,
|
||||||
module: task.module,
|
module: task.module,
|
||||||
|
path: task.path,
|
||||||
sourcePath: task.path,
|
sourcePath: task.path,
|
||||||
relativePath: path.join(task.module, 'tasks', `${task.name}.md`),
|
relativePath: path.join(task.module, 'tasks', `${task.name}.md`),
|
||||||
content,
|
content,
|
||||||
|
|
@ -116,7 +119,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
const workflowCount = await workflowGenerator.writeDashArtifacts(destDir, workflowArtifacts);
|
const workflowCount = await workflowGenerator.writeDashArtifacts(destDir, workflowArtifacts);
|
||||||
|
|
||||||
// Also write tasks using underscore format
|
// Also write tasks using underscore format
|
||||||
const ttGen = new TaskToolCommandGenerator();
|
const ttGen = new TaskToolCommandGenerator(this.bmadFolderName);
|
||||||
const tasksWritten = await ttGen.writeDashArtifacts(destDir, taskArtifacts);
|
const tasksWritten = await ttGen.writeDashArtifacts(destDir, taskArtifacts);
|
||||||
|
|
||||||
const written = agentCount + workflowCount + tasksWritten;
|
const written = agentCount + workflowCount + tasksWritten;
|
||||||
|
|
@ -214,7 +217,10 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
|
|
||||||
artifacts.push({
|
artifacts.push({
|
||||||
type: 'task',
|
type: 'task',
|
||||||
|
name: task.name,
|
||||||
|
displayName: task.name,
|
||||||
module: task.module,
|
module: task.module,
|
||||||
|
path: task.path,
|
||||||
sourcePath: task.path,
|
sourcePath: task.path,
|
||||||
relativePath: path.join(task.module, 'tasks', `${task.name}.md`),
|
relativePath: path.join(task.module, 'tasks', `${task.name}.md`),
|
||||||
content,
|
content,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IDE Manager - handles IDE-specific setup
|
* IDE Manager - handles IDE-specific setup
|
||||||
|
|
@ -14,7 +15,7 @@ class IdeManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.handlers = new Map();
|
this.handlers = new Map();
|
||||||
this._initialized = false;
|
this._initialized = false;
|
||||||
this.bmadFolderName = 'bmad'; // Default, can be overridden
|
this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,6 +74,9 @@ class IdeManager {
|
||||||
if (HandlerClass) {
|
if (HandlerClass) {
|
||||||
const instance = new HandlerClass();
|
const instance = new HandlerClass();
|
||||||
if (instance.name && typeof instance.name === 'string') {
|
if (instance.name && typeof instance.name === 'string') {
|
||||||
|
if (typeof instance.setBmadFolderName === 'function') {
|
||||||
|
instance.setBmadFolderName(this.bmadFolderName);
|
||||||
|
}
|
||||||
this.handlers.set(instance.name, instance);
|
this.handlers.set(instance.name, instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +104,9 @@ class IdeManager {
|
||||||
if (!platformInfo.installer) continue;
|
if (!platformInfo.installer) continue;
|
||||||
|
|
||||||
const handler = new ConfigDrivenIdeSetup(platformCode, platformInfo);
|
const handler = new ConfigDrivenIdeSetup(platformCode, platformInfo);
|
||||||
|
if (typeof handler.setBmadFolderName === 'function') {
|
||||||
handler.setBmadFolderName(this.bmadFolderName);
|
handler.setBmadFolderName(this.bmadFolderName);
|
||||||
|
}
|
||||||
this.handlers.set(platformCode, handler);
|
this.handlers.set(platformCode, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils');
|
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates launcher command files for each agent
|
* Generates launcher command files for each agent
|
||||||
* Similar to WorkflowCommandGenerator but for agents
|
* Similar to WorkflowCommandGenerator but for agents
|
||||||
*/
|
*/
|
||||||
class AgentCommandGenerator {
|
class AgentCommandGenerator {
|
||||||
constructor(bmadFolderName = 'bmad') {
|
constructor(bmadFolderName = BMAD_FOLDER_NAME) {
|
||||||
this.templatePath = path.join(__dirname, '../templates/agent-command-template.md');
|
this.templatePath = path.join(__dirname, '../templates/agent-command-template.md');
|
||||||
this.bmadFolderName = bmadFolderName;
|
this.bmadFolderName = bmadFolderName;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@
|
||||||
const TYPE_SEGMENTS = ['workflows', 'tasks', 'tools'];
|
const TYPE_SEGMENTS = ['workflows', 'tasks', 'tools'];
|
||||||
const AGENT_SEGMENT = 'agents';
|
const AGENT_SEGMENT = 'agents';
|
||||||
|
|
||||||
|
// BMAD installation folder name - centralized constant for all installers
|
||||||
|
const BMAD_FOLDER_NAME = '_bmad';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert hierarchical path to flat dash-separated name (NEW STANDARD)
|
* Convert hierarchical path to flat dash-separated name (NEW STANDARD)
|
||||||
* Converts: 'bmm', 'agents', 'pm' → 'bmad-agent-bmm-pm.md'
|
* Converts: 'bmm', 'agents', 'pm' → 'bmad-agent-bmm-pm.md'
|
||||||
|
|
@ -59,7 +62,9 @@ function toDashPath(relativePath) {
|
||||||
return 'bmad-unknown.md';
|
return 'bmad-unknown.md';
|
||||||
}
|
}
|
||||||
|
|
||||||
const withoutExt = relativePath.replace('.md', '');
|
// Strip common file extensions to avoid double extensions in generated filenames
|
||||||
|
// e.g., 'create-story.xml' → 'create-story', 'workflow.yaml' → 'workflow'
|
||||||
|
const withoutExt = relativePath.replace(/\.(md|yaml|yml|json|xml|toml)$/i, '');
|
||||||
const parts = withoutExt.split(/[/\\]/);
|
const parts = withoutExt.split(/[/\\]/);
|
||||||
|
|
||||||
const module = parts[0];
|
const module = parts[0];
|
||||||
|
|
@ -183,7 +188,8 @@ function toUnderscoreName(module, type, name) {
|
||||||
* @deprecated Use toDashPath instead
|
* @deprecated Use toDashPath instead
|
||||||
*/
|
*/
|
||||||
function toUnderscorePath(relativePath) {
|
function toUnderscorePath(relativePath) {
|
||||||
const withoutExt = relativePath.replace('.md', '');
|
// Strip common file extensions (same as toDashPath for consistency)
|
||||||
|
const withoutExt = relativePath.replace(/\.(md|yaml|yml|json|xml|toml)$/i, '');
|
||||||
const parts = withoutExt.split(/[/\\]/);
|
const parts = withoutExt.split(/[/\\]/);
|
||||||
|
|
||||||
const module = parts[0];
|
const module = parts[0];
|
||||||
|
|
@ -289,4 +295,5 @@ module.exports = {
|
||||||
|
|
||||||
TYPE_SEGMENTS,
|
TYPE_SEGMENTS,
|
||||||
AGENT_SEGMENT,
|
AGENT_SEGMENT,
|
||||||
|
BMAD_FOLDER_NAME,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,98 @@ const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const csv = require('csv-parse/sync');
|
const csv = require('csv-parse/sync');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { toColonName, toColonPath, toDashPath } = require('./path-utils');
|
const { toColonName, toColonPath, toDashPath, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates command files for standalone tasks and tools
|
* Generates command files for standalone tasks and tools
|
||||||
*/
|
*/
|
||||||
class TaskToolCommandGenerator {
|
class TaskToolCommandGenerator {
|
||||||
|
/**
|
||||||
|
* @param {string} bmadFolderName - Name of the BMAD folder for template rendering (default: '_bmad')
|
||||||
|
* Note: This parameter is accepted for API consistency with AgentCommandGenerator and
|
||||||
|
* WorkflowCommandGenerator, but is not used for path stripping. The manifest always stores
|
||||||
|
* filesystem paths with '_bmad/' prefix (the actual folder name), while bmadFolderName is
|
||||||
|
* used for template placeholder rendering ({{bmadFolderName}}).
|
||||||
|
*/
|
||||||
|
constructor(bmadFolderName = BMAD_FOLDER_NAME) {
|
||||||
|
this.bmadFolderName = bmadFolderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect task and tool artifacts for IDE installation
|
||||||
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
* @returns {Promise<Object>} Artifacts array with metadata
|
||||||
|
*/
|
||||||
|
async collectTaskToolArtifacts(bmadDir) {
|
||||||
|
const tasks = await this.loadTaskManifest(bmadDir);
|
||||||
|
const tools = await this.loadToolManifest(bmadDir);
|
||||||
|
|
||||||
|
// Filter to only standalone items
|
||||||
|
const standaloneTasks = tasks ? tasks.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
|
||||||
|
const standaloneTools = tools ? tools.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
|
||||||
|
|
||||||
|
const artifacts = [];
|
||||||
|
const bmadPrefix = `${BMAD_FOLDER_NAME}/`;
|
||||||
|
|
||||||
|
// Collect task artifacts
|
||||||
|
for (const task of standaloneTasks) {
|
||||||
|
let taskPath = (task.path || '').replaceAll('\\', '/');
|
||||||
|
// Convert absolute paths to relative paths
|
||||||
|
if (path.isAbsolute(taskPath)) {
|
||||||
|
taskPath = path.relative(bmadDir, taskPath).replaceAll('\\', '/');
|
||||||
|
}
|
||||||
|
// Remove _bmad/ prefix if present to get relative path within bmad folder
|
||||||
|
if (taskPath.startsWith(bmadPrefix)) {
|
||||||
|
taskPath = taskPath.slice(bmadPrefix.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskExt = path.extname(taskPath) || '.md';
|
||||||
|
artifacts.push({
|
||||||
|
type: 'task',
|
||||||
|
name: task.name,
|
||||||
|
displayName: task.displayName || task.name,
|
||||||
|
description: task.description || `Execute ${task.displayName || task.name}`,
|
||||||
|
module: task.module,
|
||||||
|
// Use forward slashes for cross-platform consistency (not path.join which uses backslashes on Windows)
|
||||||
|
relativePath: `${task.module}/tasks/${task.name}${taskExt}`,
|
||||||
|
path: taskPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect tool artifacts
|
||||||
|
for (const tool of standaloneTools) {
|
||||||
|
let toolPath = (tool.path || '').replaceAll('\\', '/');
|
||||||
|
// Convert absolute paths to relative paths
|
||||||
|
if (path.isAbsolute(toolPath)) {
|
||||||
|
toolPath = path.relative(bmadDir, toolPath).replaceAll('\\', '/');
|
||||||
|
}
|
||||||
|
// Remove _bmad/ prefix if present to get relative path within bmad folder
|
||||||
|
if (toolPath.startsWith(bmadPrefix)) {
|
||||||
|
toolPath = toolPath.slice(bmadPrefix.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolExt = path.extname(toolPath) || '.md';
|
||||||
|
artifacts.push({
|
||||||
|
type: 'tool',
|
||||||
|
name: tool.name,
|
||||||
|
displayName: tool.displayName || tool.name,
|
||||||
|
description: tool.description || `Execute ${tool.displayName || tool.name}`,
|
||||||
|
module: tool.module,
|
||||||
|
// Use forward slashes for cross-platform consistency (not path.join which uses backslashes on Windows)
|
||||||
|
relativePath: `${tool.module}/tools/${tool.name}${toolExt}`,
|
||||||
|
path: toolPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
artifacts,
|
||||||
|
counts: {
|
||||||
|
tasks: standaloneTasks.length,
|
||||||
|
tools: standaloneTools.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate task and tool commands from manifest CSVs
|
* Generate task and tool commands from manifest CSVs
|
||||||
* @param {string} projectDir - Project directory
|
* @param {string} projectDir - Project directory
|
||||||
|
|
@ -65,9 +151,33 @@ class TaskToolCommandGenerator {
|
||||||
const description = item.description || `Execute ${item.displayName || item.name}`;
|
const description = item.description || `Execute ${item.displayName || item.name}`;
|
||||||
|
|
||||||
// Convert path to use {project-root} placeholder
|
// Convert path to use {project-root} placeholder
|
||||||
|
// Handle undefined/missing path by constructing from module and name
|
||||||
let itemPath = item.path;
|
let itemPath = item.path;
|
||||||
if (itemPath && typeof itemPath === 'string' && itemPath.startsWith('bmad/')) {
|
if (!itemPath || typeof itemPath !== 'string') {
|
||||||
itemPath = `{project-root}/${itemPath}`;
|
// Fallback: construct path from module and name if path is missing
|
||||||
|
const typePlural = type === 'task' ? 'tasks' : 'tools';
|
||||||
|
itemPath = `{project-root}/${this.bmadFolderName}/${item.module}/${typePlural}/${item.name}.md`;
|
||||||
|
} else {
|
||||||
|
// Normalize path separators to forward slashes
|
||||||
|
itemPath = itemPath.replaceAll('\\', '/');
|
||||||
|
|
||||||
|
// Extract relative path from absolute paths (Windows or Unix)
|
||||||
|
// Look for _bmad/ or bmad/ in the path and extract everything after it
|
||||||
|
// Match patterns like: /_bmad/core/tasks/... or /bmad/core/tasks/...
|
||||||
|
const bmadMatch = itemPath.match(/\/_bmad\/(.+)$/) || itemPath.match(/\/bmad\/(.+)$/);
|
||||||
|
if (bmadMatch) {
|
||||||
|
// Found /_bmad/ or /bmad/ - use relative path after it
|
||||||
|
itemPath = `{project-root}/${this.bmadFolderName}/${bmadMatch[1]}`;
|
||||||
|
} else if (itemPath.startsWith(`${BMAD_FOLDER_NAME}/`)) {
|
||||||
|
// Relative path starting with _bmad/
|
||||||
|
itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(BMAD_FOLDER_NAME.length + 1)}`;
|
||||||
|
} else if (itemPath.startsWith('bmad/')) {
|
||||||
|
// Relative path starting with bmad/
|
||||||
|
itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(5)}`;
|
||||||
|
} else if (!itemPath.startsWith('{project-root}')) {
|
||||||
|
// For other relative paths, prefix with project root and bmad folder
|
||||||
|
itemPath = `{project-root}/${this.bmadFolderName}/${itemPath}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return `---
|
return `---
|
||||||
|
|
@ -187,7 +297,7 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
// Generate command files for tasks
|
// Generate command files for tasks
|
||||||
for (const task of standaloneTasks) {
|
for (const task of standaloneTasks) {
|
||||||
const commandContent = this.generateCommandContent(task, 'task');
|
const commandContent = this.generateCommandContent(task, 'task');
|
||||||
// Use underscore format: bmad_bmm_name.md
|
// Use dash format: bmad-bmm-name.md
|
||||||
const flatName = toDashPath(`${task.module}/tasks/${task.name}.md`);
|
const flatName = toDashPath(`${task.module}/tasks/${task.name}.md`);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
@ -198,7 +308,7 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
// Generate command files for tools
|
// Generate command files for tools
|
||||||
for (const tool of standaloneTools) {
|
for (const tool of standaloneTools) {
|
||||||
const commandContent = this.generateCommandContent(tool, 'tool');
|
const commandContent = this.generateCommandContent(tool, 'tool');
|
||||||
// Use underscore format: bmad_bmm_name.md
|
// Use dash format: bmad-bmm-name.md
|
||||||
const flatName = toDashPath(`${tool.module}/tools/${tool.name}.md`);
|
const flatName = toDashPath(`${tool.module}/tools/${tool.name}.md`);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const csv = require('csv-parse/sync');
|
const csv = require('csv-parse/sync');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils');
|
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates command files for each workflow in the manifest
|
* Generates command files for each workflow in the manifest
|
||||||
*/
|
*/
|
||||||
class WorkflowCommandGenerator {
|
class WorkflowCommandGenerator {
|
||||||
constructor(bmadFolderName = 'bmad') {
|
constructor(bmadFolderName = BMAD_FOLDER_NAME) {
|
||||||
this.templatePath = path.join(__dirname, '../templates/workflow-command-template.md');
|
this.templatePath = path.join(__dirname, '../templates/workflow-command-template.md');
|
||||||
this.bmadFolderName = bmadFolderName;
|
this.bmadFolderName = bmadFolderName;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: '{{name}}'
|
||||||
|
description: '{{description}}'
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{name}}
|
||||||
|
|
||||||
|
Read the entire task file at: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
|
|
||||||
|
Follow all instructions in the task file exactly as written.
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: '{{name}}'
|
||||||
|
description: '{{description}}'
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{name}}
|
||||||
|
|
||||||
|
Read the entire tool file at: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
|
|
||||||
|
Follow all instructions in the tool file exactly as written.
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
description = "Executes the {{name}} task from the BMAD Method."
|
||||||
|
prompt = """
|
||||||
|
Execute the BMAD '{{name}}' task.
|
||||||
|
|
||||||
|
TASK INSTRUCTIONS:
|
||||||
|
1. LOAD the task file from {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
|
2. READ its entire contents
|
||||||
|
3. FOLLOW every instruction precisely as specified
|
||||||
|
|
||||||
|
TASK FILE: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
|
"""
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
description = "Executes the {{name}} tool from the BMAD Method."
|
||||||
|
prompt = """
|
||||||
|
Execute the BMAD '{{name}}' tool.
|
||||||
|
|
||||||
|
TOOL INSTRUCTIONS:
|
||||||
|
1. LOAD the tool file from {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
|
2. READ its entire contents
|
||||||
|
3. FOLLOW every instruction precisely as specified
|
||||||
|
|
||||||
|
TOOL FILE: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
|
"""
|
||||||
|
|
@ -7,6 +7,7 @@ const { XmlHandler } = require('../../../lib/xml-handler');
|
||||||
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
||||||
const { filterCustomizationData } = require('../../../lib/agent/compiler');
|
const { filterCustomizationData } = require('../../../lib/agent/compiler');
|
||||||
const { ExternalModuleManager } = require('./external-manager');
|
const { ExternalModuleManager } = require('./external-manager');
|
||||||
|
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the installation, updating, and removal of BMAD modules.
|
* Manages the installation, updating, and removal of BMAD modules.
|
||||||
|
|
@ -27,7 +28,7 @@ const { ExternalModuleManager } = require('./external-manager');
|
||||||
class ModuleManager {
|
class ModuleManager {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
this.xmlHandler = new XmlHandler();
|
this.xmlHandler = new XmlHandler();
|
||||||
this.bmadFolderName = 'bmad'; // Default, can be overridden
|
this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden
|
||||||
this.customModulePaths = new Map(); // Initialize custom module paths
|
this.customModulePaths = new Map(); // Initialize custom module paths
|
||||||
this.externalModuleManager = new ExternalModuleManager(); // For external official modules
|
this.externalModuleManager = new ExternalModuleManager(); // For external official modules
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ function findBmadConfig(startPath = process.cwd()) {
|
||||||
* @returns {string} Resolved path
|
* @returns {string} Resolved path
|
||||||
*/
|
*/
|
||||||
function resolvePath(pathStr, context) {
|
function resolvePath(pathStr, context) {
|
||||||
return pathStr.replaceAll('{project-root}', context.projectRoot).replaceAll('{bmad-folder}', context_bmadFolder);
|
return pathStr.replaceAll('{project-root}', context.projectRoot).replaceAll('{bmad-folder}', context.bmadFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue