fix: normalize custom bmad workflow paths in command generation
This commit is contained in:
parent
9987a25e3e
commit
0ba1167c60
|
|
@ -862,6 +862,56 @@ internal: true
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Test 22: Custom BMAD Folder Workflow Path Guard
|
||||||
|
// ============================================================
|
||||||
|
console.log(`${colors.yellow}Test Suite 22: Custom BMAD Folder Workflow Path Guard${colors.reset}\n`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const generator = new WorkflowCommandGenerator('mybmad');
|
||||||
|
generator.loadWorkflowManifest = async () => [
|
||||||
|
{
|
||||||
|
name: 'sprint-planning',
|
||||||
|
description: 'Sprint Planning',
|
||||||
|
module: 'bmm',
|
||||||
|
path: '/tmp/project/mybmad/bmm/workflows/4-implementation/sprint-planning/workflow.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create-story',
|
||||||
|
description: 'Create Story',
|
||||||
|
module: 'bmm',
|
||||||
|
path: 'mybmad/bmm/workflows/4-implementation/create-story/workflow.md',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
generator.generateCommandContent = async () => 'content';
|
||||||
|
|
||||||
|
const { artifacts } = await generator.collectWorkflowArtifacts('/tmp');
|
||||||
|
const sprintPlanning = artifacts.find((artifact) => artifact.name === 'sprint-planning');
|
||||||
|
const createStory = artifacts.find((artifact) => artifact.name === 'create-story');
|
||||||
|
|
||||||
|
assert(
|
||||||
|
sprintPlanning?.workflowPath === 'bmm/workflows/4-implementation/sprint-planning/workflow.md',
|
||||||
|
'Custom folder absolute workflow path strips configured BMAD folder prefix',
|
||||||
|
sprintPlanning?.workflowPath,
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
createStory?.workflowPath === 'bmm/workflows/4-implementation/create-story/workflow.md',
|
||||||
|
'Custom folder relative workflow path strips configured BMAD folder prefix',
|
||||||
|
createStory?.workflowPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
const installedPath = generator.mapSourcePathToInstalled('/tmp/project/mybmad/core/tasks/workflow.md');
|
||||||
|
assert(
|
||||||
|
installedPath === 'mybmad/core/tasks/workflow.md',
|
||||||
|
'Installed workflow path mapping handles absolute paths containing custom BMAD folder',
|
||||||
|
installedPath,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
assert(false, 'Custom BMAD folder workflow path guard runs', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
for (const tmpRoot of tmpRoots) {
|
for (const tmpRoot of tmpRoots) {
|
||||||
await fs.remove(tmpRoot).catch(() => {});
|
await fs.remove(tmpRoot).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ const csv = require('csv-parse/sync');
|
||||||
const prompts = require('../../../../lib/prompts');
|
const prompts = require('../../../../lib/prompts');
|
||||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
|
function escapeRegex(value) {
|
||||||
|
return String(value).replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates command files for each workflow in the manifest
|
* Generates command files for each workflow in the manifest
|
||||||
*/
|
*/
|
||||||
|
|
@ -67,22 +71,7 @@ class WorkflowCommandGenerator {
|
||||||
|
|
||||||
for (const workflow of allWorkflows) {
|
for (const workflow of allWorkflows) {
|
||||||
const commandContent = await this.generateCommandContent(workflow, bmadDir);
|
const commandContent = await this.generateCommandContent(workflow, bmadDir);
|
||||||
// Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.md)
|
const workflowRelPath = this.mapSourcePathToModuleRelative(workflow.path);
|
||||||
let workflowRelPath = workflow.path || '';
|
|
||||||
workflowRelPath = workflowRelPath.replaceAll('\\', '/');
|
|
||||||
// Remove _bmad/ prefix if present to get relative path from project root
|
|
||||||
// Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
|
|
||||||
if (workflowRelPath.includes('_bmad/')) {
|
|
||||||
const parts = workflowRelPath.split(/_bmad\//);
|
|
||||||
if (parts.length > 1) {
|
|
||||||
workflowRelPath = parts.at(-1);
|
|
||||||
}
|
|
||||||
} else if (workflowRelPath.includes('/src/') || workflowRelPath.startsWith('src/')) {
|
|
||||||
const match = workflowRelPath.match(/(?:^|\/)src\/([^/]+)\/(.+)/);
|
|
||||||
if (match) {
|
|
||||||
workflowRelPath = `${match[1]}/${match[2]}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
artifacts.push({
|
artifacts.push({
|
||||||
type: 'workflow-command',
|
type: 'workflow-command',
|
||||||
name: workflow.name,
|
name: workflow.name,
|
||||||
|
|
@ -213,6 +202,29 @@ When running any workflow:
|
||||||
return this.mapSourcePathToInstalled(workflowPath, true);
|
return this.mapSourcePathToInstalled(workflowPath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mapSourcePathToModuleRelative(sourcePath) {
|
||||||
|
const mapped = this.mapSourcePathToInstalled(sourcePath, false);
|
||||||
|
if (!mapped) {
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = String(mapped).replaceAll('\\', '/');
|
||||||
|
|
||||||
|
// Typical installed path -> strip BMAD root prefix for templates that prepend it.
|
||||||
|
if (normalized.startsWith(`${this.bmadFolderName}/`)) {
|
||||||
|
return normalized.slice(`${this.bmadFolderName}/`.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute path containing the configured BMAD root folder.
|
||||||
|
const folderPattern = new RegExp(`(?:^|\\/)${escapeRegex(this.bmadFolderName)}\\/(.+)`);
|
||||||
|
const folderMatch = normalized.match(folderPattern);
|
||||||
|
if (folderMatch) {
|
||||||
|
return folderMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
mapSourcePathToInstalled(sourcePath, includeProjectRootPrefix = false) {
|
mapSourcePathToInstalled(sourcePath, includeProjectRootPrefix = false) {
|
||||||
if (!sourcePath) {
|
if (!sourcePath) {
|
||||||
return sourcePath;
|
return sourcePath;
|
||||||
|
|
@ -232,6 +244,15 @@ When running any workflow:
|
||||||
return includeProjectRootPrefix ? `{project-root}/${mapped}` : mapped;
|
return includeProjectRootPrefix ? `{project-root}/${mapped}` : mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle absolute paths that already include the configured BMAD folder
|
||||||
|
// (e.g., /tmp/project/mybmad/bmm/workflows/...).
|
||||||
|
const folderPattern = new RegExp(`(?:^|\\/)${escapeRegex(this.bmadFolderName)}\\/(.+)`);
|
||||||
|
const folderMatch = normalized.match(folderPattern);
|
||||||
|
if (folderMatch) {
|
||||||
|
const mapped = `${this.bmadFolderName}/${folderMatch[1]}`;
|
||||||
|
return includeProjectRootPrefix ? `{project-root}/${mapped}` : mapped;
|
||||||
|
}
|
||||||
|
|
||||||
if (normalized.startsWith(`${this.bmadFolderName}/`)) {
|
if (normalized.startsWith(`${this.bmadFolderName}/`)) {
|
||||||
return includeProjectRootPrefix ? `{project-root}/${normalized}` : normalized;
|
return includeProjectRootPrefix ? `{project-root}/${normalized}` : normalized;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue