fix: address PR review findings from triage
- Fix regex capture group index in module manager workflow path parsing - Remove stale workflow handler references from handler-multi.txt - Replace workflow with multi in activation-steps dispatch contract - Remove dead validate-workflow emission from compiler and xml-builder - Align commands.md wording to remove engine references - Fix relativePath anchoring in _base-ide.js recursive directory scans - Remove dead code from workflow-command-generator (unused template, generateCommandContent, writeColonArtifacts, writeDashArtifacts) - Delete unused workflow-commander.md template - Add regression test for workflow path regex
This commit is contained in:
parent
61fe92a129
commit
a5aa8f8499
|
|
@ -88,7 +88,7 @@ See [Agents](./agents.md) for the full list of default agents and their triggers
|
||||||
|
|
||||||
### Workflow Skills
|
### Workflow Skills
|
||||||
|
|
||||||
Workflow skills run a structured, multi-step process without loading an agent persona first. They load the workflow engine and pass a specific workflow configuration.
|
Workflow skills run a structured, multi-step process without loading an agent persona first. They load a workflow configuration and follow its steps.
|
||||||
|
|
||||||
| Example skill | Purpose |
|
| Example skill | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,4 @@
|
||||||
<step n="{HELP_STEP}">Let {user_name} know they can type command `/bmad-help` at any time to get advice on what to do next, and that they can combine that with what they need help with <example>`/bmad-help where should I start with an idea I have that does XYZ`</example></step>
|
<step n="{HELP_STEP}">Let {user_name} know they can type command `/bmad-help` at any time to get advice on what to do next, and that they can combine that with what they need help with <example>`/bmad-help where should I start with an idea I have that does XYZ`</example></step>
|
||||||
<step n="{HALT_STEP}">STOP and WAIT for user input - do NOT execute menu items automatically - accept number or cmd trigger or fuzzy command match</step>
|
<step n="{HALT_STEP}">STOP and WAIT for user input - do NOT execute menu items automatically - accept number or cmd trigger or fuzzy command match</step>
|
||||||
<step n="{INPUT_STEP}">On user input: Number → process menu item[n] | Text → case-insensitive substring match | Multiple matches → ask user to clarify | No match → show "Not recognized"</step>
|
<step n="{INPUT_STEP}">On user input: Number → process menu item[n] | Text → case-insensitive substring match | Multiple matches → ask user to clarify | No match → show "Not recognized"</step>
|
||||||
<step n="{EXECUTE_STEP}">When processing a menu item: Check menu-handlers section below - extract any attributes from the selected menu item (workflow, exec, tmpl, data, action) and follow the corresponding handler instructions</step>
|
<step n="{EXECUTE_STEP}">When processing a menu item: Check menu-handlers section below - extract any attributes from the selected menu item (exec, tmpl, data, action, multi) and follow the corresponding handler instructions</step>
|
||||||
|
|
@ -4,10 +4,9 @@
|
||||||
2. Parse all nested handlers within the multi item
|
2. Parse all nested handlers within the multi item
|
||||||
3. For each nested handler:
|
3. For each nested handler:
|
||||||
- Use the 'match' attribute for fuzzy matching user input (or Exact Match of character code in brackets [])
|
- Use the 'match' attribute for fuzzy matching user input (or Exact Match of character code in brackets [])
|
||||||
- Process based on handler attributes (exec, workflow, action)
|
- Process based on handler attributes (exec, action)
|
||||||
4. When user input matches a handler's 'match' pattern:
|
4. When user input matches a handler's 'match' pattern:
|
||||||
- For exec="path/to/file.md": follow the `handler type="exec"` instructions
|
- For exec="path/to/file.md": follow the `handler type="exec"` instructions
|
||||||
- For workflow="path/to/workflow.md": follow the `handler type="workflow"` instructions
|
|
||||||
- For action="...": Perform the specified action directly
|
- For action="...": Perform the specified action directly
|
||||||
5. Support both exact matches and fuzzy matching based on the match attribute
|
5. Support both exact matches and fuzzy matching based on the match attribute
|
||||||
6. If no handler matches, prompt user to choose from available options
|
6. If no handler matches, prompt user to choose from available options
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* Workflow Path Regex Tests
|
||||||
|
*
|
||||||
|
* Tests that the source and install workflow path regexes in ModuleManager
|
||||||
|
* extract the correct capture groups (module name and workflow sub-path).
|
||||||
|
*
|
||||||
|
* Usage: node test/test-workflow-path-regex.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ANSI colors
|
||||||
|
const colors = {
|
||||||
|
reset: '\u001B[0m',
|
||||||
|
green: '\u001B[32m',
|
||||||
|
red: '\u001B[31m',
|
||||||
|
cyan: '\u001B[36m',
|
||||||
|
dim: '\u001B[2m',
|
||||||
|
};
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
function assert(condition, testName, errorMessage = '') {
|
||||||
|
if (condition) {
|
||||||
|
console.log(`${colors.green}✓${colors.reset} ${testName}`);
|
||||||
|
passed++;
|
||||||
|
} else {
|
||||||
|
console.log(`${colors.red}✗${colors.reset} ${testName}`);
|
||||||
|
if (errorMessage) {
|
||||||
|
console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
|
||||||
|
}
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// These regexes are extracted from ModuleManager.vendorWorkflowDependencies()
|
||||||
|
// in tools/cli/installers/lib/modules/manager.js
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Source regex (line ~1081) — uses non-capturing group for _bmad
|
||||||
|
const SOURCE_REGEX = /\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/;
|
||||||
|
|
||||||
|
// Install regex (line ~1091) — uses non-capturing group for _bmad,
|
||||||
|
// consistent with source regex
|
||||||
|
const INSTALL_REGEX = /\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Test data
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
const sourcePath = '{project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.md';
|
||||||
|
const installPath = '{project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.md';
|
||||||
|
|
||||||
|
console.log(`\n${colors.cyan}Workflow Path Regex Tests${colors.reset}\n`);
|
||||||
|
|
||||||
|
// --- Source regex tests (these should pass — source regex is correct) ---
|
||||||
|
|
||||||
|
const sourceMatch = sourcePath.match(SOURCE_REGEX);
|
||||||
|
|
||||||
|
assert(sourceMatch !== null, 'Source regex matches source path');
|
||||||
|
assert(
|
||||||
|
sourceMatch && sourceMatch[1] === 'bmm',
|
||||||
|
'Source regex group [1] is the module name',
|
||||||
|
`Expected "bmm", got "${sourceMatch && sourceMatch[1]}"`,
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
sourceMatch && sourceMatch[2] === '4-implementation/create-story/workflow.md',
|
||||||
|
'Source regex group [2] is the workflow sub-path',
|
||||||
|
`Expected "4-implementation/create-story/workflow.md", got "${sourceMatch && sourceMatch[2]}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Install regex tests (group [2] returns module name, not sub-path) ---
|
||||||
|
|
||||||
|
const installMatch = installPath.match(INSTALL_REGEX);
|
||||||
|
|
||||||
|
assert(installMatch !== null, 'Install regex matches install path');
|
||||||
|
|
||||||
|
// This is the critical test: installMatch[2] should be the workflow sub-path,
|
||||||
|
// because the code uses it as `installWorkflowSubPath`.
|
||||||
|
// With the bug, installMatch[2] is "bmgd" (module name) instead of the sub-path.
|
||||||
|
assert(
|
||||||
|
installMatch && installMatch[2] === '4-production/create-story/workflow.md',
|
||||||
|
'Install regex group [2] is the workflow sub-path (used as installWorkflowSubPath)',
|
||||||
|
`Expected "4-production/create-story/workflow.md", got "${installMatch && installMatch[2]}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Summary ---
|
||||||
|
console.log(`\n${passed} passed, ${failed} failed\n`);
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
|
|
@ -326,9 +326,11 @@ class BaseIdeSetup {
|
||||||
/**
|
/**
|
||||||
* Recursively find workflow.md files
|
* Recursively find workflow.md files
|
||||||
* @param {string} dir - Directory to search
|
* @param {string} dir - Directory to search
|
||||||
|
* @param {string} [rootDir] - Original root directory (used internally for recursion)
|
||||||
* @returns {Array} List of workflow file info objects
|
* @returns {Array} List of workflow file info objects
|
||||||
*/
|
*/
|
||||||
async findWorkflowFiles(dir) {
|
async findWorkflowFiles(dir, rootDir = null) {
|
||||||
|
rootDir = rootDir || dir;
|
||||||
const workflows = [];
|
const workflows = [];
|
||||||
|
|
||||||
if (!(await fs.pathExists(dir))) {
|
if (!(await fs.pathExists(dir))) {
|
||||||
|
|
@ -342,7 +344,7 @@ class BaseIdeSetup {
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
// Recursively search subdirectories
|
// Recursively search subdirectories
|
||||||
const subWorkflows = await this.findWorkflowFiles(fullPath);
|
const subWorkflows = await this.findWorkflowFiles(fullPath, rootDir);
|
||||||
workflows.push(...subWorkflows);
|
workflows.push(...subWorkflows);
|
||||||
} else if (entry.isFile() && entry.name === 'workflow.md') {
|
} else if (entry.isFile() && entry.name === 'workflow.md') {
|
||||||
// Read workflow.md frontmatter to get name and standalone property
|
// Read workflow.md frontmatter to get name and standalone property
|
||||||
|
|
@ -360,7 +362,7 @@ class BaseIdeSetup {
|
||||||
workflows.push({
|
workflows.push({
|
||||||
name: workflowData.name,
|
name: workflowData.name,
|
||||||
path: fullPath,
|
path: fullPath,
|
||||||
relativePath: path.relative(dir, fullPath),
|
relativePath: path.relative(rootDir, fullPath),
|
||||||
filename: entry.name,
|
filename: entry.name,
|
||||||
description: workflowData.description || '',
|
description: workflowData.description || '',
|
||||||
standalone: standalone,
|
standalone: standalone,
|
||||||
|
|
@ -379,9 +381,11 @@ class BaseIdeSetup {
|
||||||
* Scan a directory for files with specific extension(s)
|
* Scan a directory for files with specific extension(s)
|
||||||
* @param {string} dir - Directory to scan
|
* @param {string} dir - Directory to scan
|
||||||
* @param {string|Array<string>} ext - File extension(s) to match (e.g., '.md' or ['.md', '.xml'])
|
* @param {string|Array<string>} ext - File extension(s) to match (e.g., '.md' or ['.md', '.xml'])
|
||||||
|
* @param {string} [rootDir] - Original root directory (used internally for recursion)
|
||||||
* @returns {Array} List of file info objects
|
* @returns {Array} List of file info objects
|
||||||
*/
|
*/
|
||||||
async scanDirectory(dir, ext) {
|
async scanDirectory(dir, ext, rootDir = null) {
|
||||||
|
rootDir = rootDir || dir;
|
||||||
const files = [];
|
const files = [];
|
||||||
|
|
||||||
if (!(await fs.pathExists(dir))) {
|
if (!(await fs.pathExists(dir))) {
|
||||||
|
|
@ -398,7 +402,7 @@ class BaseIdeSetup {
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
// Recursively scan subdirectories
|
// Recursively scan subdirectories
|
||||||
const subFiles = await this.scanDirectory(fullPath, ext);
|
const subFiles = await this.scanDirectory(fullPath, ext, rootDir);
|
||||||
files.push(...subFiles);
|
files.push(...subFiles);
|
||||||
} else if (entry.isFile()) {
|
} else if (entry.isFile()) {
|
||||||
// Check if file matches any of the extensions
|
// Check if file matches any of the extensions
|
||||||
|
|
@ -407,7 +411,7 @@ class BaseIdeSetup {
|
||||||
files.push({
|
files.push({
|
||||||
name: path.basename(entry.name, matchedExt),
|
name: path.basename(entry.name, matchedExt),
|
||||||
path: fullPath,
|
path: fullPath,
|
||||||
relativePath: path.relative(dir, fullPath),
|
relativePath: path.relative(rootDir, fullPath),
|
||||||
filename: entry.name,
|
filename: entry.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -421,9 +425,11 @@ class BaseIdeSetup {
|
||||||
* Scan a directory for files with specific extension(s) and check standalone attribute
|
* Scan a directory for files with specific extension(s) and check standalone attribute
|
||||||
* @param {string} dir - Directory to scan
|
* @param {string} dir - Directory to scan
|
||||||
* @param {string|Array<string>} ext - File extension(s) to match (e.g., '.md' or ['.md', '.xml'])
|
* @param {string|Array<string>} ext - File extension(s) to match (e.g., '.md' or ['.md', '.xml'])
|
||||||
|
* @param {string} [rootDir] - Original root directory (used internally for recursion)
|
||||||
* @returns {Array} List of file info objects with standalone property
|
* @returns {Array} List of file info objects with standalone property
|
||||||
*/
|
*/
|
||||||
async scanDirectoryWithStandalone(dir, ext) {
|
async scanDirectoryWithStandalone(dir, ext, rootDir = null) {
|
||||||
|
rootDir = rootDir || dir;
|
||||||
const files = [];
|
const files = [];
|
||||||
|
|
||||||
if (!(await fs.pathExists(dir))) {
|
if (!(await fs.pathExists(dir))) {
|
||||||
|
|
@ -440,7 +446,7 @@ class BaseIdeSetup {
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
// Recursively scan subdirectories
|
// Recursively scan subdirectories
|
||||||
const subFiles = await this.scanDirectoryWithStandalone(fullPath, ext);
|
const subFiles = await this.scanDirectoryWithStandalone(fullPath, ext, rootDir);
|
||||||
files.push(...subFiles);
|
files.push(...subFiles);
|
||||||
} else if (entry.isFile()) {
|
} else if (entry.isFile()) {
|
||||||
// Check if file matches any of the extensions
|
// Check if file matches any of the extensions
|
||||||
|
|
@ -484,7 +490,7 @@ class BaseIdeSetup {
|
||||||
files.push({
|
files.push({
|
||||||
name: path.basename(entry.name, matchedExt),
|
name: path.basename(entry.name, matchedExt),
|
||||||
path: fullPath,
|
path: fullPath,
|
||||||
relativePath: path.relative(dir, fullPath),
|
relativePath: path.relative(rootDir, fullPath),
|
||||||
filename: entry.name,
|
filename: entry.name,
|
||||||
standalone: standalone,
|
standalone: standalone,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
const path = require('node:path');
|
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 prompts = require('../../../../lib/prompts');
|
const { BMAD_FOLDER_NAME } = 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
|
||||||
|
|
@ -12,46 +11,6 @@ class WorkflowCommandGenerator {
|
||||||
this.bmadFolderName = bmadFolderName;
|
this.bmadFolderName = bmadFolderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate workflow commands from the manifest CSV
|
|
||||||
* @param {string} projectDir - Project directory
|
|
||||||
* @param {string} bmadDir - BMAD installation directory
|
|
||||||
*/
|
|
||||||
async generateWorkflowCommands(projectDir, bmadDir) {
|
|
||||||
const workflows = await this.loadWorkflowManifest(bmadDir);
|
|
||||||
|
|
||||||
if (!workflows) {
|
|
||||||
await prompts.log.warn('Workflow manifest not found. Skipping command generation.');
|
|
||||||
return { generated: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ALL workflows now generate commands - no standalone filtering
|
|
||||||
const allWorkflows = workflows;
|
|
||||||
|
|
||||||
// Base commands directory
|
|
||||||
const baseCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
|
|
||||||
|
|
||||||
let generatedCount = 0;
|
|
||||||
|
|
||||||
// Generate a command file for each workflow, organized by module
|
|
||||||
for (const workflow of allWorkflows) {
|
|
||||||
const moduleWorkflowsDir = path.join(baseCommandsDir, workflow.module, 'workflows');
|
|
||||||
await fs.ensureDir(moduleWorkflowsDir);
|
|
||||||
|
|
||||||
const commandContent = await this.generateCommandContent(workflow, bmadDir);
|
|
||||||
const commandPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`);
|
|
||||||
|
|
||||||
await fs.writeFile(commandPath, commandContent);
|
|
||||||
generatedCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also create a workflow launcher README in each module
|
|
||||||
const groupedWorkflows = this.groupWorkflowsByModule(allWorkflows);
|
|
||||||
await this.createModuleWorkflowLaunchers(baseCommandsDir, groupedWorkflows);
|
|
||||||
|
|
||||||
return { generated: generatedCount };
|
|
||||||
}
|
|
||||||
|
|
||||||
async collectWorkflowArtifacts(bmadDir) {
|
async collectWorkflowArtifacts(bmadDir) {
|
||||||
const workflows = await this.loadWorkflowManifest(bmadDir);
|
const workflows = await this.loadWorkflowManifest(bmadDir);
|
||||||
|
|
||||||
|
|
@ -65,7 +24,6 @@ class WorkflowCommandGenerator {
|
||||||
const artifacts = [];
|
const artifacts = [];
|
||||||
|
|
||||||
for (const workflow of allWorkflows) {
|
for (const workflow of allWorkflows) {
|
||||||
const commandContent = await this.generateCommandContent(workflow, bmadDir);
|
|
||||||
// Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.md)
|
// Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.md)
|
||||||
let workflowRelPath = workflow.path || '';
|
let workflowRelPath = workflow.path || '';
|
||||||
// Normalize path separators for cross-platform compatibility
|
// Normalize path separators for cross-platform compatibility
|
||||||
|
|
@ -92,7 +50,6 @@ class WorkflowCommandGenerator {
|
||||||
canonicalId: workflow.canonicalId || '',
|
canonicalId: workflow.canonicalId || '',
|
||||||
relativePath: path.join(workflow.module, 'workflows', `${workflow.name}.md`),
|
relativePath: path.join(workflow.module, 'workflows', `${workflow.name}.md`),
|
||||||
workflowPath: workflowRelPath, // Relative path to actual workflow file
|
workflowPath: workflowRelPath, // Relative path to actual workflow file
|
||||||
content: commandContent,
|
|
||||||
sourcePath: workflow.path,
|
sourcePath: workflow.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -117,43 +74,6 @@ class WorkflowCommandGenerator {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate command content for a workflow
|
|
||||||
*/
|
|
||||||
async generateCommandContent(workflow, bmadDir) {
|
|
||||||
const templatePath = path.join(__dirname, '../templates/workflow-commander.md');
|
|
||||||
|
|
||||||
// Load the template
|
|
||||||
const template = await fs.readFile(templatePath, 'utf8');
|
|
||||||
|
|
||||||
// Convert source path to installed path
|
|
||||||
// From: /Users/.../src/bmm/workflows/.../workflow.md
|
|
||||||
// To: {project-root}/_bmad/bmm/workflows/.../workflow.md
|
|
||||||
let workflowPath = workflow.path;
|
|
||||||
|
|
||||||
// Extract the relative path from source
|
|
||||||
if (workflowPath.includes('/src/bmm/')) {
|
|
||||||
// bmm is directly under src/
|
|
||||||
const match = workflowPath.match(/\/src\/bmm\/(.+)/);
|
|
||||||
if (match) {
|
|
||||||
workflowPath = `${this.bmadFolderName}/bmm/${match[1]}`;
|
|
||||||
}
|
|
||||||
} else if (workflowPath.includes('/src/core/')) {
|
|
||||||
const match = workflowPath.match(/\/src\/core\/(.+)/);
|
|
||||||
if (match) {
|
|
||||||
workflowPath = `${this.bmadFolderName}/core/${match[1]}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace template variables
|
|
||||||
return template
|
|
||||||
.replaceAll('{{name}}', workflow.name)
|
|
||||||
.replaceAll('{{module}}', workflow.module)
|
|
||||||
.replaceAll('{{description}}', workflow.description)
|
|
||||||
.replaceAll('{{workflow_path}}', workflowPath)
|
|
||||||
.replaceAll('_bmad', this.bmadFolderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create workflow launcher files for each module
|
* Create workflow launcher files for each module
|
||||||
*/
|
*/
|
||||||
|
|
@ -254,58 +174,6 @@ When running any workflow:
|
||||||
skip_empty_lines: true,
|
skip_empty_lines: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write workflow command artifacts using underscore format (Windows-compatible)
|
|
||||||
* Creates flat files like: bmad_bmm_correct-course.md
|
|
||||||
*
|
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
|
||||||
* @param {Array} artifacts - Workflow artifacts
|
|
||||||
* @returns {number} Count of commands written
|
|
||||||
*/
|
|
||||||
async writeColonArtifacts(baseCommandsDir, artifacts) {
|
|
||||||
let writtenCount = 0;
|
|
||||||
|
|
||||||
for (const artifact of artifacts) {
|
|
||||||
if (artifact.type === 'workflow-command') {
|
|
||||||
// Convert relativePath to underscore format: bmm/workflows/correct-course.md → bmad_bmm_correct-course.md
|
|
||||||
const flatName = toColonPath(artifact.relativePath);
|
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
|
||||||
await fs.writeFile(commandPath, artifact.content);
|
|
||||||
writtenCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return writtenCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write workflow command artifacts using dash format (NEW STANDARD)
|
|
||||||
* Creates flat files like: bmad-bmm-correct-course.md
|
|
||||||
*
|
|
||||||
* Note: Workflows do NOT have bmad-agent- prefix - only agents do.
|
|
||||||
*
|
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
|
||||||
* @param {Array} artifacts - Workflow artifacts
|
|
||||||
* @returns {number} Count of commands written
|
|
||||||
*/
|
|
||||||
async writeDashArtifacts(baseCommandsDir, artifacts) {
|
|
||||||
let writtenCount = 0;
|
|
||||||
|
|
||||||
for (const artifact of artifacts) {
|
|
||||||
if (artifact.type === 'workflow-command') {
|
|
||||||
// Convert relativePath to dash format: bmm/workflows/correct-course.md → bmad-bmm-correct-course.md
|
|
||||||
const flatName = toDashPath(artifact.relativePath);
|
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
|
||||||
await fs.writeFile(commandPath, artifact.content);
|
|
||||||
writtenCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return writtenCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { WorkflowCommandGenerator };
|
module.exports = { WorkflowCommandGenerator };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
description: '{{description}}'
|
|
||||||
---
|
|
||||||
|
|
||||||
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL {project-root}/{{workflow_path}}, READ its entire contents and follow its directions exactly!
|
|
||||||
|
|
@ -1088,7 +1088,7 @@ class ModuleManager {
|
||||||
|
|
||||||
// Parse INSTALL workflow path
|
// Parse INSTALL workflow path
|
||||||
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.md
|
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.md
|
||||||
const installMatch = installWorkflowPath.match(/\{project-root\}\/(_bmad)\/([^/]+)\/workflows\/(.+)/);
|
const installMatch = installWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||||
if (!installMatch) {
|
if (!installMatch) {
|
||||||
await prompts.log.warn(` Could not parse workflow-install path: ${installWorkflowPath}`);
|
await prompts.log.warn(` Could not parse workflow-install path: ${installWorkflowPath}`);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,6 @@ function buildNestedHandlers(triggers) {
|
||||||
|
|
||||||
// Add handler attributes based on exec data
|
// Add handler attributes based on exec data
|
||||||
if (execData.route) attrs.push(`exec="${execData.route}"`);
|
if (execData.route) attrs.push(`exec="${execData.route}"`);
|
||||||
if (execData['validate-workflow']) attrs.push(`validate-workflow="${execData['validate-workflow']}"`);
|
|
||||||
if (execData.action) attrs.push(`action="${execData.action}"`);
|
if (execData.action) attrs.push(`action="${execData.action}"`);
|
||||||
if (execData.data) attrs.push(`data="${execData.data}"`);
|
if (execData.data) attrs.push(`data="${execData.data}"`);
|
||||||
if (execData.tmpl) attrs.push(`tmpl="${execData.tmpl}"`);
|
if (execData.tmpl) attrs.push(`tmpl="${execData.tmpl}"`);
|
||||||
|
|
|
||||||
|
|
@ -408,7 +408,6 @@ class YamlXmlBuilder {
|
||||||
|
|
||||||
// Add handler attributes based on exec data
|
// Add handler attributes based on exec data
|
||||||
if (execData.route) attrs.push(`exec="${execData.route}"`);
|
if (execData.route) attrs.push(`exec="${execData.route}"`);
|
||||||
if (execData['validate-workflow']) attrs.push(`validate-workflow="${execData['validate-workflow']}"`);
|
|
||||||
if (execData.action) attrs.push(`action="${execData.action}"`);
|
if (execData.action) attrs.push(`action="${execData.action}"`);
|
||||||
if (execData.data) attrs.push(`data="${execData.data}"`);
|
if (execData.data) attrs.push(`data="${execData.data}"`);
|
||||||
if (execData.tmpl) attrs.push(`tmpl="${execData.tmpl}"`);
|
if (execData.tmpl) attrs.push(`tmpl="${execData.tmpl}"`);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue