Compare commits

..

No commits in common. "48a7ec8bffd0c762cf44629a02816956ad0e8cc8" and "7ee5fa313bcae045945d2df80b7fbef67873eeb4" have entirely different histories.

9 changed files with 159 additions and 270 deletions

View File

@ -227,39 +227,37 @@ Prepare the content to append to the document:
### Architecture Completeness Checklist
Mark each item `[x]` only if validation confirms it; leave `[ ]` if it is missing, partial, or unverified. Any unchecked item must be reflected in the Gap Analysis above and in the Overall Status below.
**✅ Requirements Analysis**
**Requirements Analysis**
- [x] Project context thoroughly analyzed
- [x] Scale and complexity assessed
- [x] Technical constraints identified
- [x] Cross-cutting concerns mapped
- [ ] Project context thoroughly analyzed
- [ ] Scale and complexity assessed
- [ ] Technical constraints identified
- [ ] Cross-cutting concerns mapped
**✅ Architectural Decisions**
**Architectural Decisions**
- [x] Critical decisions documented with versions
- [x] Technology stack fully specified
- [x] Integration patterns defined
- [x] Performance considerations addressed
- [ ] Critical decisions documented with versions
- [ ] Technology stack fully specified
- [ ] Integration patterns defined
- [ ] Performance considerations addressed
**✅ Implementation Patterns**
**Implementation Patterns**
- [x] Naming conventions established
- [x] Structure patterns defined
- [x] Communication patterns specified
- [x] Process patterns documented
- [ ] Naming conventions established
- [ ] Structure patterns defined
- [ ] Communication patterns specified
- [ ] Process patterns documented
**✅ Project Structure**
**Project Structure**
- [ ] Complete directory structure defined
- [ ] Component boundaries established
- [ ] Integration points mapped
- [ ] Requirements to structure mapping complete
- [x] Complete directory structure defined
- [x] Component boundaries established
- [x] Integration points mapped
- [x] Requirements to structure mapping complete
### Architecture Readiness Assessment
**Overall Status:** {{READY FOR IMPLEMENTATION | READY WITH MINOR GAPS | NOT READY}} (choose READY FOR IMPLEMENTATION only when all 16 checklist items are `[x]` and no Critical Gaps remain; choose NOT READY when any Critical Gap is open or any Requirements Analysis or Architectural Decisions item is unchecked; otherwise READY WITH MINOR GAPS)
**Overall Status:** READY FOR IMPLEMENTATION
**Confidence Level:** {{high/medium/low}} based on validation results

View File

@ -1,33 +1,33 @@
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
BMad Method,_meta,,,,,,,,,false,https://docs.bmad-method.org/llms.txt,
BMad Method,bmad-document-project,Document Project,DP,Analyze an existing project to produce useful documentation.,,,anytime,,,false,project-knowledge,*
BMad Method,bmad-generate-project-context,Generate Project Context,GPC,Scan existing codebase to generate a lean LLM-optimized project-context.md. Essential for brownfield projects.,,,anytime,,,false,output_folder,project context
BMad Method,bmad-quick-dev,Quick Dev,QQ,Unified intent-in code-out workflow: clarify plan implement review and present.,,,anytime,,,false,implementation_artifacts,spec and project implementation
BMad Method,bmad-correct-course,Correct Course,CC,Navigate significant changes. May recommend start over update PRD redo architecture sprint planning or correct epics and stories.,,,anytime,,,false,planning_artifacts,change proposal
BMad Method,bmad-document-project,Document Project,DP,Analyze an existing project to produce useful documentation.,,anytime,,,false,project-knowledge,*
BMad Method,bmad-generate-project-context,Generate Project Context,GPC,Scan existing codebase to generate a lean LLM-optimized project-context.md. Essential for brownfield projects.,,anytime,,,false,output_folder,project context
BMad Method,bmad-quick-dev,Quick Dev,QQ,Unified intent-in code-out workflow: clarify plan implement review and present.,,anytime,,,false,implementation_artifacts,spec and project implementation
BMad Method,bmad-correct-course,Correct Course,CC,Navigate significant changes. May recommend start over update PRD redo architecture sprint planning or correct epics and stories.,,anytime,,,false,planning_artifacts,change proposal
BMad Method,bmad-agent-tech-writer,Write Document,WD,"Describe in detail what you want, and the agent will follow documentation best practices. Multi-turn conversation with subprocess for research/review.",write,,anytime,,,false,project-knowledge,document
BMad Method,bmad-agent-tech-writer,Update Standards,US,Update agent memory documentation-standards.md with your specific preferences if you discover missing document conventions.,update-standards,,anytime,,,false,_bmad/_memory/tech-writer-sidecar,standards
BMad Method,bmad-agent-tech-writer,Mermaid Generate,MG,Create a Mermaid diagram based on user description. Will suggest diagram types if not specified.,mermaid,,anytime,,,false,planning_artifacts,mermaid diagram
BMad Method,bmad-agent-tech-writer,Validate Document,VD,Review the specified document against documentation standards and best practices. Returns specific actionable improvement suggestions organized by priority.,validate,[path],anytime,,,false,planning_artifacts,validation report
BMad Method,bmad-agent-tech-writer,Explain Concept,EC,Create clear technical explanations with examples and diagrams for complex concepts.,explain,[topic],anytime,,,false,project_knowledge,explanation
BMad Method,bmad-brainstorming,Brainstorm Project,BP,Expert guided facilitation through a single or multiple techniques.,,,1-analysis,,,false,planning_artifacts,brainstorming session
BMad Method,bmad-market-research,Market Research,MR,Market analysis competitive landscape customer needs and trends.,,,1-analysis,,,false,planning_artifacts|project-knowledge,research documents
BMad Method,bmad-domain-research,Domain Research,DR,Industry domain deep dive subject matter expertise and terminology.,,,1-analysis,,,false,planning_artifacts|project_knowledge,research documents
BMad Method,bmad-technical-research,Technical Research,TR,Technical feasibility architecture options and implementation approaches.,,,1-analysis,,,false,planning_artifacts|project_knowledge,research documents
BMad Method,bmad-brainstorming,Brainstorm Project,BP,Expert guided facilitation through a single or multiple techniques.,,1-analysis,,,false,planning_artifacts,brainstorming session
BMad Method,bmad-market-research,Market Research,MR,"Market analysis competitive landscape customer needs and trends.",,1-analysis,,,false,"planning_artifacts|project-knowledge",research documents
BMad Method,bmad-domain-research,Domain Research,DR,Industry domain deep dive subject matter expertise and terminology.,,1-analysis,,,false,"planning_artifacts|project_knowledge",research documents
BMad Method,bmad-technical-research,Technical Research,TR,Technical feasibility architecture options and implementation approaches.,,1-analysis,,,false,"planning_artifacts|project_knowledge",research documents
BMad Method,bmad-product-brief,Create Brief,CB,An expert guided experience to nail down your product idea in a brief. a gentler approach than PRFAQ when you are already sure of your concept and nothing will sway you.,,-A,1-analysis,,,false,planning_artifacts,product brief
BMad Method,bmad-prfaq,PRFAQ Challenge,WB,Working Backwards guided experience to forge and stress-test your product concept to ensure you have a great product that users will love and need through the PRFAQ gauntlet to determine feasibility and alignment with user needs. alternative to product brief.,,-H,1-analysis,,,false,planning_artifacts,prfaq document
BMad Method,bmad-create-prd,Create PRD,CP,Expert led facilitation to produce your Product Requirements Document.,,,2-planning,,,true,planning_artifacts,prd
BMad Method,bmad-create-prd,Create PRD,CP,Expert led facilitation to produce your Product Requirements Document.,,2-planning,,,true,planning_artifacts,prd
BMad Method,bmad-validate-prd,Validate PRD,VP,,,[path],2-planning,bmad-create-prd,,false,planning_artifacts,prd validation report
BMad Method,bmad-edit-prd,Edit PRD,EP,,,[path],2-planning,bmad-validate-prd,,false,planning_artifacts,updated prd
BMad Method,bmad-create-ux-design,Create UX,CU,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project.",,,2-planning,bmad-create-prd,,false,planning_artifacts,ux design
BMad Method,bmad-create-architecture,Create Architecture,CA,Guided workflow to document technical decisions.,,,3-solutioning,,,true,planning_artifacts,architecture
BMad Method,bmad-create-epics-and-stories,Create Epics and Stories,CE,,,,3-solutioning,bmad-create-architecture,,true,planning_artifacts,epics and stories
BMad Method,bmad-check-implementation-readiness,Check Implementation Readiness,IR,Ensure PRD UX Architecture and Epics Stories are aligned.,,,3-solutioning,bmad-create-epics-and-stories,,true,planning_artifacts,readiness report
BMad Method,bmad-sprint-planning,Sprint Planning,SP,Kicks off implementation by producing a plan the implementation agents will follow in sequence for every story.,,,4-implementation,,,true,implementation_artifacts,sprint status
BMad Method,bmad-sprint-status,Sprint Status,SS,Anytime: Summarize sprint status and route to next workflow.,,,4-implementation,bmad-sprint-planning,,false,,
BMad Method,bmad-create-story,Create Story,CS,Story cycle start: Prepare first found story in the sprint plan that is next or a specific epic/story designation.,create,,4-implementation,bmad-sprint-planning,bmad-create-story:validate,true,implementation_artifacts,story
BMad Method,bmad-create-ux-design,Create UX,CU,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project.",,2-planning,bmad-create-prd,,false,planning_artifacts,ux design
BMad Method,bmad-create-architecture,Create Architecture,CA,Guided workflow to document technical decisions.,,3-solutioning,,,true,planning_artifacts,architecture
BMad Method,bmad-create-epics-and-stories,Create Epics and Stories,CE,,,3-solutioning,bmad-create-architecture,,true,planning_artifacts,epics and stories
BMad Method,bmad-check-implementation-readiness,Check Implementation Readiness,IR,Ensure PRD UX Architecture and Epics Stories are aligned.,,3-solutioning,bmad-create-epics-and-stories,,true,planning_artifacts,readiness report
BMad Method,bmad-sprint-planning,Sprint Planning,SP,Kicks off implementation by producing a plan the implementation agents will follow in sequence for every story.,,4-implementation,,,true,implementation_artifacts,sprint status
BMad Method,bmad-sprint-status,Sprint Status,SS,Anytime: Summarize sprint status and route to next workflow.,,4-implementation,bmad-sprint-planning,,false,,
BMad Method,bmad-create-story,Create Story,CS,"Story cycle start: Prepare first found story in the sprint plan that is next or a specific epic/story designation.",create,,4-implementation,bmad-sprint-planning,bmad-create-story:validate,true,implementation_artifacts,story
BMad Method,bmad-create-story,Validate Story,VS,Validates story readiness and completeness before development work begins.,validate,,4-implementation,bmad-create-story:create,bmad-dev-story,false,implementation_artifacts,story validation report
BMad Method,bmad-dev-story,Dev Story,DS,Story cycle: Execute story implementation tasks and tests then CR then back to DS if fixes needed.,,,4-implementation,bmad-create-story:validate,,true,,
BMad Method,bmad-code-review,Code Review,CR,Story cycle: If issues back to DS if approved then next CS or ER if epic complete.,,,4-implementation,bmad-dev-story,,false,,
BMad Method,bmad-checkpoint-preview,Checkpoint,CK,Guided walkthrough of a change from purpose and context into details. Use for human review of commits branches or PRs.,,,4-implementation,,,false,,
BMad Method,bmad-qa-generate-e2e-tests,QA Automation Test,QA,Generate automated API and E2E tests for implemented code. NOT for code review or story validation — use CR for that.,,,4-implementation,bmad-dev-story,,false,implementation_artifacts,test suite
BMad Method,bmad-retrospective,Retrospective,ER,Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC.,,,4-implementation,bmad-code-review,,false,implementation_artifacts,retrospective
BMad Method,bmad-dev-story,Dev Story,DS,Story cycle: Execute story implementation tasks and tests then CR then back to DS if fixes needed.,,4-implementation,bmad-create-story:validate,,true,,
BMad Method,bmad-code-review,Code Review,CR,Story cycle: If issues back to DS if approved then next CS or ER if epic complete.,,4-implementation,bmad-dev-story,,false,,
BMad Method,bmad-checkpoint-preview,Checkpoint,CK,Guided walkthrough of a change from purpose and context into details. Use for human review of commits branches or PRs.,,4-implementation,,,false,,
BMad Method,bmad-qa-generate-e2e-tests,QA Automation Test,QA,Generate automated API and E2E tests for implemented code. NOT for code review or story validation — use CR for that.,,4-implementation,bmad-dev-story,,false,implementation_artifacts,test suite
BMad Method,bmad-retrospective,Retrospective,ER,Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC.,,4-implementation,bmad-code-review,,false,implementation_artifacts,retrospective

Can't render this file because it has a wrong number of fields in line 3.

View File

@ -5,11 +5,15 @@ default_selected: true # This module will be selected by default for new install
# Variables from Core Config inserted:
## user_name
## project_name
## communication_language
## document_output_language
## output_folder
project_name:
prompt: "What is your project called?"
default: "{directory_name}"
result: "{value}"
user_skill_level:
prompt:
- "What is your development experience level?"

View File

@ -1,13 +1,13 @@
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
Core,_meta,,,,,,,,,false,https://docs.bmad-method.org/llms.txt,
Core,bmad-brainstorming,Brainstorming,BSP,Use early in ideation or when stuck generating ideas.,,,anytime,,,false,{output_folder}/brainstorming,brainstorming session
Core,bmad-party-mode,Party Mode,PM,Orchestrate multi-agent discussions when you need multiple perspectives or want agents to collaborate.,,,anytime,,,false,,
Core,bmad-help,BMad Help,BH,,,,anytime,,,false,,
Core,bmad-index-docs,Index Docs,ID,Use when LLM needs to understand available docs without loading everything.,,,anytime,,,false,,
Core,bmad-shard-doc,Shard Document,SD,Use when doc becomes too large (>500 lines) to manage effectively.,,[path],anytime,,,false,,
Core,bmad-editorial-review-prose,Editorial Review - Prose,EP,Use after drafting to polish written content.,,[path],anytime,,,false,report located with target document,three-column markdown table with suggested fixes
Core,bmad-editorial-review-structure,Editorial Review - Structure,ES,Use when doc produced from multiple subprocesses or needs structural improvement.,,[path],anytime,,,false,report located with target document,
Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assurance or before finalizing deliverables. Code Review in other modules runs this automatically, but also useful for document reviews.",,[path],anytime,,,false,,
Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,,[path],anytime,,,false,,
Core,bmad-distillator,Distillator,DG,Use when you need token-efficient distillates that preserve all information for downstream LLM consumption.,,[path],anytime,,,false,adjacent to source document or specified output_path,distillate markdown file(s)
Core,bmad-customize,BMad Customize,BC,"Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required.",,,anytime,,,false,{project-root}/_bmad/custom,TOML override files
Core,bmad-brainstorming,Brainstorming,BSP,Use early in ideation or when stuck generating ideas.,,anytime,,,false,{output_folder}/brainstorming,brainstorming session
Core,bmad-party-mode,Party Mode,PM,Orchestrate multi-agent discussions when you need multiple perspectives or want agents to collaborate.,,anytime,,,false,,
Core,bmad-help,BMad Help,BH,,,anytime,,,false,,
Core,bmad-index-docs,Index Docs,ID,Use when LLM needs to understand available docs without loading everything.,,anytime,,,false,,
Core,bmad-shard-doc,Shard Document,SD,Use when doc becomes too large (>500 lines) to manage effectively.,[path],anytime,,,false,,
Core,bmad-editorial-review-prose,Editorial Review - Prose,EP,Use after drafting to polish written content.,[path],anytime,,,false,report located with target document,three-column markdown table with suggested fixes
Core,bmad-editorial-review-structure,Editorial Review - Structure,ES,Use when doc produced from multiple subprocesses or needs structural improvement.,[path],anytime,,,false,report located with target document,
Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assurance or before finalizing deliverables. Code Review in other modules runs this automatically, but also useful for document reviews.",[path],anytime,,,false,,
Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,[path],anytime,,,false,,
Core,bmad-distillator,Distillator,DG,Use when you need token-efficient distillates that preserve all information for downstream LLM consumption.,[path],anytime,,,false,adjacent to source document or specified output_path,distillate markdown file(s)
Core,bmad-customize,BMad Customize,BC,"Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required.",,anytime,,,false,{project-root}/_bmad/custom,TOML override files

Can't render this file because it has a wrong number of fields in line 3.

View File

@ -11,11 +11,6 @@ user_name:
default: "BMad"
result: "{value}"
project_name:
prompt: "What is your project called?"
default: "{directory_name}"
result: "{value}"
communication_language:
prompt: "What language should agents use when chatting with you?"
scope: user

View File

@ -1813,12 +1813,12 @@ async function runTests() {
const moduleConfigs = {
core: {
user_name: 'TestUser',
project_name: 'demo-project',
communication_language: 'Spanish',
document_output_language: 'English',
output_folder: '_bmad-output',
},
bmm: {
project_name: 'demo-project',
user_skill_level: 'expert',
planning_artifacts: '{project-root}/_bmad-output/planning-artifacts',
implementation_artifacts: '{project-root}/_bmad-output/implementation-artifacts',
@ -1826,10 +1826,7 @@ async function runTests() {
// Spread-from-core pollution: legacy per-module config.yaml merges
// core values into every module; writeCentralConfig must strip these
// from [modules.bmm] so core values only live in [core].
// project_name is now a core key (#2279), so it joins user_name etc.
// as a spread-from-core key that must be stripped.
user_name: 'TestUser',
project_name: 'stale-bmm-copy',
communication_language: 'Spanish',
document_output_language: 'English',
output_folder: '_bmad-output',
@ -1877,7 +1874,6 @@ async function runTests() {
assert(teamContent.includes('[core]'), 'config.toml has [core] section');
assert(teamContent.includes('document_output_language = "English"'), 'Team-scope core key lands in config.toml');
assert(teamContent.includes('output_folder = "_bmad-output"'), 'Team-scope output_folder lands in config.toml');
assert(teamContent.includes('project_name = "demo-project"'), 'project_name lands in [core] (core key as of #2279)');
assert(!teamContent.includes('user_name'), 'user_name (scope: user) is absent from config.toml');
assert(!teamContent.includes('communication_language'), 'communication_language (scope: user) is absent from config.toml');
@ -1892,9 +1888,7 @@ async function runTests() {
assert(bmmTeamMatch !== null, 'config.toml has [modules.bmm] section');
if (bmmTeamMatch) {
const bmmTeamBlock = bmmTeamMatch[0];
assert(bmmTeamBlock.includes('planning_artifacts'), 'bmm-owned team-scope key (planning_artifacts) lands under [modules.bmm]');
assert(!bmmTeamBlock.includes('project_name'), 'project_name stripped from [modules.bmm] (now a core key, #2279)');
assert(!bmmTeamBlock.includes('stale-bmm-copy'), 'stale bmm-copy of project_name not leaked into config.toml');
assert(bmmTeamBlock.includes('project_name = "demo-project"'), 'bmm team-scope key lands under [modules.bmm]');
assert(!bmmTeamBlock.includes('user_name'), 'user_name stripped from [modules.bmm] (core-key pollution)');
assert(!bmmTeamBlock.includes('communication_language'), 'communication_language stripped from [modules.bmm]');
assert(!bmmTeamBlock.includes('user_skill_level'), 'user_skill_level (scope: user) absent from [modules.bmm] in config.toml');
@ -2867,122 +2861,6 @@ async function runTests() {
console.log('');
// ============================================================
// Test Suite 43: project_name promoted to core + hoist migration (#2279)
// ============================================================
console.log(`${colors.yellow}Test Suite 43: project_name in core + hoist migration${colors.reset}\n`);
try {
const yamlLib = require('yaml');
const coreSchemaPath = path.join(__dirname, '..', 'src', 'core-skills', 'module.yaml');
const bmmSchemaPath = path.join(__dirname, '..', 'src', 'bmm-skills', 'module.yaml');
const coreSchema = yamlLib.parse(await fs.readFile(coreSchemaPath, 'utf8'));
const bmmSchema = yamlLib.parse(await fs.readFile(bmmSchemaPath, 'utf8'));
assert(
coreSchema.project_name && coreSchema.project_name.prompt && coreSchema.project_name.default === '{directory_name}',
'core/module.yaml declares project_name with {directory_name} default',
);
assert(coreSchema.project_name.scope === undefined, 'project_name has no user scope (project-scoped, not user-scoped)');
assert(bmmSchema.project_name === undefined, 'bmm/module.yaml no longer declares project_name (now inherited from core)');
// Set up a mock existing install: bmm directory has project_name (legacy),
// core has user_name but not project_name. After hoist, project_name should
// move to core, leaving bmm with only its own keys.
const fixtureRoot43 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-fixture-43-'));
const bmadDir43 = path.join(fixtureRoot43, '_bmad');
await fs.ensureDir(path.join(bmadDir43, '_config'));
await fs.writeFile(path.join(bmadDir43, '_config', 'manifest.yaml'), 'modules: []\n', 'utf8');
await fs.ensureDir(path.join(bmadDir43, 'core'));
await fs.ensureDir(path.join(bmadDir43, 'bmm'));
await fs.writeFile(path.join(bmadDir43, 'core', 'config.yaml'), 'user_name: alice\n', 'utf8');
await fs.writeFile(
path.join(bmadDir43, 'bmm', 'config.yaml'),
'project_name: legacy-from-bmm\nuser_skill_level: intermediate\n',
'utf8',
);
const officialModules43 = new OfficialModules();
await officialModules43.loadExistingConfig(fixtureRoot43);
assert(
officialModules43.existingConfig.core?.project_name === 'legacy-from-bmm',
'loadExistingConfig hoists bmm.project_name to core on existing-install upgrade',
);
assert(
!('project_name' in (officialModules43.existingConfig.bmm || {})),
'loadExistingConfig removes project_name from bmm after hoisting',
);
assert(
officialModules43.existingConfig.bmm?.user_skill_level === 'intermediate',
'loadExistingConfig leaves non-core bmm keys (user_skill_level) untouched',
);
assert(officialModules43.existingConfig.core?.user_name === 'alice', 'loadExistingConfig preserves pre-existing core values');
// Precedence: if core already has the key, hoist must NOT overwrite it.
const fixtureRoot43b = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-fixture-43b-'));
const bmadDir43b = path.join(fixtureRoot43b, '_bmad');
await fs.ensureDir(path.join(bmadDir43b, '_config'));
await fs.writeFile(path.join(bmadDir43b, '_config', 'manifest.yaml'), 'modules: []\n', 'utf8');
await fs.ensureDir(path.join(bmadDir43b, 'core'));
await fs.ensureDir(path.join(bmadDir43b, 'bmm'));
await fs.writeFile(path.join(bmadDir43b, 'core', 'config.yaml'), 'project_name: from-core\n', 'utf8');
await fs.writeFile(path.join(bmadDir43b, 'bmm', 'config.yaml'), 'project_name: stale-from-bmm\n', 'utf8');
const officialModules43b = new OfficialModules();
await officialModules43b.loadExistingConfig(fixtureRoot43b);
assert(officialModules43b.existingConfig.core?.project_name === 'from-core', 'hoist does not overwrite an existing core value');
assert(
!('project_name' in (officialModules43b.existingConfig.bmm || {})),
'hoist still strips the duplicate from bmm so writeCentralConfig partition stays clean',
);
// Malformed config.yaml (parses to a scalar) must not crash loadExistingConfig
// or the hoist pass — they should treat it as "no config for that module"
// and continue. Regression for augment review on PR #2348.
const fixtureRoot43c = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-fixture-43c-'));
const bmadDir43c = path.join(fixtureRoot43c, '_bmad');
await fs.ensureDir(path.join(bmadDir43c, '_config'));
await fs.writeFile(path.join(bmadDir43c, '_config', 'manifest.yaml'), 'modules: []\n', 'utf8');
await fs.ensureDir(path.join(bmadDir43c, 'core'));
await fs.ensureDir(path.join(bmadDir43c, 'bmm'));
// Scalar YAML — yaml.parse returns the literal 42 (truthy non-object).
// Pre-fix this crashed _hoistCoreKeysFromLegacyModuleConfigs with
// "Cannot use 'in' operator to search for 'project_name' in 42".
await fs.writeFile(path.join(bmadDir43c, 'core', 'config.yaml'), '42\n', 'utf8');
await fs.writeFile(path.join(bmadDir43c, 'bmm', 'config.yaml'), 'project_name: rescued\n', 'utf8');
const officialModules43c = new OfficialModules();
let crashErr;
try {
await officialModules43c.loadExistingConfig(fixtureRoot43c);
} catch (error) {
crashErr = error;
}
assert(!crashErr, 'loadExistingConfig does not crash on a scalar core/config.yaml', crashErr?.stack);
assert(
officialModules43c.existingConfig.core?.project_name === 'rescued',
'scalar core gets replaced with {} and bmm.project_name still hoists in',
);
await fs.remove(fixtureRoot43).catch(() => {});
await fs.remove(fixtureRoot43b).catch(() => {});
await fs.remove(fixtureRoot43c).catch(() => {});
} catch (error) {
console.log(`${colors.red}Test Suite 43 setup failed: ${error.message}${colors.reset}`);
console.log(error.stack);
failed++;
}
console.log('');
// ============================================================
// Summary
// ============================================================

View File

@ -923,15 +923,29 @@ class Installer {
/**
* Merge all module-help.csv files into a single bmad-help.csv.
* Scans all installed modules for module-help.csv and merges them.
* Output preserves the source schema verbatim see schema below.
* Enriches agent info from the in-memory agent list produced by ManifestGenerator.
* Output is written to _bmad/_config/bmad-help.csv.
* @param {string} bmadDir - BMAD installation directory
* @param {Array<Object>} _agentEntries - Unused; retained for call-site compatibility
* @param {Array<Object>} agentEntries - Agents collected from module.yaml (code, name, title, icon, module, ...)
*/
async mergeModuleHelpCatalogs(bmadDir, _agentEntries = []) {
async mergeModuleHelpCatalogs(bmadDir, agentEntries = []) {
const allRows = [];
const headerRow = 'module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs';
const COLUMN_COUNT = 13;
const PHASE_INDEX = 7;
const headerRow =
'module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs';
// Build agent lookup from the in-memory list (agent code → command + display fields).
const agentInfo = new Map();
for (const agent of agentEntries) {
if (!agent || !agent.code) continue;
const agentCommand = agent.module ? `bmad:${agent.module}:agent:${agent.code}` : `bmad:agent:${agent.code}`;
const displayName = agent.name || agent.code;
const titleCombined = agent.icon && agent.title ? `${agent.icon} ${agent.title}` : agent.title || agent.code;
agentInfo.set(agent.code, {
command: agentCommand,
displayName,
title: titleCombined,
});
}
// Get all installed module directories
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
@ -970,19 +984,64 @@ class Installer {
// Parse the line - handle quoted fields with commas
const columns = this.parseCSVLine(line);
if (columns.length < COLUMN_COUNT - 1) continue;
if (columns.length >= 12) {
// Map old schema to new schema
// Old: module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
// New: module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs
// Pad short rows; truncate over-long rows
const padded = columns.slice(0, COLUMN_COUNT);
while (padded.length < COLUMN_COUNT) padded.push('');
const [
module,
phase,
name,
code,
sequence,
workflowFile,
command,
required,
agentName,
options,
description,
outputLocation,
outputs,
] = columns;
// If module column is empty, fill with this module's name
// (core stays empty so its rows render as universal tools)
if ((!padded[0] || padded[0].trim() === '') && moduleName !== 'core') {
padded[0] = moduleName;
// Pass through _meta rows as-is (module metadata, not a skill)
if (phase === '_meta') {
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
const metaRow = [finalModule, '_meta', '', '', '', '', '', 'false', '', '', '', '', '', '', outputLocation || '', ''];
allRows.push(metaRow.map((c) => this.escapeCSVField(c)).join(','));
continue;
}
// If module column is empty, set it to this module's name (except for core which stays empty for universal tools)
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
// Lookup agent info
const cleanAgentName = agentName ? agentName.trim() : '';
const agentData = agentInfo.get(cleanAgentName) || { command: '', displayName: '', title: '' };
// Build new row with agent info
const newRow = [
finalModule,
phase || '',
name || '',
code || '',
sequence || '',
workflowFile || '',
command || '',
required || 'false',
cleanAgentName,
agentData.command,
agentData.displayName,
agentData.title,
options || '',
description || '',
outputLocation || '',
outputs || '',
];
allRows.push(newRow.map((c) => this.escapeCSVField(c)).join(','));
}
allRows.push(padded.map((c) => this.escapeCSVField(c)).join(','));
}
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
@ -994,34 +1053,44 @@ class Installer {
}
}
// Sort by module, then phase. Stable sort preserves authored order within a phase.
const decorated = allRows.map((row, index) => ({ row, index, cols: this.parseCSVLine(row) }));
decorated.sort((a, b) => {
const moduleA = (a.cols[0] || '').toLowerCase();
const moduleB = (b.cols[0] || '').toLowerCase();
if (moduleA !== moduleB) return moduleA.localeCompare(moduleB);
// Sort by module, then phase, then sequence
allRows.sort((a, b) => {
const colsA = this.parseCSVLine(a);
const colsB = this.parseCSVLine(b);
const phaseA = a.cols[PHASE_INDEX] || '';
const phaseB = b.cols[PHASE_INDEX] || '';
if (phaseA !== phaseB) return phaseA.localeCompare(phaseB);
// Module comparison (empty module/universal tools come first)
const moduleA = (colsA[0] || '').toLowerCase();
const moduleB = (colsB[0] || '').toLowerCase();
if (moduleA !== moduleB) {
return moduleA.localeCompare(moduleB);
}
return a.index - b.index;
// Phase comparison
const phaseA = colsA[1] || '';
const phaseB = colsB[1] || '';
if (phaseA !== phaseB) {
return phaseA.localeCompare(phaseB);
}
// Sequence comparison
const seqA = parseInt(colsA[4] || '0', 10);
const seqB = parseInt(colsB[4] || '0', 10);
return seqA - seqB;
});
const sortedRows = decorated.map((d) => d.row);
// Write merged catalog
const outputDir = path.join(bmadDir, '_config');
await fs.ensureDir(outputDir);
const outputPath = path.join(outputDir, 'bmad-help.csv');
const mergedContent = [headerRow, ...sortedRows].join('\n');
const mergedContent = [headerRow, ...allRows].join('\n');
await fs.writeFile(outputPath, mergedContent, 'utf8');
// Track the installed file
this.installedFiles.add(outputPath);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(` Generated bmad-help.csv: ${sortedRows.length} workflows`);
await prompts.log.message(` Generated bmad-help.csv: ${allRows.length} workflows`);
}
}

View File

@ -903,10 +903,7 @@ class OfficialModules {
try {
const content = await fs.readFile(moduleConfigPath, 'utf8');
const moduleConfig = yaml.parse(content);
// Only keep plain object parses. A corrupt config.yaml that parses
// to a scalar or array would crash later code that does `key in cfg`
// / `Object.keys(cfg)`; treat it the same as a parse error.
if (moduleConfig && typeof moduleConfig === 'object' && !Array.isArray(moduleConfig)) {
if (moduleConfig) {
this._existingConfig[entry.name] = moduleConfig;
foundAny = true;
}
@ -917,58 +914,9 @@ class OfficialModules {
}
}
if (foundAny) {
await this._hoistCoreKeysFromLegacyModuleConfigs();
}
return foundAny;
}
/**
* Migrate prior answers when a key has moved from a non-core module to core
* (e.g. project_name moving from bmm to core in #2279). Without this, the
* partition logic in writeCentralConfig drops the value from the bmm bucket
* (because it's now a core key) without re-homing it under [core], so the
* user's prior answer silently disappears on the next install/quick-update.
*/
async _hoistCoreKeysFromLegacyModuleConfigs() {
const coreSchemaPath = path.join(getSourcePath(), 'core-skills', 'module.yaml');
if (!(await fs.pathExists(coreSchemaPath))) return;
let coreSchema;
try {
coreSchema = yaml.parse(await fs.readFile(coreSchemaPath, 'utf8'));
} catch {
return;
}
if (!coreSchema || typeof coreSchema !== 'object') return;
const coreKeys = new Set(
Object.entries(coreSchema)
.filter(([, v]) => v && typeof v === 'object' && 'prompt' in v)
.map(([k]) => k),
);
if (coreKeys.size === 0) return;
// Belt-and-suspenders: loadExistingConfig already filters non-object parses,
// but anyone calling _hoistCoreKeysFromLegacyModuleConfigs in isolation (or
// future code paths populating _existingConfig directly) shouldn't be able
// to crash this with a scalar / array.
const existingCore = this._existingConfig.core;
this._existingConfig.core = existingCore && typeof existingCore === 'object' && !Array.isArray(existingCore) ? existingCore : {};
for (const [moduleName, cfg] of Object.entries(this._existingConfig)) {
if (moduleName === 'core' || !cfg || typeof cfg !== 'object' || Array.isArray(cfg)) continue;
for (const key of Object.keys(cfg)) {
if (!coreKeys.has(key)) continue;
if (!(key in this._existingConfig.core)) {
this._existingConfig.core[key] = cfg[key];
}
delete cfg[key];
}
}
}
/**
* Pre-scan module schemas to gather metadata for the configuration gateway prompt.
* Returns info about which modules have configurable options.

View File

@ -758,9 +758,6 @@ class UI {
const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
configCollector.collectedConfig.core = {
user_name: defaultUsername,
// {directory_name} default per src/core-skills/module.yaml — matches what the
// interactive flow resolves via buildQuestion()'s {directory_name} placeholder.
project_name: path.basename(directory),
communication_language: 'English',
document_output_language: 'English',
output_folder: '_bmad-output',