fix: preserve standalone module provenance in path-utils serializers

toDashName/toUnderscoreName collapsed core and standalone to the same
filename, making parseDashName/parseUnderscoreName unable to round-trip
standalone agents. Split the branches so standalone gets a distinct
token (e.g., bmad-agent-standalone-fred.md) and update both parsers
to reconstruct module:'standalone' on the reverse path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alex Verkhovsky 2026-03-09 01:34:04 -06:00
parent 646b003b48
commit 517e253db4
1 changed files with 53 additions and 4 deletions

View File

@ -12,6 +12,7 @@
* - bmm/workflows/plan-project.md bmad-bmm-plan-project.md
* - bmm/tasks/create-story.md bmad-bmm-create-story.md
* - core/agents/brainstorming.md bmad-agent-brainstorming.md (core agents skip module name)
* - standalone/agents/fred.md bmad-agent-standalone-fred.md
*/
// Type segments - agents are included in naming, others are filtered out
@ -26,8 +27,9 @@ const BMAD_FOLDER_NAME = '_bmad';
* Converts: 'bmm', 'agents', 'pm' 'bmad-agent-bmm-pm.md'
* Converts: 'bmm', 'workflows', 'correct-course' 'bmad-bmm-correct-course.md'
* Converts: 'core', 'agents', 'brainstorming' 'bmad-agent-brainstorming.md' (core agents skip module name)
* Converts: 'standalone', 'agents', 'fred' 'bmad-agent-standalone-fred.md'
*
* @param {string} module - Module name (e.g., 'bmm', 'core')
* @param {string} module - Module name (e.g., 'bmm', 'core', 'standalone')
* @param {string} type - Artifact type ('agents', 'workflows', 'tasks', 'tools')
* @param {string} name - Artifact name (e.g., 'pm', 'brainstorming')
* @returns {string} Flat filename like 'bmad-agent-bmm-pm.md' or 'bmad-bmm-correct-course.md'
@ -36,9 +38,13 @@ function toDashName(module, type, name) {
const isAgent = type === AGENT_SEGMENT;
// For core module, skip the module name: use 'bmad-agent-name.md' instead of 'bmad-agent-core-name.md'
if (module === 'core' || module === 'standalone') {
if (module === 'core') {
return isAgent ? `bmad-agent-${name}.md` : `bmad-${name}.md`;
}
// For standalone module, include 'standalone' in the name
if (module === 'standalone') {
return isAgent ? `bmad-agent-standalone-${name}.md` : `bmad-standalone-${name}.md`;
}
// Module artifacts: bmad-module-name.md or bmad-agent-module-name.md
// eslint-disable-next-line unicorn/prefer-string-replace-all -- regex replace is intentional here
@ -110,6 +116,8 @@ function isDashFormat(filename) {
* Parses: 'bmad-bmm-correct-course.md' { prefix: 'bmad', module: 'bmm', type: 'workflows', name: 'correct-course' }
* Parses: 'bmad-agent-brainstorming.md' { prefix: 'bmad', module: 'core', type: 'agents', name: 'brainstorming' } (core agents)
* Parses: 'bmad-brainstorming.md' { prefix: 'bmad', module: 'core', type: 'workflows', name: 'brainstorming' } (core workflows)
* Parses: 'bmad-agent-standalone-fred.md' { prefix: 'bmad', module: 'standalone', type: 'agents', name: 'fred' }
* Parses: 'bmad-standalone-foo.md' { prefix: 'bmad', module: 'standalone', type: 'workflows', name: 'foo' }
*
* @param {string} filename - Dash-formatted filename
* @returns {Object|null} Parsed parts or null if invalid format
@ -127,7 +135,16 @@ function parseDashName(filename) {
if (isAgent) {
// This is an agent file
// Format: bmad-agent-name (core) or bmad-agent-module-name
// Format: bmad-agent-name (core) or bmad-agent-standalone-name or bmad-agent-module-name
if (parts.length >= 4 && parts[2] === 'standalone') {
// Standalone agent: bmad-agent-standalone-name
return {
prefix: parts[0],
module: 'standalone',
type: 'agents',
name: parts.slice(3).join('-'),
};
}
if (parts.length === 3) {
// Core agent: bmad-agent-name
return {
@ -158,6 +175,16 @@ function parseDashName(filename) {
};
}
// Check for standalone non-agent: bmad-standalone-name
if (parts[1] === 'standalone') {
return {
prefix: parts[0],
module: 'standalone',
type: 'workflows', // Default to workflows for non-agent standalone items
name: parts.slice(2).join('-'),
};
}
// Otherwise, it's a module workflow/tool/task (bmad-module-name)
return {
prefix: parts[0],
@ -177,9 +204,12 @@ function parseDashName(filename) {
*/
function toUnderscoreName(module, type, name) {
const isAgent = type === AGENT_SEGMENT;
if (module === 'core' || module === 'standalone') {
if (module === 'core') {
return isAgent ? `bmad_agent_${name}.md` : `bmad_${name}.md`;
}
if (module === 'standalone') {
return isAgent ? `bmad_agent_standalone_${name}.md` : `bmad_standalone_${name}.md`;
}
return isAgent ? `bmad_${module}_agent_${name}.md` : `bmad_${module}_${name}.md`;
}
@ -231,6 +261,15 @@ function parseUnderscoreName(filename) {
if (agentIndex !== -1) {
if (agentIndex === 1) {
// bmad_agent_... - check for standalone
if (parts.length >= 4 && parts[2] === 'standalone') {
return {
prefix: parts[0],
module: 'standalone',
type: 'agents',
name: parts.slice(3).join('_'),
};
}
return {
prefix: parts[0],
module: 'core',
@ -256,6 +295,16 @@ function parseUnderscoreName(filename) {
};
}
// Check for standalone non-agent: bmad_standalone_name
if (parts[1] === 'standalone') {
return {
prefix: parts[0],
module: 'standalone',
type: 'workflows',
name: parts.slice(2).join('_'),
};
}
return {
prefix: parts[0],
module: parts[1],