bmad-plus 0.13.0 → 0.14.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 (104) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +96 -489
  3. package/package.json +7 -2
  4. package/readme-international/README.de.md +9 -5
  5. package/readme-international/README.es.md +9 -5
  6. package/readme-international/README.fr.md +9 -5
  7. package/src/bmad-plus/agents/agent-orchestrator/SKILL.md +3 -3
  8. package/src/bmad-plus/packs/pack-dev-studio/README.md +133 -141
  9. package/src/bmad-plus/packs/pack-dev-studio/SKILL.md +49 -0
  10. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/analyst-agent.md +35 -60
  11. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/document-project.md +59 -59
  12. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/domain-research.md +55 -93
  13. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/market-research.md +58 -93
  14. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/prfaq.md +55 -132
  15. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/product-brief.md +63 -78
  16. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/tech-writer-agent.md +54 -69
  17. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/technical-research.md +54 -93
  18. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/architect-agent.md +32 -60
  19. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-architecture.md +67 -71
  20. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-epics-stories.md +61 -90
  21. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/generate-project-context.md +56 -78
  22. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/implementation-readiness.md +55 -88
  23. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01-init.md +20 -153
  24. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01b-continue.md +20 -173
  25. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-02-context.md +14 -220
  26. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-03-starter.md +20 -329
  27. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-04-decisions.md +15 -314
  28. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-05-patterns.md +15 -355
  29. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-06-structure.md +15 -375
  30. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-07-validation.md +14 -357
  31. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-08-complete.md +13 -78
  32. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/checkpoint-preview.md +52 -65
  33. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-01-gather-context.md +14 -81
  34. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-02-review.md +14 -31
  35. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-03-triage.md +14 -45
  36. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-04-present.md +13 -128
  37. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review.md +61 -87
  38. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/correct-course.md +55 -298
  39. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/create-story.md +52 -426
  40. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-agent.md +48 -69
  41. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story-checklist.md +22 -80
  42. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story.md +60 -482
  43. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/investigate.md +50 -184
  44. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/qa-e2e-tests.md +57 -173
  45. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/quick-dev.md +56 -108
  46. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/retrospective.md +54 -1509
  47. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-planning.md +54 -296
  48. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-status.md +41 -283
  49. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-prd.md +58 -18
  50. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-ux-design.md +103 -72
  51. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/edit-prd.md +55 -27
  52. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/pm-agent.md +34 -60
  53. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/prd.md +46 -87
  54. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/steps/step-01-init.md +10 -0
  55. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/ux-designer-agent.md +30 -60
  56. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/validate-prd.md +57 -27
  57. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/advanced-elicitation.md +47 -138
  58. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/adversarial-review.md +48 -34
  59. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/bmad-help.md +51 -68
  60. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/brainstorming.md +46 -3
  61. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/customize.md +68 -109
  62. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/distillator.md +53 -174
  63. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/edge-case-hunter.md +39 -53
  64. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-prose.md +45 -83
  65. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-structure.md +45 -176
  66. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/index-docs.md +45 -63
  67. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/party-mode.md +53 -124
  68. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/shard-doc.md +44 -100
  69. package/src/bmad-plus/packs/pack-dev-studio/dev-studio-orchestrator.md +56 -116
  70. package/src/bmad-plus/packs/pack-dev-studio/shared/architecture-decision-template.md +49 -12
  71. package/src/bmad-plus/packs/pack-dev-studio/shared/bwml-spec.md +51 -328
  72. package/src/bmad-plus/packs/pack-dev-studio/shared/catalog.json +489 -0
  73. package/src/bmad-plus/packs/pack-dev-studio/shared/execution.md +69 -0
  74. package/src/bmad-plus/packs/pack-dev-studio/shared/module-help.csv +39 -32
  75. package/src/bmad-plus/packs/pack-dev-studio/upstream-sync.yaml +85 -14
  76. package/src/bmad-plus/packs/pack-memory/README.md +6 -0
  77. package/src/bmad-plus/packs/pack-memory/memory-orchestrator.md +12 -5
  78. package/src/bmad-plus/packs/pack-seo/SKILL.md +27 -1
  79. package/src/bmad-plus/packs/pack-seo/seo-chief.md +16 -1
  80. package/src/bmad-plus/packs/pack-seo/seo-judge.md +12 -0
  81. package/src/bmad-plus/packs/pack-seo/seo-scout.md +12 -0
  82. package/src/bmad-plus/skills/bmad-plus-autopilot/SKILL.md +3 -3
  83. package/src/bmad-plus/skills/bmad-plus-sync/SKILL.md +5 -3
  84. package/src/bmad-plus/skills/dev-studio/SKILL.md +19 -0
  85. package/tools/build/check-install-contract.js +165 -13
  86. package/tools/build/generate.js +213 -40
  87. package/tools/build/generated-adapters/.codex/AGENTS.md +1 -1
  88. package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +1 -1
  89. package/tools/build/generated-adapters/.opencode/AGENTS.md +1 -1
  90. package/tools/build/generated-adapters/AGENTS.md +1 -1
  91. package/tools/build/generated-adapters/CLAUDE.md +1 -1
  92. package/tools/build/generated-adapters/CONVENTIONS.md +1 -1
  93. package/tools/build/generated-adapters/GEMINI.md +1 -1
  94. package/tools/build/pack-delivery.js +78 -0
  95. package/tools/cli/bmad-plus-cli.js +1 -1
  96. package/tools/cli/commands/doctor.js +49 -189
  97. package/tools/cli/commands/install.js +1 -1
  98. package/tools/cli/commands/studio.js +68 -0
  99. package/tools/cli/lib/installation-health.js +360 -0
  100. package/tools/cli/lib/packs.js +118 -9
  101. package/tools/cli/lib/python-health.js +233 -0
  102. package/tools/cli/lib/python-provision.js +2 -2
  103. package/tools/cli/lib/studio.js +310 -0
  104. package/tools/release/publication-content.js +901 -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,67 @@ 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
+ for (const [runtime, config] of Object.entries(registry.runtimes || {})) {
193
+ if (
194
+ typeof config?.min_version !== 'string' ||
195
+ !/^\d+\.\d+(?:\.\d+)?$/.test(config.min_version)
196
+ ) {
197
+ throw new Error(`registry.yaml: runtime "${runtime}" needs a numeric min_version`);
198
+ }
199
+ }
200
+ const tools = new Set();
201
+ for (const adapter of registry.targets.adapters || []) {
202
+ if (!adapter || typeof adapter.tool !== 'string' || !adapter.tool || tools.has(adapter.tool)) {
203
+ throw new Error('registry.yaml: adapter tool names must be nonempty and unique');
204
+ }
205
+ tools.add(adapter.tool);
206
+ assertResourcePath(adapter.file, `registry.yaml: adapter "${adapter.tool}"`);
207
+ }
113
208
  return registry;
114
209
  }
115
210
 
@@ -171,10 +266,14 @@ function renderSummary(pack, derived, field = 'summary') {
171
266
  const fallback = field === 'summary' ? pack.summary : pack.cli.desc;
172
267
  if (!template) return fallback || '';
173
268
  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,
269
+ agent_count: derived.agentCount,
270
+ installer_agent_count: derived.installerAgentCount,
271
+ sub_agent_count: derived.subAgentCount,
272
+ workflow_count: derived.workflowCount,
273
+ framework_count: derived.frameworkCount,
274
+ category_count: derived.categoryCount,
275
+ skill_count: derived.skillCount,
276
+ reference_file_count: derived.referenceFiles,
178
277
  };
179
278
  return template.replace(/\{([a-z_]+)\}/g, (_match, key) => {
180
279
  if (!(key in values)) throw new Error(`Unknown derived summary placeholder: ${key}`);
@@ -195,18 +294,33 @@ function buildDerived(registry, { sourceRoot = path.join(REPO_ROOT, 'src', 'bmad
195
294
  const installerAgentCount = pack.agents.length;
196
295
  const subAgentCount = (pack.sub_agents || []).length;
197
296
  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,
297
+ id,
298
+ order: pack.order,
299
+ name: pack.cli.name,
300
+ displayName: pack.display_name || pack.cli.name,
301
+ required: pack.required === true,
302
+ installerAgentCount,
303
+ categoryAgentCount,
200
304
  agentCount: categoryAgentCount || subAgentCount || installerAgentCount,
201
305
  subAgentCount,
202
- workflowCount: (pack.workflows || []).length + categories.reduce((sum, c) => sum + (c.workflows || []).length, 0),
306
+ workflowCount:
307
+ (pack.workflows || []).length +
308
+ categories.reduce((sum, c) => sum + (c.workflows || []).length, 0),
203
309
  frameworkCount: (pack.compliance_tags || []).length,
204
310
  categoryCount: categories.length,
205
311
  skillCount: (pack.skills || []).length,
206
- referenceFiles: countMarkdownFiles(path.join(sourceRoot, pack.pack_src_dir || 'packs', pack.pack_dir, 'references')),
312
+ referenceFiles: countMarkdownFiles(
313
+ path.join(sourceRoot, pack.pack_src_dir || 'packs', pack.pack_dir, 'references')
314
+ ),
207
315
  categoryAgentCounts,
316
+ runtimes: [...pack.runtime],
317
+ requiredResources: buildRequiredResources(pack),
208
318
  personas: (pack.personas || []).map(({ id, name, role, description, alias }) => ({
209
- id, name, role, description, ...(alias ? { alias } : {}),
319
+ id,
320
+ name,
321
+ role,
322
+ description,
323
+ ...(alias ? { alias } : {}),
210
324
  })),
211
325
  };
212
326
  facts.summary = renderSummary(pack, facts);
@@ -215,10 +329,13 @@ function buildDerived(registry, { sourceRoot = path.join(REPO_ROOT, 'src', 'bmad
215
329
  packs[id] = facts;
216
330
  installerAgents += installerAgentCount;
217
331
  totalAgents += new Set([
218
- ...pack.agents, ...(pack.sub_agents || []), ...categories.flatMap((c) => c.agents || []),
332
+ ...pack.agents,
333
+ ...(pack.sub_agents || []),
334
+ ...categories.flatMap((c) => c.agents || []),
219
335
  ]).size;
220
336
  if ((pack.runtime || []).includes('python')) {
221
- const packagePath = pack.python_package || `src/bmad-plus/${pack.pack_src_dir || 'packs'}/${pack.pack_dir}`;
337
+ const packagePath =
338
+ pack.python_package || `src/bmad-plus/${pack.pack_src_dir || 'packs'}/${pack.pack_dir}`;
222
339
  pythonPacks[id] = {
223
340
  requirements: [...packagePath.split(/[\\/]/), 'requirements.txt'],
224
341
  verifyModules: [...(pack.python_verify_modules || [])],
@@ -228,12 +345,34 @@ function buildDerived(registry, { sourceRoot = path.join(REPO_ROOT, 'src', 'bmad
228
345
  const { LANGUAGES } = require('../cli/i18n');
229
346
  return {
230
347
  product: {
231
- code: registry.product.code, displayName: registry.product.display_name,
232
- version: registry.product.version, derivedFrom: registry.product.derived_from,
348
+ code: registry.product.code,
349
+ displayName: registry.product.display_name,
350
+ version: registry.product.version,
351
+ derivedFrom: registry.product.derived_from,
233
352
  },
234
353
  packOrder: buildPackOrder(registry),
235
- packCount: Object.keys(packs).length, installerAgents, totalAgents,
236
- languages: Object.keys(LANGUAGES), packs, pythonPacks,
354
+ packCount: Object.keys(packs).length,
355
+ installerAgents,
356
+ totalAgents,
357
+ languages: Object.keys(LANGUAGES),
358
+ packs,
359
+ pythonPacks,
360
+ diagnostics: {
361
+ schemaVersion: 1,
362
+ runtimeMinimums: Object.fromEntries(
363
+ Object.entries(registry.runtimes).map(([id, runtime]) => [id, runtime.min_version])
364
+ ),
365
+ integrations: Object.fromEntries(
366
+ registry.targets.adapters.map(({ tool, file }) => [
367
+ tool,
368
+ {
369
+ instructionFile: file,
370
+ execution: registry.targets.integration.execution,
371
+ lifecycleEvents: registry.targets.integration.lifecycle_events,
372
+ },
373
+ ])
374
+ ),
375
+ },
237
376
  targets: {
238
377
  spine: registry.targets.spine,
239
378
  adapters: registry.targets.adapters.map(({ tool, file }) => ({ tool, file })),
@@ -251,11 +390,24 @@ function generateModuleSource(registry, templatePath = MODULE_TEMPLATE_PATH) {
251
390
  for (const [id, pack] of sortedPackEntries(registry)) {
252
391
  const facts = derived.packs[id];
253
392
  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 || [])],
393
+ name: facts.displayName,
394
+ icon: pack.icon_emoji,
395
+ description: facts.summary || facts.desc,
396
+ required: facts.required,
397
+ agents: [...pack.agents],
398
+ skills: [...(pack.skills || [])],
256
399
  };
257
- for (const key of ['data', 'external_package', 'orchestrator', 'categories', 'workflows',
258
- 'sub_agents', 'required_keys', 'optional_keys', 'cohabitation_warning']) {
400
+ for (const key of [
401
+ 'data',
402
+ 'external_package',
403
+ 'orchestrator',
404
+ 'categories',
405
+ 'workflows',
406
+ 'sub_agents',
407
+ 'required_keys',
408
+ 'optional_keys',
409
+ 'cohabitation_warning',
410
+ ]) {
259
411
  if (key in pack) entry[key] = JSON.parse(JSON.stringify(pack[key]));
260
412
  }
261
413
  if (pack.install_layout === 'packaged') {
@@ -265,14 +417,19 @@ function generateModuleSource(registry, templatePath = MODULE_TEMPLATE_PATH) {
265
417
  moduleConfig.packs[id] = entry;
266
418
  }
267
419
  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
- })),
420
+ ...derived.packOrder
421
+ .filter((id) => !derived.packs[id].required)
422
+ .map((id) => ({
423
+ value: id,
424
+ label: `${registry.packs[id].icon_emoji} ${derived.packs[id].displayName} — ${derived.packs[id].summary || derived.packs[id].desc}`,
425
+ })),
271
426
  { value: 'all', label: 'Tout installer' },
272
427
  { value: 'none', label: 'Aucun — Core uniquement' },
273
428
  ];
274
- return '# AUTO-GENERATED from registry.yaml + tools/build/module.template.yaml — DO NOT EDIT.\n' +
275
- yaml.dump(moduleConfig, { lineWidth: 110, noRefs: true });
429
+ return (
430
+ '# AUTO-GENERATED from registry.yaml + tools/build/module.template.yaml — DO NOT EDIT.\n' +
431
+ yaml.dump(moduleConfig, { lineWidth: 110, noRefs: true })
432
+ );
276
433
  }
277
434
 
278
435
  /**
@@ -373,6 +530,16 @@ function check({
373
530
  const mismatches = [];
374
531
 
375
532
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
533
+ const { checkPythonDelivery } = require('./pack-delivery');
534
+ mismatches.push(...checkPythonDelivery(registry, { packageRoot: REPO_ROOT, packageJson: pkg }));
535
+ try {
536
+ require('../cli/lib/studio').validatePack(
537
+ path.join(REPO_ROOT, 'src/bmad-plus/packs/pack-dev-studio'),
538
+ registry.packs['dev-studio']
539
+ );
540
+ } catch (error) {
541
+ mismatches.push('Dev Studio: ' + error.message);
542
+ }
376
543
  if (!registry.product || registry.product.version !== pkg.version) {
377
544
  const declared = registry.product ? registry.product.version : undefined;
378
545
  mismatches.push(
@@ -385,7 +552,9 @@ function check({
385
552
  describeDiff(key, generated[key], current[key], mismatches);
386
553
  }
387
554
  }
388
- if (fs.readFileSync(modulePath, 'utf8').replace(/\r\n/g, '\n') !== generateModuleSource(registry)) {
555
+ if (
556
+ fs.readFileSync(modulePath, 'utf8').replace(/\r\n/g, '\n') !== generateModuleSource(registry)
557
+ ) {
389
558
  mismatches.push('module.yaml: generated pack metadata differs from registry.yaml');
390
559
  }
391
560
 
@@ -427,7 +596,11 @@ function main(argv) {
427
596
  const moduleOutIdx = args.indexOf('--module-out');
428
597
  if (moduleOutIdx !== -1) {
429
598
  if (!args[moduleOutIdx + 1]) throw new Error('--module-out requires a file path');
430
- fs.writeFileSync(path.resolve(args[moduleOutIdx + 1]), generateModuleSource(registry), 'utf8');
599
+ fs.writeFileSync(
600
+ path.resolve(args[moduleOutIdx + 1]),
601
+ generateModuleSource(registry),
602
+ 'utf8'
603
+ );
431
604
  }
432
605
  console.log(`Generated ${path.resolve(outPath)} from registry.yaml`);
433
606
  return 0;
@@ -437,10 +610,6 @@ function main(argv) {
437
610
  return 0;
438
611
  }
439
612
 
440
- if (require.main === module) {
441
- process.exitCode = main(process.argv);
442
- }
443
-
444
613
  module.exports = {
445
614
  DEFAULT_REGISTRY_PATH,
446
615
  DEFAULT_PACKS_MODULE_PATH,
@@ -459,3 +628,7 @@ module.exports = {
459
628
  check,
460
629
  main,
461
630
  };
631
+
632
+ if (require.main === module) {
633
+ process.exitCode = main(process.argv);
634
+ }
@@ -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.14.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.14.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.14.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.14.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.14.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.14.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.14.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 };
@@ -116,7 +116,7 @@ for (const option of autoconfig.options || []) {
116
116
  autoconfigCmd.action(autoconfig.action);
117
117
 
118
118
  // Version discovery and explicit per-project update policy.
119
- for (const modulePath of ['./commands/update-check', './commands/update-policy']) {
119
+ for (const modulePath of ['./commands/update-check', './commands/update-policy', './commands/studio']) {
120
120
  const command = require(modulePath);
121
121
  const configured = program.command(command.command).description(command.description);
122
122
  for (const option of command.options || []) configured.option(...option);