From c0898ef003879d0d4c419e20bde8faaf4c009e47 Mon Sep 17 00:00:00 2001 From: Harley McDaniel Date: Wed, 18 Mar 2026 09:07:52 -0600 Subject: [PATCH] fix: update CLI installer paths after skill directory rename The refactor in 0380656d renamed src/core -> src/core-skills and src/bmm -> src/bmm-skills but did not update the CLI installer code. This broke `npx bmad-method install` with ENOENT errors scanning the old directory paths. Also restores the deleted src/utility/agent-components/ directory whose fragment templates are still required by activation-builder.js during agent compilation. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../agent-components/activation-rules.txt | 6 +++ .../agent-components/activation-steps.txt | 14 +++++++ .../agent-components/agent-command-header.md | 1 + .../agent.customize.template.yaml | 41 +++++++++++++++++++ .../agent-components/handler-action.txt | 4 ++ src/utility/agent-components/handler-data.txt | 5 +++ src/utility/agent-components/handler-exec.txt | 6 +++ .../agent-components/handler-multi.txt | 13 ++++++ src/utility/agent-components/handler-tmpl.txt | 5 +++ .../agent-components/menu-handlers.txt | 6 +++ .../lib/core/dependency-resolver.js | 22 +++++----- tools/cli/installers/lib/core/installer.js | 6 +-- tools/cli/installers/lib/core/manifest.js | 4 +- .../ide/shared/workflow-command-generator.js | 8 ++-- tools/cli/installers/lib/modules/manager.js | 18 ++++---- tools/cli/lib/project-root.js | 14 +++---- tools/cli/lib/yaml-xml-builder.js | 10 +++-- 17 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 src/utility/agent-components/activation-rules.txt create mode 100644 src/utility/agent-components/activation-steps.txt create mode 100644 src/utility/agent-components/agent-command-header.md create mode 100644 src/utility/agent-components/agent.customize.template.yaml create mode 100644 src/utility/agent-components/handler-action.txt create mode 100644 src/utility/agent-components/handler-data.txt create mode 100644 src/utility/agent-components/handler-exec.txt create mode 100644 src/utility/agent-components/handler-multi.txt create mode 100644 src/utility/agent-components/handler-tmpl.txt create mode 100644 src/utility/agent-components/menu-handlers.txt diff --git a/src/utility/agent-components/activation-rules.txt b/src/utility/agent-components/activation-rules.txt new file mode 100644 index 000000000..a67ae4993 --- /dev/null +++ b/src/utility/agent-components/activation-rules.txt @@ -0,0 +1,6 @@ + + ALWAYS communicate in {communication_language} UNLESS contradicted by communication_style. + Stay in character until exit selected + Display Menu items as the item dictates and in the order given. + Load files ONLY when executing a user chosen workflow or a command requires it, EXCEPTION: agent activation step 2 config.yaml + \ No newline at end of file diff --git a/src/utility/agent-components/activation-steps.txt b/src/utility/agent-components/activation-steps.txt new file mode 100644 index 000000000..726be3e06 --- /dev/null +++ b/src/utility/agent-components/activation-steps.txt @@ -0,0 +1,14 @@ + Load persona from this current agent file (already in context) + 🚨 IMMEDIATE ACTION REQUIRED - BEFORE ANY OUTPUT: + - Load and read {project-root}/_bmad/{{module}}/config.yaml NOW + - Store ALL fields as session variables: {user_name}, {communication_language}, {output_folder} + - VERIFY: If config not loaded, STOP and report error to user + - DO NOT PROCEED to step 3 until config is successfully loaded and variables stored + + Remember: user's name is {user_name} + {AGENT_SPECIFIC_STEPS} + Show greeting using {user_name} from config, communicate in {communication_language}, then display numbered list of ALL menu items from menu section + Let {user_name} know they can invoke the `bmad-help` skill at any time to get advice on what to do next, and that they can combine it with what they need help with Invoke the `bmad-help` skill with a question like "where should I start with an idea I have that does XYZ?" + STOP and WAIT for user input - do NOT execute menu items automatically - accept number or cmd trigger or fuzzy command match + On user input: Number → process menu item[n] | Text → case-insensitive substring match | Multiple matches → ask user to clarify | No match → show "Not recognized" + 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 diff --git a/src/utility/agent-components/agent-command-header.md b/src/utility/agent-components/agent-command-header.md new file mode 100644 index 000000000..d4f9b5d6a --- /dev/null +++ b/src/utility/agent-components/agent-command-header.md @@ -0,0 +1 @@ +You must fully embody this agent's persona and follow all activation instructions, steps and rules exactly as specified. NEVER break character until given an exit command. diff --git a/src/utility/agent-components/agent.customize.template.yaml b/src/utility/agent-components/agent.customize.template.yaml new file mode 100644 index 000000000..b8cc648b4 --- /dev/null +++ b/src/utility/agent-components/agent.customize.template.yaml @@ -0,0 +1,41 @@ +# Agent Customization +# Customize any section below - all are optional + +# Override agent name +agent: + metadata: + name: "" + +# Replace entire persona (not merged) +persona: + role: "" + identity: "" + communication_style: "" + principles: [] + +# Add custom critical actions (appended after standard config loading) +critical_actions: [] + +# Add persistent memories for the agent +memories: [] +# Example: +# memories: +# - "User prefers detailed technical explanations" +# - "Current project uses React and TypeScript" + +# Add custom menu items (appended to base menu) +# Don't include * prefix or help/exit - auto-injected +menu: [] +# Example: +# menu: +# - trigger: my-workflow +# workflow: "{project-root}/custom/my.yaml" +# description: My custom workflow + +# Add custom prompts (for action="#id" handlers) +prompts: [] +# Example: +# prompts: +# - id: my-prompt +# content: | +# Prompt instructions here diff --git a/src/utility/agent-components/handler-action.txt b/src/utility/agent-components/handler-action.txt new file mode 100644 index 000000000..db31f4852 --- /dev/null +++ b/src/utility/agent-components/handler-action.txt @@ -0,0 +1,4 @@ + + When menu item has: action="#id" → Find prompt with id="id" in current agent XML, follow its content + When menu item has: action="text" → Follow the text directly as an inline instruction + \ No newline at end of file diff --git a/src/utility/agent-components/handler-data.txt b/src/utility/agent-components/handler-data.txt new file mode 100644 index 000000000..14036fa58 --- /dev/null +++ b/src/utility/agent-components/handler-data.txt @@ -0,0 +1,5 @@ + + When menu item has: data="path/to/file.json|yaml|yml|csv|xml" + Load the file first, parse according to extension + Make available as {data} variable to subsequent handler operations + diff --git a/src/utility/agent-components/handler-exec.txt b/src/utility/agent-components/handler-exec.txt new file mode 100644 index 000000000..1c8459a64 --- /dev/null +++ b/src/utility/agent-components/handler-exec.txt @@ -0,0 +1,6 @@ + + When menu item or handler has: exec="path/to/file.md": + 1. Read fully and follow the file at that path + 2. Process the complete file and follow all instructions within it + 3. If there is data="some/path/data-foo.md" with the same item, pass that data path to the executed file as context. + \ No newline at end of file diff --git a/src/utility/agent-components/handler-multi.txt b/src/utility/agent-components/handler-multi.txt new file mode 100644 index 000000000..e05be2390 --- /dev/null +++ b/src/utility/agent-components/handler-multi.txt @@ -0,0 +1,13 @@ + + When menu item has: type="multi" with nested handlers + 1. Display the multi item text as a single menu option + 2. Parse all nested handlers within the multi item + 3. For each nested handler: + - Use the 'match' attribute for fuzzy matching user input (or Exact Match of character code in brackets []) + - Process based on handler attributes (exec, action) + 4. When user input matches a handler's 'match' pattern: + - For exec="path/to/file.md": follow the `handler type="exec"` instructions + - For action="...": Perform the specified action directly + 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 + \ No newline at end of file diff --git a/src/utility/agent-components/handler-tmpl.txt b/src/utility/agent-components/handler-tmpl.txt new file mode 100644 index 000000000..a190504b7 --- /dev/null +++ b/src/utility/agent-components/handler-tmpl.txt @@ -0,0 +1,5 @@ + + 1. When menu item has: tmpl="path/to/template.md" + 2. Load template file, parse as markdown with {{mustache}} style variables + 3. Make template content available as {template} to action/exec/workflow handlers + \ No newline at end of file diff --git a/src/utility/agent-components/menu-handlers.txt b/src/utility/agent-components/menu-handlers.txt new file mode 100644 index 000000000..3dd1ae9c7 --- /dev/null +++ b/src/utility/agent-components/menu-handlers.txt @@ -0,0 +1,6 @@ + + {DYNAMIC_EXTRACT_LIST} + + {DYNAMIC_HANDLERS} + + diff --git a/tools/cli/installers/lib/core/dependency-resolver.js b/tools/cli/installers/lib/core/dependency-resolver.js index 3fb282c5d..8b0971bf1 100644 --- a/tools/cli/installers/lib/core/dependency-resolver.js +++ b/tools/cli/installers/lib/core/dependency-resolver.js @@ -82,11 +82,11 @@ class DependencyResolver { // Check if this is a source directory (has 'src' subdirectory) const srcDir = path.join(bmadDir, 'src'); if (await fs.pathExists(srcDir)) { - // Source directory structure: src/core or src/bmm + // Source directory structure: src/core-skills or src/bmm-skills if (module === 'core') { - moduleDir = path.join(srcDir, 'core'); + moduleDir = path.join(srcDir, 'core-skills'); } else if (module === 'bmm') { - moduleDir = path.join(srcDir, 'bmm'); + moduleDir = path.join(srcDir, 'bmm-skills'); } } @@ -401,8 +401,8 @@ class DependencyResolver { const bmadPath = dep.dependency.replace(/^bmad\//, ''); // Try to resolve as if it's in src structure - // bmad/core/tasks/foo.md -> src/core/tasks/foo.md - // bmad/bmm/tasks/bar.md -> src/bmm/tasks/bar.md (bmm is directly under src/) + // bmad/core/tasks/foo.md -> src/core-skills/tasks/foo.md + // bmad/bmm/tasks/bar.md -> src/bmm-skills/tasks/bar.md (bmm is directly under src/) // bmad/cis/agents/bar.md -> src/modules/cis/agents/bar.md if (bmadPath.startsWith('core/')) { @@ -584,11 +584,11 @@ class DependencyResolver { const relative = path.relative(bmadDir, filePath); const parts = relative.split(path.sep); - // Handle source directory structure (src/core, src/bmm, or src/modules/xxx) + // Handle source directory structure (src/core-skills, src/bmm-skills, or src/modules/xxx) if (parts[0] === 'src') { - if (parts[1] === 'core') { + if (parts[1] === 'core-skills') { return 'core'; - } else if (parts[1] === 'bmm') { + } else if (parts[1] === 'bmm-skills') { return 'bmm'; } else if (parts[1] === 'modules' && parts.length > 2) { return parts[2]; @@ -631,11 +631,11 @@ class DependencyResolver { let moduleBase; // Check if file is in source directory structure - if (file.includes('/src/core/') || file.includes('/src/bmm/')) { + if (file.includes('/src/core-skills/') || file.includes('/src/bmm-skills/')) { if (module === 'core') { - moduleBase = path.join(bmadDir, 'src', 'core'); + moduleBase = path.join(bmadDir, 'src', 'core-skills'); } else if (module === 'bmm') { - moduleBase = path.join(bmadDir, 'src', 'bmm'); + moduleBase = path.join(bmadDir, 'src', 'bmm-skills'); } } else { moduleBase = module === 'core' ? path.join(bmadDir, 'core') : path.join(bmadDir, 'modules', module); diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index 85864145f..5022ab954 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -1789,8 +1789,8 @@ class Installer { .filter((entry) => entry.isDirectory() && entry.name !== '_config' && entry.name !== 'docs' && entry.name !== '_memory') .map((entry) => entry.name); - // Add core module to scan (it's installed at root level as _config, but we check src/core) - const coreModulePath = getSourcePath('core'); + // Add core module to scan (it's installed at root level as _config, but we check src/core-skills) + const coreModulePath = getSourcePath('core-skills'); const modulePaths = new Map(); // Map all module source paths @@ -2709,7 +2709,7 @@ class Installer { // Get source path let sourcePath; if (moduleId === 'core') { - sourcePath = getSourcePath('core'); + sourcePath = getSourcePath('core-skills'); } else { // First check if it's in the custom cache if (customModuleSources.has(moduleId)) { diff --git a/tools/cli/installers/lib/core/manifest.js b/tools/cli/installers/lib/core/manifest.js index cb5368843..0b5fc447b 100644 --- a/tools/cli/installers/lib/core/manifest.js +++ b/tools/cli/installers/lib/core/manifest.js @@ -764,10 +764,10 @@ class Manifest { const configs = {}; for (const moduleName of modules) { - // Handle core module differently - it's in src/core not src/modules/core + // Handle core module differently - it's in src/core-skills not src/modules/core const configPath = moduleName === 'core' - ? path.join(process.cwd(), 'src', 'core', 'config.yaml') + ? path.join(process.cwd(), 'src', 'core-skills', 'config.yaml') : path.join(process.cwd(), 'src', 'modules', moduleName, 'config.yaml'); try { diff --git a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js index ed8c3e508..996c8728d 100644 --- a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js @@ -146,13 +146,13 @@ When running any workflow: transformWorkflowPath(workflowPath) { let transformed = workflowPath; - if (workflowPath.includes('/src/bmm/')) { - const match = workflowPath.match(/\/src\/bmm\/(.+)/); + if (workflowPath.includes('/src/bmm-skills/')) { + const match = workflowPath.match(/\/src\/bmm-skills\/(.+)/); if (match) { transformed = `{project-root}/${this.bmadFolderName}/bmm/${match[1]}`; } - } else if (workflowPath.includes('/src/core/')) { - const match = workflowPath.match(/\/src\/core\/(.+)/); + } else if (workflowPath.includes('/src/core-skills/')) { + const match = workflowPath.match(/\/src\/core-skills\/(.+)/); if (match) { transformed = `{project-root}/${this.bmadFolderName}/core/${match[1]}`; } diff --git a/tools/cli/installers/lib/modules/manager.js b/tools/cli/installers/lib/modules/manager.js index 9bc027d85..625e38e98 100644 --- a/tools/cli/installers/lib/modules/manager.js +++ b/tools/cli/installers/lib/modules/manager.js @@ -195,10 +195,10 @@ class ModuleManager { const modules = []; const customModules = []; - // Add built-in bmm module (directly under src/bmm) - const bmmPath = getSourcePath('bmm'); + // Add built-in bmm module (directly under src/bmm-skills) + const bmmPath = getSourcePath('bmm-skills'); if (await fs.pathExists(bmmPath)) { - const bmmInfo = await this.getModuleInfo(bmmPath, 'bmm', 'src/bmm'); + const bmmInfo = await this.getModuleInfo(bmmPath, 'bmm', 'src/bmm-skills'); if (bmmInfo) { modules.push(bmmInfo); } @@ -250,8 +250,8 @@ class ModuleManager { return null; } - // Mark as custom if it's using custom.yaml OR if it's outside src/bmm or src/core - const isCustomSource = sourceDescription !== 'src/bmm' && sourceDescription !== 'src/core' && sourceDescription !== 'src/modules'; + // Mark as custom if it's using custom.yaml OR if it's outside src/bmm-skills or src/core-skills + const isCustomSource = sourceDescription !== 'src/bmm-skills' && sourceDescription !== 'src/core-skills' && sourceDescription !== 'src/modules'; const moduleInfo = { id: defaultName, path: modulePath, @@ -300,9 +300,9 @@ class ModuleManager { return this.customModulePaths.get(moduleCode); } - // Check for built-in bmm module (directly under src/bmm) + // Check for built-in bmm module (directly under src/bmm-skills) if (moduleCode === 'bmm') { - const bmmPath = getSourcePath('bmm'); + const bmmPath = getSourcePath('bmm-skills'); if (await fs.pathExists(bmmPath)) { return bmmPath; } @@ -1141,10 +1141,10 @@ class ModuleManager { const projectRoot = path.dirname(bmadDir); const emptyResult = { createdDirs: [], movedDirs: [], createdWdsFolders: [] }; - // Special handling for core module - it's in src/core not src/modules + // Special handling for core module - it's in src/core-skills not src/modules let sourcePath; if (moduleName === 'core') { - sourcePath = getSourcePath('core'); + sourcePath = getSourcePath('core-skills'); } else { sourcePath = await this.findModuleSource(moduleName, { silent: true }); if (!sourcePath) { diff --git a/tools/cli/lib/project-root.js b/tools/cli/lib/project-root.js index 4533c773c..5a1f97830 100644 --- a/tools/cli/lib/project-root.js +++ b/tools/cli/lib/project-root.js @@ -16,7 +16,7 @@ function findProjectRoot(startPath = __dirname) { try { const pkg = fs.readJsonSync(packagePath); // Check if this is the BMAD project - if (pkg.name === 'bmad-method' || fs.existsSync(path.join(currentPath, 'src', 'core'))) { + if (pkg.name === 'bmad-method' || fs.existsSync(path.join(currentPath, 'src', 'core-skills'))) { return currentPath; } } catch { @@ -24,8 +24,8 @@ function findProjectRoot(startPath = __dirname) { } } - // Also check for src/core as a marker - if (fs.existsSync(path.join(currentPath, 'src', 'core', 'agents'))) { + // Also check for src/core-skills as a marker + if (fs.existsSync(path.join(currentPath, 'src', 'core-skills'))) { return currentPath; } @@ -55,16 +55,16 @@ function getSourcePath(...segments) { /** * Get path to a module's directory - * bmm is a built-in module directly under src/ - * core is also directly under src/ + * bmm is a built-in module directly under src/bmm-skills + * core is also directly under src/core-skills * All other modules are stored remote */ function getModulePath(moduleName, ...segments) { if (moduleName === 'core') { - return getSourcePath('core', ...segments); + return getSourcePath('core-skills', ...segments); } if (moduleName === 'bmm') { - return getSourcePath('bmm', ...segments); + return getSourcePath('bmm-skills', ...segments); } return getSourcePath('modules', moduleName, ...segments); } diff --git a/tools/cli/lib/yaml-xml-builder.js b/tools/cli/lib/yaml-xml-builder.js index f4f8e2f5a..f7c466e88 100644 --- a/tools/cli/lib/yaml-xml-builder.js +++ b/tools/cli/lib/yaml-xml-builder.js @@ -495,7 +495,7 @@ class YamlXmlBuilder { // Extract module from path (e.g., /path/to/modules/bmm/agents/pm.yaml -> bmm) // or /path/to/bmad/bmm/agents/pm.yaml -> bmm - // or /path/to/src/bmm/agents/pm.yaml -> bmm + // or /path/to/src/bmm-skills/agents/pm.yaml -> bmm let module = 'core'; // default to core const pathParts = agentYamlPath.split(path.sep); @@ -515,10 +515,12 @@ class YamlXmlBuilder { module = potentialModule; } } else if (srcIndex !== -1 && pathParts[srcIndex + 1]) { - // Path contains /src/{module}/ (bmm and core are directly under src/) + // Path contains /src/{module-skills}/ (bmm-skills and core-skills are directly under src/) const potentialModule = pathParts[srcIndex + 1]; - if (potentialModule === 'bmm' || potentialModule === 'core') { - module = potentialModule; + if (potentialModule === 'bmm-skills') { + module = 'bmm'; + } else if (potentialModule === 'core-skills') { + module = 'core'; } }