bmad-plus 0.13.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +113 -487
  3. package/SECURITY.md +71 -0
  4. package/THIRD-PARTY-LICENSES.md +349 -0
  5. package/osint-agent-package/README.md +1 -1
  6. package/package.json +14 -3
  7. package/readme-international/README.de.md +18 -8
  8. package/readme-international/README.es.md +19 -9
  9. package/readme-international/README.fr.md +18 -8
  10. package/src/bmad-plus/agents/agent-architect-dev/SKILL.md +11 -13
  11. package/src/bmad-plus/agents/agent-orchestrator/SKILL.md +148 -9
  12. package/src/bmad-plus/agents/agent-quality/SKILL.md +41 -11
  13. package/src/bmad-plus/data/role-triggers.yaml +19 -0
  14. package/src/bmad-plus/module-help.csv +1 -0
  15. package/src/bmad-plus/module.yaml +1 -0
  16. package/src/bmad-plus/packs/pack-dev-studio/README.md +133 -141
  17. package/src/bmad-plus/packs/pack-dev-studio/SKILL.md +49 -0
  18. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/analyst-agent.md +35 -60
  19. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/document-project.md +59 -59
  20. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/domain-research.md +55 -93
  21. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/market-research.md +58 -93
  22. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/prfaq.md +55 -132
  23. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/product-brief.md +63 -78
  24. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/tech-writer-agent.md +54 -69
  25. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/technical-research.md +54 -93
  26. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/architect-agent.md +32 -60
  27. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-architecture.md +67 -71
  28. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-epics-stories.md +61 -90
  29. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/generate-project-context.md +56 -78
  30. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/implementation-readiness.md +55 -88
  31. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01-init.md +20 -153
  32. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01b-continue.md +20 -173
  33. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-02-context.md +14 -220
  34. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-03-starter.md +20 -329
  35. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-04-decisions.md +15 -314
  36. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-05-patterns.md +15 -355
  37. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-06-structure.md +15 -375
  38. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-07-validation.md +14 -357
  39. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-08-complete.md +13 -78
  40. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/checkpoint-preview.md +52 -65
  41. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-01-gather-context.md +14 -81
  42. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-02-review.md +14 -31
  43. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-03-triage.md +14 -45
  44. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-04-present.md +13 -128
  45. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review.md +61 -87
  46. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/correct-course.md +55 -298
  47. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/create-story.md +54 -426
  48. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-agent.md +48 -69
  49. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story-checklist.md +24 -80
  50. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story.md +64 -482
  51. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/investigate.md +50 -184
  52. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/qa-e2e-tests.md +57 -173
  53. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/quick-dev.md +56 -108
  54. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/retrospective.md +54 -1509
  55. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-planning.md +54 -296
  56. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-status.md +41 -283
  57. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-prd.md +58 -18
  58. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-ux-design.md +103 -72
  59. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/edit-prd.md +55 -27
  60. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/pm-agent.md +34 -60
  61. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/prd.md +46 -87
  62. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/steps/step-01-init.md +10 -0
  63. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/ux-designer-agent.md +30 -60
  64. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/validate-prd.md +57 -27
  65. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/advanced-elicitation.md +47 -138
  66. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/adversarial-review.md +48 -34
  67. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/bmad-help.md +51 -68
  68. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/brainstorming.md +46 -3
  69. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/customize.md +68 -109
  70. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/distillator.md +53 -174
  71. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/edge-case-hunter.md +39 -53
  72. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-prose.md +45 -83
  73. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-structure.md +45 -176
  74. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/index-docs.md +45 -63
  75. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/party-mode.md +53 -124
  76. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/shard-doc.md +44 -100
  77. package/src/bmad-plus/packs/pack-dev-studio/dev-studio-orchestrator.md +56 -116
  78. package/src/bmad-plus/packs/pack-dev-studio/shared/architecture-decision-template.md +49 -12
  79. package/src/bmad-plus/packs/pack-dev-studio/shared/bwml-spec.md +51 -328
  80. package/src/bmad-plus/packs/pack-dev-studio/shared/catalog.json +489 -0
  81. package/src/bmad-plus/packs/pack-dev-studio/shared/execution.md +69 -0
  82. package/src/bmad-plus/packs/pack-dev-studio/shared/module-help.csv +39 -32
  83. package/src/bmad-plus/packs/pack-dev-studio/upstream-sync.yaml +85 -14
  84. package/src/bmad-plus/packs/pack-memory/README.md +35 -4
  85. package/src/bmad-plus/packs/pack-memory/memory-orchestrator.md +33 -6
  86. package/src/bmad-plus/packs/pack-memory/shared/karpathy-guardrails.md +3 -3
  87. package/src/bmad-plus/packs/pack-memory/shared/memory-protocol.md +27 -3
  88. package/src/bmad-plus/packs/pack-memory/zecher-agent.md +18 -2
  89. package/src/bmad-plus/packs/pack-seo/SKILL.md +27 -1
  90. package/src/bmad-plus/packs/pack-seo/seo-chief.md +16 -1
  91. package/src/bmad-plus/packs/pack-seo/seo-judge.md +12 -0
  92. package/src/bmad-plus/packs/pack-seo/seo-scout.md +12 -0
  93. package/src/bmad-plus/skills/bmad-plus-autopilot/SKILL.md +49 -12
  94. package/src/bmad-plus/skills/bmad-plus-parallel/SKILL.md +17 -3
  95. package/src/bmad-plus/skills/bmad-plus-sync/SKILL.md +76 -65
  96. package/src/bmad-plus/skills/bmad-plus-uat/SKILL.md +144 -0
  97. package/src/bmad-plus/skills/bmad-plus-uat/ref/uat-results.schema.json +60 -0
  98. package/src/bmad-plus/skills/bmad-plus-uat/ref/uat-spec.schema.json +121 -0
  99. package/src/bmad-plus/skills/bmad-plus-uat/ref/uat-triage.schema.json +60 -0
  100. package/src/bmad-plus/skills/bmad-plus-uat/template/page.html +552 -0
  101. package/src/bmad-plus/skills/bmad-plus-uat/template/strings.json +362 -0
  102. package/src/bmad-plus/skills/dev-studio/SKILL.md +19 -0
  103. package/tools/build/check-install-contract.js +367 -17
  104. package/tools/build/generate.js +229 -40
  105. package/tools/build/generated-adapters/.codex/AGENTS.md +1 -1
  106. package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +1 -1
  107. package/tools/build/generated-adapters/.opencode/AGENTS.md +1 -1
  108. package/tools/build/generated-adapters/AGENTS.md +1 -1
  109. package/tools/build/generated-adapters/CLAUDE.md +1 -1
  110. package/tools/build/generated-adapters/CONVENTIONS.md +1 -1
  111. package/tools/build/generated-adapters/GEMINI.md +1 -1
  112. package/tools/build/pack-delivery.js +78 -0
  113. package/tools/cli/bmad-plus-cli.js +15 -12
  114. package/tools/cli/commands/doctor.js +50 -189
  115. package/tools/cli/commands/install.js +22 -3
  116. package/tools/cli/commands/memory-journal-cmd.js +119 -19
  117. package/tools/cli/commands/nexus.js +111 -0
  118. package/tools/cli/commands/studio.js +68 -0
  119. package/tools/cli/commands/uat.js +389 -0
  120. package/tools/cli/lib/README-memory-journal.md +19 -8
  121. package/tools/cli/lib/installation-health.js +366 -0
  122. package/tools/cli/lib/memory-journal.js +0 -0
  123. package/tools/cli/lib/memory-outcomes.js +293 -0
  124. package/tools/cli/lib/memory-store.js +139 -0
  125. package/tools/cli/lib/nexus-process.js +377 -0
  126. package/tools/cli/lib/nexus.js +1532 -0
  127. package/tools/cli/lib/pack-copy.js +39 -11
  128. package/tools/cli/lib/packs.js +134 -11
  129. package/tools/cli/lib/python-health.js +233 -0
  130. package/tools/cli/lib/python-provision.js +2 -2
  131. package/tools/cli/lib/studio.js +310 -0
  132. package/tools/cli/lib/uat.js +869 -0
  133. package/tools/maintain/upstream-candidate.js +456 -0
  134. package/tools/release/publication-content.js +903 -0
  135. package/tools/release/supply-chain.js +282 -0
@@ -35,15 +35,61 @@ const MODULE_TEMPLATE_PATH = path.join(__dirname, 'module.template.yaml');
35
35
  const PACKAGE_JSON_PATH = path.join(REPO_ROOT, 'package.json');
36
36
 
37
37
  const INSTALL_LAYOUTS = ['loose', 'packaged'];
38
- const OWNED_COUNT_RE = /(?<![\w.-])\d+\+?[\s-]+(?:[A-Za-zÀ-ÿ-]+\s+){0,6}(?:agents?|skills?|workflows?|packs?|frameworks?|languages?|reference files)\b/gi;
39
- const STANDARD_PREFIX_RE = /\b(?:ISO(?:\/IEC)?|IEC|SOC|NIST(?: SP)?|PCI(?: DSS)?|WCAG|Section)\s*$/i;
38
+ const OWNED_COUNT_RE =
39
+ /(?<![\w.-])\d+\+?[\s-]+(?:[A-Za-zÀ-ÿ-]+\s+){0,6}(?:agents?|skills?|workflows?|packs?|frameworks?|languages?|reference files)\b/gi;
40
+ const STANDARD_PREFIX_RE =
41
+ /\b(?:ISO(?:\/IEC)?|IEC|SOC|NIST(?: SP)?|PCI(?: DSS)?|WCAG|Section)\s*$/i;
40
42
 
41
43
  function hasOwnedCount(value) {
42
- return [...value.matchAll(OWNED_COUNT_RE)].some((match) =>
43
- !STANDARD_PREFIX_RE.test(value.slice(0, match.index))
44
+ return [...value.matchAll(OWNED_COUNT_RE)].some(
45
+ (match) => !STANDARD_PREFIX_RE.test(value.slice(0, match.index))
44
46
  );
45
47
  }
46
48
 
49
+ function assertResourcePath(file, where) {
50
+ if (
51
+ typeof file !== 'string' ||
52
+ !file ||
53
+ /[\\:\0]/.test(file) ||
54
+ file.split('/').some((part) => !part || part === '.' || part === '..')
55
+ ) {
56
+ throw new Error(`${where}: required resource must be a portable relative file path`);
57
+ }
58
+ }
59
+
60
+ /** Installed entry points, derived from the same layout used by pack-copy.js. */
61
+ function buildRequiredResources(pack) {
62
+ const where = `registry.yaml: pack "${pack.pack_dir}"`;
63
+ for (const file of [
64
+ ...pack.agents,
65
+ ...(pack.skills || []),
66
+ ...(pack.data || []),
67
+ ...((pack.doctor || {}).pack_agents || []),
68
+ ...((pack.doctor || {}).required_resources || []),
69
+ ...(pack.python_entry ? [pack.python_entry] : []),
70
+ ]) {
71
+ assertResourcePath(file, where);
72
+ }
73
+ const resources = [];
74
+ if (pack.install_layout === 'loose') {
75
+ resources.push(...pack.agents.map((id) => `.agents/skills/${id}/SKILL.md`));
76
+ } else {
77
+ resources.push(
78
+ ...pack.doctor.pack_agents.map((file) => `.agents/skills/${pack.pack_dir}/${file}`)
79
+ );
80
+ }
81
+ resources.push(...(pack.skills || []).map((id) => `.agents/skills/${id}/SKILL.md`));
82
+ resources.push(...(pack.data || []).map((file) => `.agents/data/${file}`));
83
+ resources.push(
84
+ ...((pack.doctor || {}).required_resources || []).map(
85
+ (file) => `.agents/skills/${pack.pack_dir}/${file}`
86
+ )
87
+ );
88
+ if (pack.python_entry) resources.push(`.agents/skills/${pack.pack_dir}/${pack.python_entry}`);
89
+ for (const file of resources) assertResourcePath(file, where);
90
+ return [...new Set(resources)];
91
+ }
92
+
47
93
  /**
48
94
  * Validate the minimal contract the generator relies on.
49
95
  * Throws with a precise message on the first violation.
@@ -74,11 +120,15 @@ function validateRegistry(registry) {
74
120
  throw new Error(`${where}: missing cli.desc or cli.desc_template`);
75
121
  }
76
122
  for (const [field, value] of Object.entries({
77
- 'cli.desc': pack.cli.desc, 'cli.desc_template': pack.cli.desc_template,
78
- summary: pack.summary, summary_template: pack.summary_template,
123
+ 'cli.desc': pack.cli.desc,
124
+ 'cli.desc_template': pack.cli.desc_template,
125
+ summary: pack.summary,
126
+ summary_template: pack.summary_template,
79
127
  })) {
80
128
  if (value !== undefined && (typeof value !== 'string' || hasOwnedCount(value))) {
81
- throw new Error(`${where}: ${field} must use derived placeholders for owned count dimensions`);
129
+ throw new Error(
130
+ `${where}: ${field} must use derived placeholders for owned count dimensions`
131
+ );
82
132
  }
83
133
  }
84
134
  if (!Array.isArray(pack.agents)) throw new Error(`${where}: "agents" must be a list`);
@@ -94,22 +144,82 @@ function validateRegistry(registry) {
94
144
  if (pack.install_layout === 'packaged' && !Array.isArray((pack.doctor || {}).pack_agents)) {
95
145
  throw new Error(`${where}: packaged layout requires "doctor.pack_agents" list`);
96
146
  }
147
+ if (
148
+ pack.doctor?.required_resources !== undefined &&
149
+ !Array.isArray(pack.doctor.required_resources)
150
+ ) {
151
+ throw new Error(`${where}: doctor.required_resources must be a list`);
152
+ }
153
+ if (
154
+ !Array.isArray(pack.runtime) ||
155
+ pack.runtime.length === 0 ||
156
+ pack.runtime.some((runtime) => !Object.hasOwn(registry.runtimes || {}, runtime))
157
+ ) {
158
+ throw new Error(`${where}: runtime must declare known runtimes`);
159
+ }
160
+ buildRequiredResources(pack);
97
161
  if (pack.personas !== undefined) {
98
162
  if (!Array.isArray(pack.personas)) throw new Error(`${where}: personas must be a list`);
99
163
  const roster = new Set([...pack.agents, ...(pack.sub_agents || [])]);
100
164
  const ids = new Set();
101
165
  for (const persona of pack.personas) {
102
- if (!persona || ['id', 'name', 'role', 'description'].some((key) =>
103
- typeof persona[key] !== 'string' || !persona[key].trim())) {
166
+ if (
167
+ !persona ||
168
+ ['id', 'name', 'role', 'description'].some(
169
+ (key) => typeof persona[key] !== 'string' || !persona[key].trim()
170
+ )
171
+ ) {
104
172
  throw new Error(`${where}: each persona needs id, name, role and description`);
105
173
  }
106
174
  if (!roster.has(persona.id) || ids.has(persona.id)) {
107
- throw new Error(`${where}: persona ${persona.id} is absent from its roster or duplicated`);
175
+ throw new Error(
176
+ `${where}: persona ${persona.id} is absent from its roster or duplicated`
177
+ );
108
178
  }
109
179
  ids.add(persona.id);
110
180
  }
111
181
  }
112
182
  }
183
+ const integration = registry.targets?.integration;
184
+ if (
185
+ integration?.execution !== 'host-managed' ||
186
+ integration?.lifecycle_events !== 'not-integrated'
187
+ ) {
188
+ throw new Error(
189
+ 'registry.yaml: targets.integration must declare the delivered instruction integration'
190
+ );
191
+ }
192
+ const processBackend = registry.targets?.optional_process_backend;
193
+ if (
194
+ processBackend &&
195
+ (!isDeepStrictEqual(processBackend.adapters, ['command', 'codex-exec']) ||
196
+ processBackend.supervisor !== 'foreground' ||
197
+ processBackend.launch !== 'explicit-plan' ||
198
+ processBackend.collect !== 'exact-attempt-receipt' ||
199
+ processBackend.cancellation !== 'original-owner-direct-child' ||
200
+ processBackend.verification !== 'independent-required' ||
201
+ processBackend.scheduling !== 'none')
202
+ ) {
203
+ throw new Error(
204
+ 'registry.yaml: optional process backend differs from the delivered Nexus contract'
205
+ );
206
+ }
207
+ for (const [runtime, config] of Object.entries(registry.runtimes || {})) {
208
+ if (
209
+ typeof config?.min_version !== 'string' ||
210
+ !/^\d+\.\d+(?:\.\d+)?$/.test(config.min_version)
211
+ ) {
212
+ throw new Error(`registry.yaml: runtime "${runtime}" needs a numeric min_version`);
213
+ }
214
+ }
215
+ const tools = new Set();
216
+ for (const adapter of registry.targets.adapters || []) {
217
+ if (!adapter || typeof adapter.tool !== 'string' || !adapter.tool || tools.has(adapter.tool)) {
218
+ throw new Error('registry.yaml: adapter tool names must be nonempty and unique');
219
+ }
220
+ tools.add(adapter.tool);
221
+ assertResourcePath(adapter.file, `registry.yaml: adapter "${adapter.tool}"`);
222
+ }
113
223
  return registry;
114
224
  }
115
225
 
@@ -171,10 +281,14 @@ function renderSummary(pack, derived, field = 'summary') {
171
281
  const fallback = field === 'summary' ? pack.summary : pack.cli.desc;
172
282
  if (!template) return fallback || '';
173
283
  const values = {
174
- agent_count: derived.agentCount, installer_agent_count: derived.installerAgentCount,
175
- sub_agent_count: derived.subAgentCount, workflow_count: derived.workflowCount,
176
- framework_count: derived.frameworkCount, category_count: derived.categoryCount,
177
- skill_count: derived.skillCount, reference_file_count: derived.referenceFiles,
284
+ agent_count: derived.agentCount,
285
+ installer_agent_count: derived.installerAgentCount,
286
+ sub_agent_count: derived.subAgentCount,
287
+ workflow_count: derived.workflowCount,
288
+ framework_count: derived.frameworkCount,
289
+ category_count: derived.categoryCount,
290
+ skill_count: derived.skillCount,
291
+ reference_file_count: derived.referenceFiles,
178
292
  };
179
293
  return template.replace(/\{([a-z_]+)\}/g, (_match, key) => {
180
294
  if (!(key in values)) throw new Error(`Unknown derived summary placeholder: ${key}`);
@@ -195,18 +309,33 @@ function buildDerived(registry, { sourceRoot = path.join(REPO_ROOT, 'src', 'bmad
195
309
  const installerAgentCount = pack.agents.length;
196
310
  const subAgentCount = (pack.sub_agents || []).length;
197
311
  const facts = {
198
- id, order: pack.order, name: pack.cli.name, displayName: pack.display_name || pack.cli.name,
199
- required: pack.required === true, installerAgentCount, categoryAgentCount,
312
+ id,
313
+ order: pack.order,
314
+ name: pack.cli.name,
315
+ displayName: pack.display_name || pack.cli.name,
316
+ required: pack.required === true,
317
+ installerAgentCount,
318
+ categoryAgentCount,
200
319
  agentCount: categoryAgentCount || subAgentCount || installerAgentCount,
201
320
  subAgentCount,
202
- workflowCount: (pack.workflows || []).length + categories.reduce((sum, c) => sum + (c.workflows || []).length, 0),
321
+ workflowCount:
322
+ (pack.workflows || []).length +
323
+ categories.reduce((sum, c) => sum + (c.workflows || []).length, 0),
203
324
  frameworkCount: (pack.compliance_tags || []).length,
204
325
  categoryCount: categories.length,
205
326
  skillCount: (pack.skills || []).length,
206
- referenceFiles: countMarkdownFiles(path.join(sourceRoot, pack.pack_src_dir || 'packs', pack.pack_dir, 'references')),
327
+ referenceFiles: countMarkdownFiles(
328
+ path.join(sourceRoot, pack.pack_src_dir || 'packs', pack.pack_dir, 'references')
329
+ ),
207
330
  categoryAgentCounts,
331
+ runtimes: [...pack.runtime],
332
+ requiredResources: buildRequiredResources(pack),
208
333
  personas: (pack.personas || []).map(({ id, name, role, description, alias }) => ({
209
- id, name, role, description, ...(alias ? { alias } : {}),
334
+ id,
335
+ name,
336
+ role,
337
+ description,
338
+ ...(alias ? { alias } : {}),
210
339
  })),
211
340
  };
212
341
  facts.summary = renderSummary(pack, facts);
@@ -215,10 +344,13 @@ function buildDerived(registry, { sourceRoot = path.join(REPO_ROOT, 'src', 'bmad
215
344
  packs[id] = facts;
216
345
  installerAgents += installerAgentCount;
217
346
  totalAgents += new Set([
218
- ...pack.agents, ...(pack.sub_agents || []), ...categories.flatMap((c) => c.agents || []),
347
+ ...pack.agents,
348
+ ...(pack.sub_agents || []),
349
+ ...categories.flatMap((c) => c.agents || []),
219
350
  ]).size;
220
351
  if ((pack.runtime || []).includes('python')) {
221
- const packagePath = pack.python_package || `src/bmad-plus/${pack.pack_src_dir || 'packs'}/${pack.pack_dir}`;
352
+ const packagePath =
353
+ pack.python_package || `src/bmad-plus/${pack.pack_src_dir || 'packs'}/${pack.pack_dir}`;
222
354
  pythonPacks[id] = {
223
355
  requirements: [...packagePath.split(/[\\/]/), 'requirements.txt'],
224
356
  verifyModules: [...(pack.python_verify_modules || [])],
@@ -228,12 +360,35 @@ function buildDerived(registry, { sourceRoot = path.join(REPO_ROOT, 'src', 'bmad
228
360
  const { LANGUAGES } = require('../cli/i18n');
229
361
  return {
230
362
  product: {
231
- code: registry.product.code, displayName: registry.product.display_name,
232
- version: registry.product.version, derivedFrom: registry.product.derived_from,
363
+ code: registry.product.code,
364
+ displayName: registry.product.display_name,
365
+ version: registry.product.version,
366
+ derivedFrom: registry.product.derived_from,
233
367
  },
234
368
  packOrder: buildPackOrder(registry),
235
- packCount: Object.keys(packs).length, installerAgents, totalAgents,
236
- languages: Object.keys(LANGUAGES), packs, pythonPacks,
369
+ packCount: Object.keys(packs).length,
370
+ installerAgents,
371
+ totalAgents,
372
+ languages: Object.keys(LANGUAGES),
373
+ packs,
374
+ pythonPacks,
375
+ diagnostics: {
376
+ schemaVersion: 1,
377
+ processExecution: registry.targets.optional_process_backend || null,
378
+ runtimeMinimums: Object.fromEntries(
379
+ Object.entries(registry.runtimes).map(([id, runtime]) => [id, runtime.min_version])
380
+ ),
381
+ integrations: Object.fromEntries(
382
+ registry.targets.adapters.map(({ tool, file }) => [
383
+ tool,
384
+ {
385
+ instructionFile: file,
386
+ execution: registry.targets.integration.execution,
387
+ lifecycleEvents: registry.targets.integration.lifecycle_events,
388
+ },
389
+ ])
390
+ ),
391
+ },
237
392
  targets: {
238
393
  spine: registry.targets.spine,
239
394
  adapters: registry.targets.adapters.map(({ tool, file }) => ({ tool, file })),
@@ -251,11 +406,24 @@ function generateModuleSource(registry, templatePath = MODULE_TEMPLATE_PATH) {
251
406
  for (const [id, pack] of sortedPackEntries(registry)) {
252
407
  const facts = derived.packs[id];
253
408
  const entry = {
254
- name: facts.displayName, icon: pack.icon_emoji, description: facts.summary || facts.desc,
255
- required: facts.required, agents: [...pack.agents], skills: [...(pack.skills || [])],
409
+ name: facts.displayName,
410
+ icon: pack.icon_emoji,
411
+ description: facts.summary || facts.desc,
412
+ required: facts.required,
413
+ agents: [...pack.agents],
414
+ skills: [...(pack.skills || [])],
256
415
  };
257
- for (const key of ['data', 'external_package', 'orchestrator', 'categories', 'workflows',
258
- 'sub_agents', 'required_keys', 'optional_keys', 'cohabitation_warning']) {
416
+ for (const key of [
417
+ 'data',
418
+ 'external_package',
419
+ 'orchestrator',
420
+ 'categories',
421
+ 'workflows',
422
+ 'sub_agents',
423
+ 'required_keys',
424
+ 'optional_keys',
425
+ 'cohabitation_warning',
426
+ ]) {
259
427
  if (key in pack) entry[key] = JSON.parse(JSON.stringify(pack[key]));
260
428
  }
261
429
  if (pack.install_layout === 'packaged') {
@@ -265,14 +433,19 @@ function generateModuleSource(registry, templatePath = MODULE_TEMPLATE_PATH) {
265
433
  moduleConfig.packs[id] = entry;
266
434
  }
267
435
  moduleConfig.install_packs['multi-select'] = [
268
- ...derived.packOrder.filter((id) => !derived.packs[id].required).map((id) => ({
269
- value: id, label: `${registry.packs[id].icon_emoji} ${derived.packs[id].displayName} — ${derived.packs[id].summary || derived.packs[id].desc}`,
270
- })),
436
+ ...derived.packOrder
437
+ .filter((id) => !derived.packs[id].required)
438
+ .map((id) => ({
439
+ value: id,
440
+ label: `${registry.packs[id].icon_emoji} ${derived.packs[id].displayName} — ${derived.packs[id].summary || derived.packs[id].desc}`,
441
+ })),
271
442
  { value: 'all', label: 'Tout installer' },
272
443
  { value: 'none', label: 'Aucun — Core uniquement' },
273
444
  ];
274
- return '# AUTO-GENERATED from registry.yaml + tools/build/module.template.yaml — DO NOT EDIT.\n' +
275
- yaml.dump(moduleConfig, { lineWidth: 110, noRefs: true });
445
+ return (
446
+ '# AUTO-GENERATED from registry.yaml + tools/build/module.template.yaml — DO NOT EDIT.\n' +
447
+ yaml.dump(moduleConfig, { lineWidth: 110, noRefs: true })
448
+ );
276
449
  }
277
450
 
278
451
  /**
@@ -373,6 +546,16 @@ function check({
373
546
  const mismatches = [];
374
547
 
375
548
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
549
+ const { checkPythonDelivery } = require('./pack-delivery');
550
+ mismatches.push(...checkPythonDelivery(registry, { packageRoot: REPO_ROOT, packageJson: pkg }));
551
+ try {
552
+ require('../cli/lib/studio').validatePack(
553
+ path.join(REPO_ROOT, 'src/bmad-plus/packs/pack-dev-studio'),
554
+ registry.packs['dev-studio']
555
+ );
556
+ } catch (error) {
557
+ mismatches.push('Dev Studio: ' + error.message);
558
+ }
376
559
  if (!registry.product || registry.product.version !== pkg.version) {
377
560
  const declared = registry.product ? registry.product.version : undefined;
378
561
  mismatches.push(
@@ -385,7 +568,9 @@ function check({
385
568
  describeDiff(key, generated[key], current[key], mismatches);
386
569
  }
387
570
  }
388
- if (fs.readFileSync(modulePath, 'utf8').replace(/\r\n/g, '\n') !== generateModuleSource(registry)) {
571
+ if (
572
+ fs.readFileSync(modulePath, 'utf8').replace(/\r\n/g, '\n') !== generateModuleSource(registry)
573
+ ) {
389
574
  mismatches.push('module.yaml: generated pack metadata differs from registry.yaml');
390
575
  }
391
576
 
@@ -427,7 +612,11 @@ function main(argv) {
427
612
  const moduleOutIdx = args.indexOf('--module-out');
428
613
  if (moduleOutIdx !== -1) {
429
614
  if (!args[moduleOutIdx + 1]) throw new Error('--module-out requires a file path');
430
- fs.writeFileSync(path.resolve(args[moduleOutIdx + 1]), generateModuleSource(registry), 'utf8');
615
+ fs.writeFileSync(
616
+ path.resolve(args[moduleOutIdx + 1]),
617
+ generateModuleSource(registry),
618
+ 'utf8'
619
+ );
431
620
  }
432
621
  console.log(`Generated ${path.resolve(outPath)} from registry.yaml`);
433
622
  return 0;
@@ -437,10 +626,6 @@ function main(argv) {
437
626
  return 0;
438
627
  }
439
628
 
440
- if (require.main === module) {
441
- process.exitCode = main(process.argv);
442
- }
443
-
444
629
  module.exports = {
445
630
  DEFAULT_REGISTRY_PATH,
446
631
  DEFAULT_PACKS_MODULE_PATH,
@@ -459,3 +644,7 @@ module.exports = {
459
644
  check,
460
645
  main,
461
646
  };
647
+
648
+ if (require.main === module) {
649
+ process.exitCode = main(process.argv);
650
+ }
@@ -113,7 +113,7 @@ After a successful update, reread the project spine and relevant installed skill
113
113
 
114
114
  ## Registry facts (computed from registry.yaml — never hand-typed)
115
115
 
116
- - Product: BMAD+ v0.13.0 (derived from BMAD-METHOD v6.6.0)
116
+ - Product: BMAD+ v0.16.0 (derived from BMAD-METHOD v6.6.0)
117
117
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
118
118
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
119
119
  - Installer agents (14 across all packs):
@@ -118,7 +118,7 @@ After a successful update, reread the project spine and relevant installed skill
118
118
 
119
119
  ## Registry facts (computed from registry.yaml — never hand-typed)
120
120
 
121
- - Product: BMAD+ v0.13.0 (derived from BMAD-METHOD v6.6.0)
121
+ - Product: BMAD+ v0.16.0 (derived from BMAD-METHOD v6.6.0)
122
122
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
123
123
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
124
124
  - Installer agents (14 across all packs):
@@ -113,7 +113,7 @@ After a successful update, reread the project spine and relevant installed skill
113
113
 
114
114
  ## Registry facts (computed from registry.yaml — never hand-typed)
115
115
 
116
- - Product: BMAD+ v0.13.0 (derived from BMAD-METHOD v6.6.0)
116
+ - Product: BMAD+ v0.16.0 (derived from BMAD-METHOD v6.6.0)
117
117
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
118
118
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
119
119
  - Installer agents (14 across all packs):
@@ -111,7 +111,7 @@ After a successful update, reread the project spine and relevant installed skill
111
111
 
112
112
  ## Registry facts (computed from registry.yaml — never hand-typed)
113
113
 
114
- - Product: BMAD+ v0.13.0 (derived from BMAD-METHOD v6.6.0)
114
+ - Product: BMAD+ v0.16.0 (derived from BMAD-METHOD v6.6.0)
115
115
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
116
116
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
117
117
  - Installer agents (14 across all packs):
@@ -114,7 +114,7 @@ After a successful update, reread the project spine and relevant installed skill
114
114
 
115
115
  ## Registry facts (computed from registry.yaml — never hand-typed)
116
116
 
117
- - Product: BMAD+ v0.13.0 (derived from BMAD-METHOD v6.6.0)
117
+ - Product: BMAD+ v0.16.0 (derived from BMAD-METHOD v6.6.0)
118
118
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
119
119
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
120
120
  - Installer agents (14 across all packs):
@@ -113,7 +113,7 @@ After a successful update, reread the project spine and relevant installed skill
113
113
 
114
114
  ## Registry facts (computed from registry.yaml — never hand-typed)
115
115
 
116
- - Product: BMAD+ v0.13.0 (derived from BMAD-METHOD v6.6.0)
116
+ - Product: BMAD+ v0.16.0 (derived from BMAD-METHOD v6.6.0)
117
117
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
118
118
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
119
119
  - Installer agents (14 across all packs):
@@ -118,7 +118,7 @@ After a successful update, reread the project spine and relevant installed skill
118
118
 
119
119
  ## Registry facts (computed from registry.yaml — never hand-typed)
120
120
 
121
- - Product: BMAD+ v0.13.0 (derived from BMAD-METHOD v6.6.0)
121
+ - Product: BMAD+ v0.16.0 (derived from BMAD-METHOD v6.6.0)
122
122
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
123
123
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
124
124
  - Installer agents (14 across all packs):
@@ -0,0 +1,78 @@
1
+ /** Build-time Python delivery checks; the npm contract repeats them on the archive. */
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ function portable(file) {
8
+ return (
9
+ typeof file === 'string' &&
10
+ Boolean(file) &&
11
+ !/[\\:\0*?![\]{}]/.test(file) &&
12
+ file.split('/').every((part) => part && part !== '.' && part !== '..')
13
+ );
14
+ }
15
+
16
+ function checkPythonDelivery(registry, { packageRoot, packageJson }) {
17
+ const errors = [];
18
+ const roots = (packageJson.files || []).map((root) => root.replace(/\/$/, ''));
19
+ for (const [id, pack] of Object.entries(registry.packs)) {
20
+ if (!pack.runtime.includes('python')) continue;
21
+ if (!portable(pack.python_package)) {
22
+ errors.push(`${id}: python_package must name a portable package directory`);
23
+ continue;
24
+ }
25
+ const resources = [`${pack.python_package}/requirements.txt`];
26
+ if (pack.python_entry !== undefined) {
27
+ if (!portable(pack.python_entry)) {
28
+ errors.push(`${id}: python_entry must remain inside python_package`);
29
+ continue;
30
+ }
31
+ resources.push(`${pack.python_package}/${pack.python_entry}`);
32
+ }
33
+ for (const file of resources) {
34
+ if (!roots.some((root) => portable(root) && (file === root || file.startsWith(`${root}/`)))) {
35
+ errors.push(`${id}: ${file} is outside package.json files roots`);
36
+ }
37
+ try {
38
+ const target = path.join(packageRoot, file);
39
+ if (!fs.lstatSync(target).isFile()) throw new Error('not a regular file');
40
+ const relative = path.relative(fs.realpathSync(packageRoot), fs.realpathSync(target));
41
+ if (relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
42
+ throw new Error('outside package');
43
+ } catch {
44
+ errors.push(`${id}: ${file} is missing or is not a regular file inside the package`);
45
+ }
46
+ }
47
+ }
48
+ return errors;
49
+ }
50
+
51
+ function checkSeoMirror(packageRoot) {
52
+ const shipped = path.join(packageRoot, 'src/bmad-plus/packs/pack-seo');
53
+ const tested = path.join(packageRoot, 'oveanet-pack/seo-audit-360');
54
+ const files = (root) =>
55
+ fs
56
+ .readdirSync(path.join(root, 'scripts'))
57
+ .filter((file) => file.endsWith('.py'))
58
+ .sort();
59
+ const shippedFiles = files(shipped);
60
+ const testedFiles = files(tested);
61
+ const errors = [];
62
+ if (JSON.stringify(shippedFiles) !== JSON.stringify(testedFiles))
63
+ errors.push('SEO script inventory differs between shipped and tested sources');
64
+ for (const file of ['requirements.txt', ...shippedFiles.map((file) => `scripts/${file}`)]) {
65
+ try {
66
+ if (
67
+ !fs.readFileSync(path.join(shipped, file)).equals(fs.readFileSync(path.join(tested, file)))
68
+ ) {
69
+ errors.push(`SEO shipped/tested sources differ: ${file}`);
70
+ }
71
+ } catch {
72
+ errors.push(`SEO shipped/tested source is missing: ${file}`);
73
+ }
74
+ }
75
+ return errors;
76
+ }
77
+
78
+ module.exports = { checkPythonDelivery, checkSeoMirror };
@@ -45,9 +45,7 @@ for (const option of install.options || []) {
45
45
  installCmd.action(install.action);
46
46
 
47
47
  // Uninstall command
48
- const uninstallCmd = program
49
- .command('uninstall')
50
- .description('Remove BMAD+ from your project');
48
+ const uninstallCmd = program.command('uninstall').description('Remove BMAD+ from your project');
51
49
  for (const option of uninstall.options || []) {
52
50
  uninstallCmd.option(...option);
53
51
  }
@@ -64,9 +62,7 @@ for (const option of update.options || []) {
64
62
  updateCmd.action(update.action);
65
63
 
66
64
  // Doctor command
67
- const doctorCmd = program
68
- .command('doctor')
69
- .description('Check BMAD+ installation integrity');
65
+ const doctorCmd = program.command('doctor').description('Check BMAD+ installation integrity');
70
66
 
71
67
  for (const option of doctor.options || []) {
72
68
  doctorCmd.option(...option);
@@ -81,7 +77,9 @@ const scanCmd = program
81
77
  for (const option of scan.options || []) {
82
78
  scanCmd.option(...option);
83
79
  }
84
- scanCmd.action((scanPath, options) => scan.action({ ...options, directory: scanPath || options.directory }));
80
+ scanCmd.action((scanPath, options) =>
81
+ scan.action({ ...options, directory: scanPath || options.directory })
82
+ );
85
83
 
86
84
  // Memory command
87
85
  const memoryCmd = program
@@ -95,9 +93,7 @@ memoryCmd.action(memory.action);
95
93
 
96
94
  // Mem command (Karpathy memory loop — journal recall/write/reinforce)
97
95
  const memJournal = require('./commands/memory-journal-cmd');
98
- const memJournalCmd = program
99
- .command(memJournal.command)
100
- .description(memJournal.description);
96
+ const memJournalCmd = program.command(memJournal.command).description(memJournal.description);
101
97
 
102
98
  for (const option of memJournal.options || []) {
103
99
  memJournalCmd.option(...option);
@@ -116,15 +112,22 @@ for (const option of autoconfig.options || []) {
116
112
  autoconfigCmd.action(autoconfig.action);
117
113
 
118
114
  // Version discovery and explicit per-project update policy.
119
- for (const modulePath of ['./commands/update-check', './commands/update-policy']) {
115
+ for (const modulePath of [
116
+ './commands/update-check',
117
+ './commands/update-policy',
118
+ './commands/studio',
119
+ './commands/nexus',
120
+ './commands/uat',
121
+ ]) {
120
122
  const command = require(modulePath);
121
123
  const configured = program.command(command.command).description(command.description);
124
+ if (command.aliases) configured.aliases(command.aliases);
122
125
  for (const option of command.options || []) configured.option(...option);
123
126
  configured.action(command.action);
124
127
  }
125
128
 
126
129
  // Await asynchronous registry checks and updates before completing the CLI.
127
- program.parseAsync(process.argv).catch(error => {
130
+ program.parseAsync(process.argv).catch((error) => {
128
131
  console.error(`BMAD+: ${error.message}`);
129
132
  process.exitCode = 1;
130
133
  });