Compare commits
11 Commits
2a38637c32
...
6bd611413c
| Author | SHA1 | Date |
|---|---|---|
|
|
6bd611413c | |
|
|
1f99eb0496 | |
|
|
92498ebb52 | |
|
|
fdfe23fc22 | |
|
|
f32d1d4e8d | |
|
|
f9e7d65cf9 | |
|
|
bfdeef0453 | |
|
|
3ac8736756 | |
|
|
417fc44a98 | |
|
|
4e96a50515 | |
|
|
ad77c8e1c6 |
|
|
@ -1,51 +1,70 @@
|
||||||
num,category,method_name,description,output_pattern
|
num,category,method_name,description,output_pattern
|
||||||
1,collaboration,Stakeholder Round Table,Convene multiple personas to contribute diverse perspectives - essential for requirements gathering and finding balanced solutions across competing interests,perspectives → synthesis → alignment
|
1,advanced,Tree of Thoughts,Explore multiple reasoning paths simultaneously then evaluate and select the best - perfect for complex problems with multiple valid approaches,paths → evaluation → selection
|
||||||
2,collaboration,Expert Panel Review,Assemble domain experts for deep specialized analysis - ideal when technical depth and peer review quality are needed,expert views → consensus → recommendations
|
2,advanced,Graph of Thoughts,Model reasoning as an interconnected network of ideas to reveal hidden relationships - ideal for systems thinking and discovering emergent patterns,nodes → connections → patterns
|
||||||
3,collaboration,Debate Club Showdown,Two personas argue opposing positions while a moderator scores points - great for exploring controversial decisions and finding middle ground,thesis → antithesis → synthesis
|
3,advanced,Thread of Thought,Maintain coherent reasoning across long contexts by weaving a continuous narrative thread - essential for RAG systems and maintaining consistency,context → thread → synthesis
|
||||||
4,collaboration,User Persona Focus Group,Gather your product's user personas to react to proposals and share frustrations - essential for validating features and discovering unmet needs,reactions → concerns → priorities
|
4,advanced,Self-Consistency Validation,Generate multiple independent approaches then compare for consistency - crucial for high-stakes decisions where verification matters,approaches → comparison → consensus
|
||||||
5,collaboration,Time Traveler Council,Past-you and future-you advise present-you on decisions - powerful for gaining perspective on long-term consequences vs short-term pressures,past wisdom → present choice → future impact
|
5,advanced,Meta-Prompting Analysis,Step back to analyze the approach structure and methodology itself - valuable for optimizing prompts and improving problem-solving,current → analysis → optimization
|
||||||
6,collaboration,Cross-Functional War Room,Product manager + engineer + designer tackle a problem together - reveals trade-offs between feasibility desirability and viability,constraints → trade-offs → balanced solution
|
6,advanced,Reasoning via Planning,Build a reasoning tree guided by world models and goal states - excellent for strategic planning and sequential decision-making,model → planning → strategy
|
||||||
7,collaboration,Mentor and Apprentice,Senior expert teaches junior while junior asks naive questions - surfaces hidden assumptions through teaching,explanation → questions → deeper understanding
|
7,advanced,Chain-of-Thought Scaffolding,Force explicit intermediate reasoning steps before any conclusion — prevents intuitive leaps that skip flawed logic,premise → step → step → conclusion
|
||||||
8,collaboration,Good Cop Bad Cop,Supportive persona and critical persona alternate - finds both strengths to build on and weaknesses to address,encouragement → criticism → balanced view
|
8,advanced,Few-Shot Exemplar Priming,Provide 2-3 worked examples of the desired reasoning pattern before the real task — aligns output format and depth through demonstration,examples → pattern recognition → application
|
||||||
9,collaboration,Improv Yes-And,Multiple personas build on each other's ideas without blocking - generates unexpected creative directions through collaborative building,idea → build → build → surprising result
|
9,collaboration,Stakeholder Round Table,Convene multiple personas to contribute diverse perspectives - essential for requirements gathering and finding balanced solutions across competing interests,perspectives → synthesis → alignment
|
||||||
10,collaboration,Customer Support Theater,Angry customer and support rep roleplay to find pain points - reveals real user frustrations and service gaps,complaint → investigation → resolution → prevention
|
10,collaboration,Expert Panel Review,Assemble domain experts for deep specialized analysis - ideal when technical depth and peer review quality are needed,expert views → consensus → recommendations
|
||||||
11,advanced,Tree of Thoughts,Explore multiple reasoning paths simultaneously then evaluate and select the best - perfect for complex problems with multiple valid approaches,paths → evaluation → selection
|
11,collaboration,Debate Club Showdown,Two personas argue opposing positions while a moderator scores points - great for exploring controversial decisions and finding middle ground,thesis → antithesis → synthesis
|
||||||
12,advanced,Graph of Thoughts,Model reasoning as an interconnected network of ideas to reveal hidden relationships - ideal for systems thinking and discovering emergent patterns,nodes → connections → patterns
|
12,collaboration,User Persona Focus Group,Gather your product's user personas to react to proposals and share frustrations - essential for validating features and discovering unmet needs,reactions → concerns → priorities
|
||||||
13,advanced,Thread of Thought,Maintain coherent reasoning across long contexts by weaving a continuous narrative thread - essential for RAG systems and maintaining consistency,context → thread → synthesis
|
13,collaboration,Time Traveler Council,Past-you and future-you advise present-you on decisions - powerful for gaining perspective on long-term consequences vs short-term pressures,past wisdom → present choice → future impact
|
||||||
14,advanced,Self-Consistency Validation,Generate multiple independent approaches then compare for consistency - crucial for high-stakes decisions where verification matters,approaches → comparison → consensus
|
14,collaboration,Cross-Functional War Room,Product manager + engineer + designer tackle a problem together - reveals trade-offs between feasibility desirability and viability,constraints → trade-offs → balanced solution
|
||||||
15,advanced,Meta-Prompting Analysis,Step back to analyze the approach structure and methodology itself - valuable for optimizing prompts and improving problem-solving,current → analysis → optimization
|
15,collaboration,Mentor and Apprentice,Senior expert teaches junior while junior asks naive questions - surfaces hidden assumptions through teaching,explanation → questions → deeper understanding
|
||||||
16,advanced,Reasoning via Planning,Build a reasoning tree guided by world models and goal states - excellent for strategic planning and sequential decision-making,model → planning → strategy
|
16,collaboration,Good Cop Bad Cop,Supportive persona and critical persona alternate - finds both strengths to build on and weaknesses to address,encouragement → criticism → balanced view
|
||||||
17,competitive,Red Team vs Blue Team,Adversarial attack-defend analysis to find vulnerabilities - critical for security testing and building robust solutions,defense → attack → hardening
|
17,collaboration,Improv Yes-And,Multiple personas build on each other's ideas without blocking - generates unexpected creative directions through collaborative building,idea → build → build → surprising result
|
||||||
18,competitive,Shark Tank Pitch,Entrepreneur pitches to skeptical investors who poke holes - stress-tests business viability and forces clarity on value proposition,pitch → challenges → refinement
|
18,collaboration,Customer Support Theater,Angry customer and support rep roleplay to find pain points - reveals real user frustrations and service gaps,complaint → investigation → resolution → prevention
|
||||||
19,competitive,Code Review Gauntlet,Senior devs with different philosophies review the same code - surfaces style debates and finds consensus on best practices,reviews → debates → standards
|
19,collaboration,Six Thinking Hats,Rotate through six modes (facts - feelings - caution - optimism - creativity - process) to ensure a group covers every angle without crosstalk,white → red → black → yellow → green → blue
|
||||||
20,technical,Architecture Decision Records,Multiple architect personas propose and debate architectural choices with explicit trade-offs - ensures decisions are well-reasoned and documented,options → trade-offs → decision → rationale
|
20,collaboration,Delphi Method,Experts give independent estimates - see anonymized results - then revise — converges on calibrated group judgment while avoiding anchoring bias,independent estimates → reveal → revise → converge
|
||||||
21,technical,Rubber Duck Debugging Evolved,Explain your code to progressively more technical ducks until you find the bug - forces clarity at multiple abstraction levels,simple → detailed → technical → aha
|
21,competitive,Red Team vs Blue Team,Adversarial attack-defend analysis to find vulnerabilities - critical for security testing and building robust solutions,defense → attack → hardening
|
||||||
22,technical,Algorithm Olympics,Multiple approaches compete on the same problem with benchmarks - finds optimal solution through direct comparison,implementations → benchmarks → winner
|
22,competitive,Shark Tank Pitch,Entrepreneur pitches to skeptical investors who poke holes - stress-tests business viability and forces clarity on value proposition,pitch → challenges → refinement
|
||||||
23,technical,Security Audit Personas,Hacker + defender + auditor examine system from different threat models - comprehensive security review from multiple angles,vulnerabilities → defenses → compliance
|
23,competitive,Code Review Gauntlet,Senior devs with different philosophies review the same code - surfaces style debates and finds consensus on best practices,reviews → debates → standards
|
||||||
24,technical,Performance Profiler Panel,Database expert + frontend specialist + DevOps engineer diagnose slowness - finds bottlenecks across the full stack,symptoms → analysis → optimizations
|
24,core,First Principles Analysis,Strip away assumptions to rebuild from fundamental truths - breakthrough technique for innovation and solving impossible problems,assumptions → truths → new approach
|
||||||
25,creative,SCAMPER Method,Apply seven creativity lenses (Substitute/Combine/Adapt/Modify/Put/Eliminate/Reverse) - systematic ideation for product innovation,S→C→A→M→P→E→R
|
25,core,5 Whys Deep Dive,Repeatedly ask why to drill down to root causes - simple but powerful for understanding failures,why chain → root cause → solution
|
||||||
26,creative,Reverse Engineering,Work backwards from desired outcome to find implementation path - powerful for goal achievement and understanding endpoints,end state → steps backward → path forward
|
26,core,Socratic Questioning,Use targeted questions to reveal hidden assumptions and guide discovery - excellent for teaching and self-discovery,questions → revelations → understanding
|
||||||
27,creative,What If Scenarios,Explore alternative realities to understand possibilities and implications - valuable for contingency planning and exploration,scenarios → implications → insights
|
27,core,Critique and Refine,Systematic review to identify strengths and weaknesses then improve - standard quality check for drafts,strengths/weaknesses → improvements → refined
|
||||||
28,creative,Random Input Stimulus,Inject unrelated concepts to spark unexpected connections - breaks creative blocks through forced lateral thinking,random word → associations → novel ideas
|
28,core,Explain Reasoning,Walk through step-by-step thinking to show how conclusions were reached - crucial for transparency,steps → logic → conclusion
|
||||||
29,creative,Exquisite Corpse Brainstorm,Each persona adds to the idea seeing only the previous contribution - generates surprising combinations through constrained collaboration,contribution → handoff → contribution → surprise
|
29,core,Expand or Contract for Audience,Dynamically adjust detail level and technical depth for target audience - matches content to reader capabilities,audience → adjustments → refined content
|
||||||
30,creative,Genre Mashup,Combine two unrelated domains to find fresh approaches - innovation through unexpected cross-pollination,domain A + domain B → hybrid insights
|
30,core,Second-Order Thinking,Think beyond immediate consequences to anticipate cascading effects and long-term implications - essential for strategic decisions where first-order solutions create hidden downstream problems,action → consequences → second-order effects → informed choice
|
||||||
31,research,Literature Review Personas,Optimist researcher + skeptic researcher + synthesizer review sources - balanced assessment of evidence quality,sources → critiques → synthesis
|
31,core,Inversion Analysis,Flip the problem by asking what would guarantee failure instead of how to succeed - reveals hidden obstacles and blind spots by approaching challenges from the opposite direction,goal → invert → failure paths → avoidance → solution
|
||||||
32,research,Thesis Defense Simulation,Student defends hypothesis against committee with different concerns - stress-tests research methodology and conclusions,thesis → challenges → defense → refinements
|
32,core,Problem Decomposition,Break a complex problem into independent sub-problems - solve each - then reassemble — essential when a task is too large or tangled to tackle whole,whole → parts → solutions → reassembly
|
||||||
33,research,Comparative Analysis Matrix,Multiple analysts evaluate options against weighted criteria - structured decision-making with explicit scoring,options → criteria → scores → recommendation
|
33,core,Analogy Mapping,Find a well-understood parallel domain and transfer its structure to the current problem — unlocks insight by borrowing proven mental models,source domain → mapping → target insight
|
||||||
34,risk,Pre-mortem Analysis,Imagine future failure then work backwards to prevent it - powerful technique for risk mitigation before major launches,failure scenario → causes → prevention
|
34,core,Steelmanning,Construct the strongest possible version of an opposing argument before responding — builds credibility and catches blind spots that strawmanning misses,opposing view → strongest form → honest rebuttal
|
||||||
35,risk,Failure Mode Analysis,Systematically explore how each component could fail - critical for reliability engineering and safety-critical systems,components → failures → prevention
|
35,creative,SCAMPER Method,Apply seven creativity lenses (Substitute/Combine/Adapt/Modify/Put/Eliminate/Reverse) - systematic ideation for product innovation,S→C→A→M→P→E→R
|
||||||
36,risk,Challenge from Critical Perspective,Play devil's advocate to stress-test ideas and find weaknesses - essential for overcoming groupthink,assumptions → challenges → strengthening
|
36,creative,Reverse Engineering,Work backwards from desired outcome to find implementation path - powerful for goal achievement and understanding endpoints,end state → steps backward → path forward
|
||||||
37,risk,Identify Potential Risks,Brainstorm what could go wrong across all categories - fundamental for project planning and deployment preparation,categories → risks → mitigations
|
37,creative,What If Scenarios,Explore alternative realities to understand possibilities and implications - valuable for contingency planning and exploration,scenarios → implications → insights
|
||||||
38,risk,Chaos Monkey Scenarios,Deliberately break things to test resilience and recovery - ensures systems handle failures gracefully,break → observe → harden
|
38,creative,Random Input Stimulus,Inject unrelated concepts to spark unexpected connections - breaks creative blocks through forced lateral thinking,random word → associations → novel ideas
|
||||||
39,core,First Principles Analysis,Strip away assumptions to rebuild from fundamental truths - breakthrough technique for innovation and solving impossible problems,assumptions → truths → new approach
|
39,creative,Exquisite Corpse Brainstorm,Each persona adds to the idea seeing only the previous contribution - generates surprising combinations through constrained collaboration,contribution → handoff → contribution → surprise
|
||||||
40,core,5 Whys Deep Dive,Repeatedly ask why to drill down to root causes - simple but powerful for understanding failures,why chain → root cause → solution
|
40,creative,Genre Mashup,Combine two unrelated domains to find fresh approaches - innovation through unexpected cross-pollination,domain A + domain B → hybrid insights
|
||||||
41,core,Socratic Questioning,Use targeted questions to reveal hidden assumptions and guide discovery - excellent for teaching and self-discovery,questions → revelations → understanding
|
41,creative,Constraint Injection,Deliberately add an artificial limitation (budget - time - technology) to force novel solutions — creativity thrives under pressure,add constraint → forced creativity → remove constraint → evaluate
|
||||||
42,core,Critique and Refine,Systematic review to identify strengths and weaknesses then improve - standard quality check for drafts,strengths/weaknesses → improvements → refined
|
42,creative,Morphological Analysis,List independent parameters of a problem - enumerate options for each - then systematically combine — ensures you don't miss non-obvious configurations,parameters → options grid → combinations → evaluation
|
||||||
43,core,Explain Reasoning,Walk through step-by-step thinking to show how conclusions were reached - crucial for transparency,steps → logic → conclusion
|
43,framing,Abstraction Laddering,"Move up (""why?"") for strategic clarity or down (""how?"") for tactical detail — ensures you're solving at the right altitude",concrete ↔ abstract → right level
|
||||||
44,core,Expand or Contract for Audience,Dynamically adjust detail level and technical depth for target audience - matches content to reader capabilities,audience → adjustments → refined content
|
44,framing,Reframe the Question,Challenge whether the stated problem is the real problem — often the question itself is wrong and a better framing unlocks an easy answer,stated problem → reframe → true problem → solution
|
||||||
45,learning,Feynman Technique,Explain complex concepts simply as if teaching a child - the ultimate test of true understanding,complex → simple → gaps → mastery
|
45,framing,Stakeholder Lens Rotation,Serially adopt each stakeholder's world-view to see the same situation differently — reveals whose needs are being overlooked,perspective A → B → C → gaps found
|
||||||
46,learning,Active Recall Testing,Test understanding without references to verify true knowledge - essential for identifying gaps,test → gaps → reinforcement
|
46,learning,Feynman Technique,Explain complex concepts simply as if teaching a child - the ultimate test of true understanding,complex → simple → gaps → mastery
|
||||||
47,philosophical,Occam's Razor Application,Find the simplest sufficient explanation by eliminating unnecessary complexity - essential for debugging,options → simplification → selection
|
47,learning,Active Recall Testing,Test understanding without references to verify true knowledge - essential for identifying gaps,test → gaps → reinforcement
|
||||||
48,philosophical,Trolley Problem Variations,Explore ethical trade-offs through moral dilemmas - valuable for understanding values and difficult decisions,dilemma → analysis → decision
|
48,learning,Deliberate Practice Loop,Identify a specific sub-skill - drill it with immediate feedback - adjust - repeat — targeted improvement beats general repetition,isolate → drill → feedback → adjust → repeat
|
||||||
49,retrospective,Hindsight Reflection,Imagine looking back from the future to gain perspective - powerful for project reviews,future view → insights → application
|
49,philosophical,Occam's Razor Application,Find the simplest sufficient explanation by eliminating unnecessary complexity - essential for debugging,options → simplification → selection
|
||||||
50,retrospective,Lessons Learned Extraction,Systematically identify key takeaways and actionable improvements - essential for continuous improvement,experience → lessons → actions
|
50,philosophical,Trolley Problem Variations,Explore ethical trade-offs through moral dilemmas - valuable for understanding values and difficult decisions,dilemma → analysis → decision
|
||||||
|
51,research,Literature Review Personas,Optimist researcher + skeptic researcher + synthesizer review sources - balanced assessment of evidence quality,sources → critiques → synthesis
|
||||||
|
52,research,Thesis Defense Simulation,Student defends hypothesis against committee with different concerns - stress-tests research methodology and conclusions,thesis → challenges → defense → refinements
|
||||||
|
53,research,Comparative Analysis Matrix,Multiple analysts evaluate options against weighted criteria - structured decision-making with explicit scoring,options → criteria → scores → recommendation
|
||||||
|
54,research,Source Triangulation,Require at least three independent source types (quantitative - qualitative - expert) before accepting a claim — guards against single-source bias,claim → source A → source B → source C → confidence rating
|
||||||
|
55,retrospective,Hindsight Reflection,Imagine looking back from the future to gain perspective - powerful for project reviews,future view → insights → application
|
||||||
|
56,retrospective,Lessons Learned Extraction,Systematically identify key takeaways and actionable improvements - essential for continuous improvement,experience → lessons → actions
|
||||||
|
57,risk,Pre-mortem Analysis,Imagine future failure then work backwards to prevent it - powerful technique for risk mitigation before major launches,failure scenario → causes → prevention
|
||||||
|
58,risk,Failure Mode Analysis,Systematically explore how each component could fail - critical for reliability engineering and safety-critical systems,components → failures → prevention
|
||||||
|
59,risk,Challenge from Critical Perspective,Play devil's advocate to stress-test ideas and find weaknesses - essential for overcoming groupthink,assumptions → challenges → strengthening
|
||||||
|
60,risk,Identify Potential Risks,Brainstorm what could go wrong across all categories - fundamental for project planning and deployment preparation,categories → risks → mitigations
|
||||||
|
61,risk,Chaos Monkey Scenarios,Deliberately break things to test resilience and recovery - ensures systems handle failures gracefully,break → observe → harden
|
||||||
|
62,risk,Assumption Audit,Explicitly list every assumption underlying a plan - rate each by confidence and impact - then stress-test the weakest — prevents building on shaky foundations,list → rate → stress-test → shore up
|
||||||
|
63,risk,Cascading Failure Simulation,Trace how one component's failure propagates through dependencies — reveals hidden coupling and single points of failure,trigger failure → trace propagation → find amplifiers → decouple
|
||||||
|
64,technical,Architecture Decision Records,Multiple architect personas propose and debate architectural choices with explicit trade-offs - ensures decisions are well-reasoned and documented,options → trade-offs → decision → rationale
|
||||||
|
65,technical,Rubber Duck Debugging Evolved,Explain your code to progressively more technical ducks until you find the bug - forces clarity at multiple abstraction levels,simple → detailed → technical → aha
|
||||||
|
66,technical,Algorithm Olympics,Multiple approaches compete on the same problem with benchmarks - finds optimal solution through direct comparison,implementations → benchmarks → winner
|
||||||
|
67,technical,Security Audit Personas,Hacker + defender + auditor examine system from different threat models - comprehensive security review from multiple angles,vulnerabilities → defenses → compliance
|
||||||
|
68,technical,Performance Profiler Panel,Database expert + frontend specialist + DevOps engineer diagnose slowness - finds bottlenecks across the full stack,symptoms → analysis → optimizations
|
||||||
|
69,technical,Boundary & Edge Case Sweep,Systematically test extremes - zeros - nulls - maximums - and type mismatches — catches the failures that happy-path thinking always misses,inputs → boundaries → edge cases → failures found
|
||||||
|
|
|
||||||
|
|
|
@ -14,7 +14,9 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const { Installer } = require('../tools/installer/core/installer');
|
||||||
const { ManifestGenerator } = require('../tools/installer/core/manifest-generator');
|
const { ManifestGenerator } = require('../tools/installer/core/manifest-generator');
|
||||||
|
const { OfficialModules } = require('../tools/installer/modules/official-modules');
|
||||||
const { IdeManager } = require('../tools/installer/ide/manager');
|
const { IdeManager } = require('../tools/installer/ide/manager');
|
||||||
const { clearCache, loadPlatformCodes } = require('../tools/installer/ide/platform-codes');
|
const { clearCache, loadPlatformCodes } = require('../tools/installer/ide/platform-codes');
|
||||||
|
|
||||||
|
|
@ -126,6 +128,56 @@ async function createSkillCollisionFixture() {
|
||||||
return { root: fixtureRoot, bmadDir: fixtureDir };
|
return { root: fixtureRoot, bmadDir: fixtureDir };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createCustomModuleManifestFixture() {
|
||||||
|
const fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-custom-manifest-'));
|
||||||
|
const bmadDir = path.join(fixtureRoot, '_bmad');
|
||||||
|
const configDir = path.join(bmadDir, '_config');
|
||||||
|
const moduleSourceDir = path.join(fixtureRoot, 'test-module-source');
|
||||||
|
await fs.ensureDir(configDir);
|
||||||
|
await fs.ensureDir(moduleSourceDir);
|
||||||
|
|
||||||
|
const minimalAgent = '<agent name="Test" title="T"><persona>p</persona></agent>';
|
||||||
|
await fs.ensureDir(path.join(bmadDir, 'core', 'agents'));
|
||||||
|
await fs.writeFile(path.join(bmadDir, 'core', 'agents', 'test.md'), minimalAgent);
|
||||||
|
await fs.ensureDir(path.join(bmadDir, 'test-module', 'agents'));
|
||||||
|
await fs.writeFile(path.join(bmadDir, 'test-module', 'agents', 'test.md'), minimalAgent);
|
||||||
|
await fs.writeFile(path.join(moduleSourceDir, 'module.yaml'), ['code: test-module', 'name: Test Module', ''].join('\n'));
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, 'manifest.yaml'),
|
||||||
|
[
|
||||||
|
'installation:',
|
||||||
|
' version: 6.2.2',
|
||||||
|
' installDate: 2026-03-30T00:00:00.000Z',
|
||||||
|
' lastUpdated: 2026-03-30T00:00:00.000Z',
|
||||||
|
'modules:',
|
||||||
|
' - name: core',
|
||||||
|
' version: 6.2.2',
|
||||||
|
' installDate: 2026-03-30T00:00:00.000Z',
|
||||||
|
' lastUpdated: 2026-03-30T00:00:00.000Z',
|
||||||
|
' source: built-in',
|
||||||
|
' npmPackage: null',
|
||||||
|
' repoUrl: null',
|
||||||
|
' - name: test-module',
|
||||||
|
' version: null',
|
||||||
|
' installDate: 2026-03-30T00:00:00.000Z',
|
||||||
|
' lastUpdated: 2026-03-30T00:00:00.000Z',
|
||||||
|
' source: custom',
|
||||||
|
' npmPackage: null',
|
||||||
|
' repoUrl: null',
|
||||||
|
'customModules:',
|
||||||
|
' - id: test-module',
|
||||||
|
' name: "Test Module"',
|
||||||
|
` sourcePath: ${JSON.stringify(moduleSourceDir)}`,
|
||||||
|
'ides:',
|
||||||
|
' - codex',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { root: fixtureRoot, bmadDir, manifestPath: path.join(configDir, 'manifest.yaml'), moduleSourceDir };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Suite
|
* Test Suite
|
||||||
*/
|
*/
|
||||||
|
|
@ -1713,6 +1765,107 @@ async function runTests() {
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Suite 33: Main manifest preserves active customModules only
|
||||||
|
// ============================================================
|
||||||
|
console.log(`${colors.yellow}Test Suite 33: Preserve active customModules in main manifest${colors.reset}\n`);
|
||||||
|
|
||||||
|
let customManifestFixture = null;
|
||||||
|
try {
|
||||||
|
customManifestFixture = await createCustomModuleManifestFixture();
|
||||||
|
const yaml = require('yaml');
|
||||||
|
const originalManifest = yaml.parse(await fs.readFile(customManifestFixture.manifestPath, 'utf8'));
|
||||||
|
originalManifest.customModules.push({
|
||||||
|
id: 'removed-module',
|
||||||
|
name: 'Removed Module',
|
||||||
|
sourcePath: path.join(customManifestFixture.root, 'removed-module-source'),
|
||||||
|
});
|
||||||
|
await fs.writeFile(customManifestFixture.manifestPath, yaml.stringify(originalManifest), 'utf8');
|
||||||
|
|
||||||
|
const generator33 = new ManifestGenerator();
|
||||||
|
await generator33.generateManifests(customManifestFixture.bmadDir, ['core', 'test-module'], [], { ides: ['codex'] });
|
||||||
|
|
||||||
|
const updatedManifest = yaml.parse(await fs.readFile(customManifestFixture.manifestPath, 'utf8'));
|
||||||
|
const customModule = updatedManifest.customModules?.find((entry) => entry.id === 'test-module');
|
||||||
|
|
||||||
|
assert(Array.isArray(updatedManifest.customModules), 'Main manifest keeps customModules array');
|
||||||
|
assert(customModule !== undefined, 'Main manifest preserves existing custom module entry');
|
||||||
|
assert(
|
||||||
|
customModule && customModule.sourcePath === customManifestFixture.moduleSourceDir,
|
||||||
|
'Main manifest preserves custom module sourcePath',
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!updatedManifest.customModules?.some((entry) => entry.id === 'removed-module'),
|
||||||
|
'Main manifest drops stale custom module entries',
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
assert(false, 'Main manifest preserves customModules test succeeds', error.message);
|
||||||
|
} finally {
|
||||||
|
if (customManifestFixture?.root) await fs.remove(customManifestFixture.root).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Suite 34: Quick update uses manifest-backed custom sources
|
||||||
|
// ============================================================
|
||||||
|
console.log(`${colors.yellow}Test Suite 34: Quick update uses manifest-backed custom module sources${colors.reset}\n`);
|
||||||
|
|
||||||
|
let quickUpdateFixture = null;
|
||||||
|
const originalListAvailable34 = OfficialModules.prototype.listAvailable;
|
||||||
|
const originalLoadExistingConfig34 = OfficialModules.prototype.loadExistingConfig;
|
||||||
|
const originalCollectModuleConfigQuick34 = OfficialModules.prototype.collectModuleConfigQuick;
|
||||||
|
try {
|
||||||
|
quickUpdateFixture = await createCustomModuleManifestFixture();
|
||||||
|
const installer34 = new Installer();
|
||||||
|
installer34.externalModuleManager.hasModule = async () => false;
|
||||||
|
installer34.externalModuleManager.listAvailable = async () => [];
|
||||||
|
|
||||||
|
let capturedInstallConfig34 = null;
|
||||||
|
installer34.install = async (config) => {
|
||||||
|
capturedInstallConfig34 = config;
|
||||||
|
return { success: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
OfficialModules.prototype.listAvailable = async function () {
|
||||||
|
return { modules: [], customModules: [] };
|
||||||
|
};
|
||||||
|
OfficialModules.prototype.loadExistingConfig = async function () {
|
||||||
|
this.collectedConfig = this.collectedConfig || {};
|
||||||
|
};
|
||||||
|
OfficialModules.prototype.collectModuleConfigQuick = async function (moduleName) {
|
||||||
|
this.collectedConfig = this.collectedConfig || {};
|
||||||
|
if (!this.collectedConfig[moduleName]) {
|
||||||
|
this.collectedConfig[moduleName] = {};
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
await installer34.quickUpdate({
|
||||||
|
directory: quickUpdateFixture.root,
|
||||||
|
skipPrompts: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const customModule34 = capturedInstallConfig34?._customModuleSources?.get('test-module');
|
||||||
|
|
||||||
|
assert(capturedInstallConfig34 !== null, 'Quick update forwards config to install');
|
||||||
|
assert(customModule34 !== undefined, 'Quick update keeps manifest-backed custom module updateable');
|
||||||
|
assert(customModule34 && customModule34.cached === false, 'Quick update uses manifest-backed source before cache');
|
||||||
|
assert(
|
||||||
|
customModule34 && customModule34.sourcePath === quickUpdateFixture.moduleSourceDir,
|
||||||
|
'Quick update uses preserved manifest sourcePath for custom modules',
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
assert(false, 'Quick update manifest-backed custom source test succeeds', error.message);
|
||||||
|
} finally {
|
||||||
|
OfficialModules.prototype.listAvailable = originalListAvailable34;
|
||||||
|
OfficialModules.prototype.loadExistingConfig = originalLoadExistingConfig34;
|
||||||
|
OfficialModules.prototype.collectModuleConfigQuick = originalCollectModuleConfigQuick34;
|
||||||
|
if (quickUpdateFixture?.root) await fs.remove(quickUpdateFixture.root).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Summary
|
// Summary
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -1144,59 +1144,12 @@ class Installer {
|
||||||
const configuredIdes = existingInstall.ides;
|
const configuredIdes = existingInstall.ides;
|
||||||
const projectRoot = path.dirname(bmadDir);
|
const projectRoot = path.dirname(bmadDir);
|
||||||
|
|
||||||
// Get custom module sources: first from --custom-content (re-cache from source), then from cache
|
const customModuleSources = await this.customModules.assembleQuickUpdateSources(
|
||||||
const customModuleSources = new Map();
|
config,
|
||||||
if (config.customContent?.sources?.length > 0) {
|
existingInstall,
|
||||||
for (const source of config.customContent.sources) {
|
bmadDir,
|
||||||
if (source.id && source.path && (await fs.pathExists(source.path))) {
|
this.externalModuleManager,
|
||||||
customModuleSources.set(source.id, {
|
);
|
||||||
id: source.id,
|
|
||||||
name: source.name || source.id,
|
|
||||||
sourcePath: source.path,
|
|
||||||
cached: false, // From CLI, will be re-cached
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
||||||
if (await fs.pathExists(cacheDir)) {
|
|
||||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
||||||
|
|
||||||
for (const cachedModule of cachedModules) {
|
|
||||||
const moduleId = cachedModule.name;
|
|
||||||
const cachedPath = path.join(cacheDir, moduleId);
|
|
||||||
|
|
||||||
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
|
||||||
if (!(await fs.pathExists(cachedPath))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!cachedModule.isDirectory()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if we already have this module from manifest
|
|
||||||
if (customModuleSources.has(moduleId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is an external official module - skip cache for those
|
|
||||||
const isExternal = await this.externalModuleManager.hasModule(moduleId);
|
|
||||||
if (isExternal) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is actually a custom module (has module.yaml)
|
|
||||||
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
||||||
if (await fs.pathExists(moduleYamlPath)) {
|
|
||||||
customModuleSources.set(moduleId, {
|
|
||||||
id: moduleId,
|
|
||||||
name: moduleId,
|
|
||||||
sourcePath: cachedPath,
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get available modules (what we have source for)
|
// Get available modules (what we have source for)
|
||||||
const availableModulesData = await new OfficialModules().listAvailable();
|
const availableModulesData = await new OfficialModules().listAvailable();
|
||||||
|
|
|
||||||
|
|
@ -377,10 +377,12 @@ class ManifestGenerator {
|
||||||
*/
|
*/
|
||||||
async writeMainManifest(cfgDir) {
|
async writeMainManifest(cfgDir) {
|
||||||
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
||||||
|
const installedModuleSet = new Set(this.modules);
|
||||||
|
|
||||||
// Read existing manifest to preserve install date
|
// Read existing manifest to preserve install date
|
||||||
let existingInstallDate = null;
|
let existingInstallDate = null;
|
||||||
const existingModulesMap = new Map();
|
const existingModulesMap = new Map();
|
||||||
|
let existingCustomModules = [];
|
||||||
|
|
||||||
if (await fs.pathExists(manifestPath)) {
|
if (await fs.pathExists(manifestPath)) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -402,6 +404,12 @@ class ManifestGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existingManifest.customModules && Array.isArray(existingManifest.customModules)) {
|
||||||
|
// We filter here so manifest regeneration preserves source metadata only for custom modules that
|
||||||
|
// are still installed. Without that, customModules can retain stale entries for modules that were removed.
|
||||||
|
existingCustomModules = existingManifest.customModules.filter((customModule) => installedModuleSet.has(customModule?.id));
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// If we can't read existing manifest, continue with defaults
|
// If we can't read existing manifest, continue with defaults
|
||||||
}
|
}
|
||||||
|
|
@ -437,6 +445,7 @@ class ManifestGenerator {
|
||||||
lastUpdated: new Date().toISOString(),
|
lastUpdated: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
modules: updatedModules,
|
modules: updatedModules,
|
||||||
|
customModules: existingCustomModules,
|
||||||
ides: this.selectedIdes,
|
ides: this.selectedIdes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,111 @@ class CustomModules {
|
||||||
|
|
||||||
return this.paths;
|
return this.paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble quick-update source candidates before install() hands them to discoverPaths().
|
||||||
|
* This exists because discoverPaths() consumes already-prepared quick-update sources,
|
||||||
|
* while quickUpdate() still has to build that source map from manifest, explicit inputs,
|
||||||
|
* and cache conventions.
|
||||||
|
* Precedence: manifest-backed paths, explicit sources override them, then cached modules.
|
||||||
|
* @param {Object} config - Quick update configuration
|
||||||
|
* @param {Object} existingInstall - Existing installation snapshot
|
||||||
|
* @param {string} bmadDir - BMAD directory
|
||||||
|
* @param {Object} externalModuleManager - External module manager
|
||||||
|
* @returns {Promise<Map<string, Object>>} Map of custom module ID to source info
|
||||||
|
*/
|
||||||
|
async assembleQuickUpdateSources(config, existingInstall, bmadDir, externalModuleManager) {
|
||||||
|
const projectRoot = path.dirname(bmadDir);
|
||||||
|
const customModuleSources = new Map();
|
||||||
|
|
||||||
|
if (existingInstall.customModules) {
|
||||||
|
for (const customModule of existingInstall.customModules) {
|
||||||
|
// Skip if no ID - can't reliably track or re-cache without it
|
||||||
|
if (!customModule?.id) continue;
|
||||||
|
|
||||||
|
let sourcePath = customModule.sourcePath;
|
||||||
|
if (sourcePath && sourcePath.startsWith('_config')) {
|
||||||
|
// Paths are relative to BMAD dir, but we want absolute paths for install
|
||||||
|
sourcePath = path.join(bmadDir, sourcePath);
|
||||||
|
} else if (!sourcePath && customModule.relativePath) {
|
||||||
|
// Fall back to relativePath
|
||||||
|
sourcePath = path.resolve(projectRoot, customModule.relativePath);
|
||||||
|
} else if (sourcePath && !path.isAbsolute(sourcePath)) {
|
||||||
|
// If we have a sourcePath but it's not absolute, resolve it relative to project root
|
||||||
|
sourcePath = path.resolve(projectRoot, sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still don't have a valid source path, skip this module
|
||||||
|
if (!sourcePath || !(await fs.pathExists(sourcePath))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
customModuleSources.set(customModule.id, {
|
||||||
|
id: customModule.id,
|
||||||
|
name: customModule.name || customModule.id,
|
||||||
|
sourcePath,
|
||||||
|
relativePath: customModule.relativePath,
|
||||||
|
cached: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.customContent?.sources?.length > 0) {
|
||||||
|
for (const source of config.customContent.sources) {
|
||||||
|
if (source.id && source.path) {
|
||||||
|
customModuleSources.set(source.id, {
|
||||||
|
id: source.id,
|
||||||
|
name: source.name || source.id,
|
||||||
|
sourcePath: source.path,
|
||||||
|
cached: false, // From CLI, will be re-cached
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
||||||
|
if (!(await fs.pathExists(cacheDir))) {
|
||||||
|
return customModuleSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
||||||
|
for (const cachedModule of cachedModules) {
|
||||||
|
const moduleId = cachedModule.name;
|
||||||
|
const cachedPath = path.join(cacheDir, moduleId);
|
||||||
|
|
||||||
|
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
||||||
|
if (!(await fs.pathExists(cachedPath))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!cachedModule.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if we already have this module from manifest
|
||||||
|
if (customModuleSources.has(moduleId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is an external official module - skip cache for those
|
||||||
|
const isExternal = await externalModuleManager.hasModule(moduleId);
|
||||||
|
if (isExternal) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is actually a custom module (has module.yaml)
|
||||||
|
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
||||||
|
if (await fs.pathExists(moduleYamlPath)) {
|
||||||
|
customModuleSources.set(moduleId, {
|
||||||
|
id: moduleId,
|
||||||
|
name: moduleId,
|
||||||
|
sourcePath: cachedPath,
|
||||||
|
cached: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return customModuleSources;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { CustomModules };
|
module.exports = { CustomModules };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue