bmad-plus 0.8.0 → 0.9.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 (37) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +4 -2
  3. package/package.json +1 -1
  4. package/readme-international/README.de.md +10 -2
  5. package/readme-international/README.es.md +32 -9
  6. package/readme-international/README.fr.md +29 -6
  7. package/src/bmad-plus/packs/pack-seo/bmad-skill-manifest.yaml +13 -0
  8. package/src/bmad-plus/packs/pack-shield/SKILL.md +82 -0
  9. package/tools/bmad-plus-npx.js +3 -5
  10. package/tools/cli/commands/autoconfig.js +16 -6
  11. package/tools/cli/commands/doctor.js +28 -31
  12. package/tools/cli/commands/install.js +37 -228
  13. package/tools/cli/commands/scan.js +37 -35
  14. package/tools/cli/commands/update.js +13 -71
  15. package/tools/cli/i18n.js +92 -10
  16. package/tools/cli/lib/memory-init.js +114 -0
  17. package/tools/cli/lib/pack-copy.js +84 -0
  18. package/tools/cli/lib/packs.js +114 -0
  19. package/src/bmad-plus/agents/pack-animated/animated-website-agent.md +0 -325
  20. package/src/bmad-plus/agents/pack-animated/templates/animated-website-workflow.md +0 -55
  21. package/src/bmad-plus/agents/pack-backup/backup-agent.md +0 -71
  22. package/src/bmad-plus/agents/pack-backup/templates/backup-workflow.md +0 -51
  23. package/src/bmad-plus/agents/pack-seo/SKILL.md +0 -171
  24. package/src/bmad-plus/agents/pack-seo/checklist.md +0 -140
  25. package/src/bmad-plus/agents/pack-seo/pagespeed-playbook.md +0 -320
  26. package/src/bmad-plus/agents/pack-seo/ref/audit-schema.json +0 -187
  27. package/src/bmad-plus/agents/pack-seo/ref/cwv-thresholds.md +0 -87
  28. package/src/bmad-plus/agents/pack-seo/ref/eeat-criteria.md +0 -123
  29. package/src/bmad-plus/agents/pack-seo/ref/geo-signals.md +0 -167
  30. package/src/bmad-plus/agents/pack-seo/ref/hreflang-rules.md +0 -153
  31. package/src/bmad-plus/agents/pack-seo/ref/quality-gates.md +0 -133
  32. package/src/bmad-plus/agents/pack-seo/ref/schema-catalog.md +0 -91
  33. package/src/bmad-plus/agents/pack-seo/ref/schema-templates.json +0 -356
  34. package/src/bmad-plus/agents/pack-seo/seo-chief.md +0 -294
  35. package/src/bmad-plus/agents/pack-seo/seo-judge.md +0 -241
  36. package/src/bmad-plus/agents/pack-seo/seo-scout.md +0 -171
  37. package/src/bmad-plus/agents/pack-seo/templates/seo-audit-workflow.md +0 -241
@@ -13,94 +13,11 @@ const fsExtra = require('fs-extra');
13
13
  const clack = require('@clack/prompts');
14
14
  const pc = require('picocolors');
15
15
  const { t, getLanguageOptions, getCommLanguageOptions } = require('../i18n');
16
+ const { PACKS } = require('../lib/packs');
17
+ const { copyPackFiles } = require('../lib/pack-copy');
18
+ const { initMemory } = require('../lib/memory-init');
16
19
 
17
- // Pack definitions
18
- const PACKS = {
19
- core: {
20
- name: 'Core Development',
21
- icon: '⚙️',
22
- description: '4 multi-role agents (Atlas, Forge, Sentinel, Nexus)',
23
- required: true,
24
- agents: ['agent-strategist', 'agent-architect-dev', 'agent-quality', 'agent-orchestrator'],
25
- skills: ['bmad-plus-autopilot', 'bmad-plus-parallel', 'bmad-plus-sync'],
26
- data: ['role-triggers.yaml'],
27
- },
28
- osint: {
29
- name: 'OSINT Intelligence',
30
- icon: '🔍',
31
- description: 'Agent Shadow — investigation, scraping, psychoprofil',
32
- required: false,
33
- agents: ['agent-shadow'],
34
- skills: [],
35
- externalPackage: 'osint-agent-package',
36
- },
37
- maker: {
38
- name: 'Agent Creator',
39
- icon: '🧬',
40
- description: 'Maker — design, build, and package new BMAD+ agents',
41
- required: false,
42
- agents: ['agent-maker'],
43
- skills: [],
44
- data: [],
45
- },
46
- shield: {
47
- name: 'Pack Shield (GRC)',
48
- icon: '🛡️',
49
- description: '38 compliance agents — GDPR, ISO 27001, SOC 2, PCI DSS, EU AI Act...',
50
- required: false,
51
- agents: [],
52
- skills: [],
53
- packDir: 'pack-shield',
54
- packSrcDir: 'packs',
55
- },
56
- 'dev-studio': {
57
- name: 'Dev Studio — Full SDLC',
58
- icon: '🏗️',
59
- description: 'Full SDLC pipeline: brainstorm → PRD → architecture → TDD → code review → deploy',
60
- required: false,
61
- agents: [],
62
- skills: [],
63
- packDir: 'pack-dev-studio',
64
- packSrcDir: 'packs',
65
- },
66
- seo: {
67
- name: 'SEO Audit 360',
68
- icon: '🔍',
69
- description: '3 agents (Scout, Chief, Judge) + 6-phase audit + PageSpeed loop',
70
- required: false,
71
- agents: [],
72
- skills: [],
73
- packDir: 'pack-seo',
74
- },
75
- backup: {
76
- name: 'Universal Backup',
77
- icon: '🗂️',
78
- description: 'Timestamped ZIP backup with smart exclusions',
79
- required: false,
80
- agents: [],
81
- skills: [],
82
- packDir: 'pack-backup',
83
- },
84
- animated: {
85
- name: 'Animated Website',
86
- icon: '🎬',
87
- description: 'Luxury scroll-driven website from video',
88
- required: false,
89
- agents: [],
90
- skills: [],
91
- packDir: 'pack-animated',
92
- },
93
- memory: {
94
- name: 'Memory — Persistent Brain',
95
- icon: '🧠',
96
- description: 'Cross-session memory + project scanner + Karpathy guardrails. Agents learn.',
97
- required: false,
98
- agents: [],
99
- skills: [],
100
- packDir: 'pack-memory',
101
- packSrcDir: 'packs',
102
- },
103
- };
20
+ // Pack definitions are imported from the shared module: require('../lib/packs').PACKS
104
21
 
105
22
  // IDE configurations
106
23
  const IDE_CONFIGS = {
@@ -153,7 +70,7 @@ module.exports = {
153
70
 
154
71
  if (clack.isCancel(langChoice)) {
155
72
  clack.cancel('Installation cancelled.');
156
- process.exit(0);
73
+ throw new Error('Installation cancelled.');
157
74
  }
158
75
  lang = langChoice;
159
76
  }
@@ -164,7 +81,7 @@ module.exports = {
164
81
  if (!fs.existsSync(bmadSrc)) {
165
82
  clack.log.error(`${i.source_not_found}: ${bmadSrc}`);
166
83
  clack.outro(pc.red(i.failed));
167
- process.exit(1);
84
+ throw new Error(`Source not found: ${bmadSrc}`);
168
85
  }
169
86
 
170
87
  clack.log.info(`${i.installing_to}: ${pc.cyan(projectDir)}`);
@@ -187,7 +104,7 @@ module.exports = {
187
104
  .map(([key, pack]) => ({
188
105
  value: key,
189
106
  label: `${pack.icon} ${pack.name}`,
190
- hint: pack.disabled ? i.soon : pack.description,
107
+ hint: pack.disabled ? i.soon : (pack.desc || pack.description || ''),
191
108
  disabled: pack.disabled,
192
109
  })),
193
110
  required: false,
@@ -195,7 +112,7 @@ module.exports = {
195
112
 
196
113
  if (clack.isCancel(packChoice)) {
197
114
  clack.cancel(i.cancelled);
198
- process.exit(0);
115
+ throw new Error(i.cancelled);
199
116
  }
200
117
 
201
118
  selectedPacks = [...new Set(['core', ...packChoice])];
@@ -278,10 +195,24 @@ module.exports = {
278
195
 
279
196
  if (clack.isCancel(userConfig)) {
280
197
  clack.cancel(i.cancelled);
281
- process.exit(0);
198
+ throw new Error(i.cancelled);
282
199
  }
283
200
 
284
- userName = userConfig.userName;
201
+ // Validate user-provided name
202
+ const rawName = userConfig.userName;
203
+ const SHELL_META = /[;&|`$(){}[\]!#~<>*?\\\n\r]/;
204
+ if (!rawName || rawName.trim().length === 0) {
205
+ clack.log.warn('Name cannot be empty. Using default.');
206
+ userName = process.env.USER || process.env.USERNAME || 'Developer';
207
+ } else if (rawName.length > 100) {
208
+ clack.log.warn('Name too long (>100 chars). Truncating.');
209
+ userName = rawName.slice(0, 100);
210
+ } else if (SHELL_META.test(rawName)) {
211
+ clack.log.warn('Name contains shell metacharacters. Using sanitized version.');
212
+ userName = rawName.replace(SHELL_META, '').trim() || 'Developer';
213
+ } else {
214
+ userName = rawName;
215
+ }
285
216
  commLang = userConfig.commLang;
286
217
  }
287
218
 
@@ -302,148 +233,26 @@ module.exports = {
302
233
  let copiedSkills = 0;
303
234
  let copiedFiles = 0;
304
235
 
236
+ const projectRoot = path.join(bmadSrc, '..', '..');
237
+
305
238
  for (const packId of selectedPacks) {
306
239
  const pack = PACKS[packId];
307
240
  if (!pack || pack.disabled) continue;
308
241
 
309
- // Copy agents
310
- for (const agent of pack.agents) {
311
- const src = path.join(bmadSrc, 'agents', agent);
312
- const dest = path.join(targetAgentsDir, agent);
313
- if (fs.existsSync(src)) {
314
- fsExtra.copySync(src, dest, { overwrite: true });
315
- copiedAgents++;
316
- }
317
- }
318
-
319
- // Copy skills
320
- for (const skill of pack.skills) {
321
- const src = path.join(bmadSrc, 'skills', skill);
322
- const dest = path.join(targetAgentsDir, skill);
323
- if (fs.existsSync(src)) {
324
- fsExtra.copySync(src, dest, { overwrite: true });
325
- copiedSkills++;
326
- }
327
- }
328
-
329
- // Copy data files
330
- for (const dataFile of (pack.data || [])) {
331
- const src = path.join(bmadSrc, 'data', dataFile);
332
- const dest = path.join(targetDataDir, dataFile);
333
- if (fs.existsSync(src)) {
334
- fsExtra.copySync(src, dest, { overwrite: true });
335
- copiedFiles++;
336
- }
337
- }
338
-
339
- // Copy external package (OSINT)
340
- if (pack.externalPackage) {
341
- const extSrc = path.join(__dirname, '..', '..', '..', pack.externalPackage, 'skills');
342
- const extDest = path.join(targetAgentsDir);
343
- if (fs.existsSync(extSrc)) {
344
- fsExtra.copySync(extSrc, extDest, { overwrite: true });
345
- copiedSkills++;
346
- }
347
- }
348
-
349
- // Copy pack directory (SEO, Backup, Animated Website, Shield)
350
- if (pack.packDir) {
351
- const srcParent = pack.packSrcDir || 'agents';
352
- const packSrc = path.join(bmadSrc, srcParent, pack.packDir);
353
- const packDest = path.join(targetAgentsDir, pack.packDir);
354
- if (fs.existsSync(packSrc)) {
355
- fsExtra.copySync(packSrc, packDest, { overwrite: true });
356
- copiedAgents++;
357
- copiedFiles++;
358
- }
359
- }
242
+ const result = copyPackFiles({
243
+ bmadSrc,
244
+ targetAgentsDir,
245
+ targetDataDir,
246
+ projectRoot,
247
+ pack,
248
+ });
249
+ copiedAgents += result.copiedAgents;
250
+ copiedSkills += result.copiedSkills;
251
+ copiedFiles += result.copiedFiles;
360
252
 
361
253
  // Memory pack: initialize brain with existing brain detection
362
254
  if (packId === 'memory' && pack.packDir) {
363
- const memoryDir = path.join(projectDir, '.agents', 'memory');
364
- const sessionsDir = path.join(memoryDir, 'sessions');
365
- const globalBrainDir = path.join(os.homedir(), '.bmad-plus', 'brain', 'projects');
366
- const templateDir = path.join(bmadSrc, 'packs', 'pack-memory', 'templates');
367
-
368
- // Create project memory (never overwrite existing)
369
- fsExtra.ensureDirSync(sessionsDir);
370
- const memoryFiles = ['decisions.md', 'lessons.md', 'patterns.md', 'context.md'];
371
- for (const mf of memoryFiles) {
372
- const dest = path.join(memoryDir, mf);
373
- if (!fs.existsSync(dest)) {
374
- const src = path.join(templateDir, mf);
375
- if (fs.existsSync(src)) {
376
- let content = fs.readFileSync(src, 'utf8');
377
- content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
378
- content = content.replace(/\{\{project_name\}\}/g, path.basename(projectDir));
379
- content = content.replace(/\{\{project_path\}\}/g, projectDir);
380
- fs.writeFileSync(dest, content, 'utf8');
381
- }
382
- }
383
- }
384
-
385
- // Detect existing brain directories
386
- const brainCandidates = [
387
- path.join(os.homedir(), '.bmad-plus', 'brain'),
388
- path.join(projectDir, '_brain'),
389
- path.join(os.homedir(), '.claude', 'memory'),
390
- ];
391
- const existingBrain = brainCandidates.find(p => fs.existsSync(p));
392
-
393
- if (existingBrain) {
394
- clack.log.info(`🧠 ${i.brain_detected || 'Existing brain detected'}: ${existingBrain}`);
395
- // Write brain link pointer
396
- fs.writeFileSync(
397
- path.join(memoryDir, '.brain-link'),
398
- JSON.stringify({ linked_brain: existingBrain, linked_at: new Date().toISOString() }, null, 2),
399
- 'utf8'
400
- );
401
- } else {
402
- // Create fresh global brain
403
- fsExtra.ensureDirSync(globalBrainDir);
404
- const identitySrc = path.join(templateDir, 'identity.yaml');
405
- const identityDest = path.join(os.homedir(), '.bmad-plus', 'brain', 'identity.yaml');
406
- if (fs.existsSync(identitySrc) && !fs.existsSync(identityDest)) {
407
- let content = fs.readFileSync(identitySrc, 'utf8');
408
- content = content.replace(/\{\{user_name\}\}/g, userName);
409
- content = content.replace(/\{\{language\}\}/g, commLang);
410
- content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
411
- fs.writeFileSync(identityDest, content, 'utf8');
412
- }
413
- // Copy global memory templates
414
- for (const gf of ['decisions.md', 'lessons.md', 'patterns.md']) {
415
- const dest = path.join(os.homedir(), '.bmad-plus', 'brain', gf);
416
- if (!fs.existsSync(dest)) {
417
- const src = path.join(templateDir, gf);
418
- if (fs.existsSync(src)) {
419
- let content = fs.readFileSync(src, 'utf8');
420
- content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
421
- content = content.replace(/\{\{project_name\}\}/g, 'Global Brain');
422
- fs.writeFileSync(dest, content, 'utf8');
423
- }
424
- }
425
- }
426
- clack.log.info(`🧠 ${i.brain_created || 'Global brain created'}: ${path.join(os.homedir(), '.bmad-plus', 'brain')}`);
427
- }
428
-
429
- // Index this project in global brain
430
- const crypto = require('node:crypto');
431
- const projHash = crypto.createHash('sha256').update(projectDir).digest('hex').slice(0, 8);
432
- const projMeta = {
433
- path: projectDir,
434
- name: path.basename(projectDir),
435
- hash: projHash,
436
- status: 'active',
437
- bmad_installed: true,
438
- packs_installed: selectedPacks,
439
- last_scanned: new Date().toISOString().slice(0, 10),
440
- };
441
- fsExtra.ensureDirSync(globalBrainDir);
442
- fs.writeFileSync(
443
- path.join(globalBrainDir, `${projHash}.yaml`),
444
- Object.entries(projMeta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
445
- 'utf8'
446
- );
255
+ initMemory({ projectDir, bmadSrc, userName, commLang, selectedPacks });
447
256
  }
448
257
  }
449
258
 
@@ -143,6 +143,31 @@ function scanDirectory(rootDir, maxDepth = 4, currentDepth = 0, activeDays = 30,
143
143
  return projects;
144
144
  }
145
145
 
146
+ /**
147
+ * Index a single project in the global brain by writing its metadata YAML file.
148
+ * @param {object} project - Project metadata object
149
+ * @param {string} globalBrainDir - Path to the brain projects directory
150
+ * @returns {void}
151
+ */
152
+ function indexProject(project, globalBrainDir) {
153
+ const hash = crypto.createHash('sha256').update(project.path).digest('hex').slice(0, 8);
154
+ const meta = {
155
+ path: project.path,
156
+ name: project.name,
157
+ hash,
158
+ stack: project.stack,
159
+ status: project.status,
160
+ bmad_installed: project.bmad,
161
+ has_git: project.hasGit,
162
+ last_scanned: new Date().toISOString().slice(0, 10),
163
+ };
164
+ fs.writeFileSync(
165
+ path.join(globalBrainDir, `${hash}.yaml`),
166
+ Object.entries(meta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
167
+ 'utf8'
168
+ );
169
+ }
170
+
146
171
  module.exports = {
147
172
  command: 'scan',
148
173
  description: 'Scan directories to discover and index projects in the global brain',
@@ -155,9 +180,15 @@ module.exports = {
155
180
  ],
156
181
  action: async (options) => {
157
182
  const scanDir = path.resolve(options.directory || process.cwd());
158
- const maxDepth = parseInt(options.depth) || 4;
159
- const activeDays = parseInt(options.activeDays) || 30;
160
- const pausedDays = parseInt(options.pausedDays) || 180;
183
+
184
+ const rawDepth = parseInt(options.depth, 10);
185
+ const maxDepth = (!isNaN(rawDepth) && rawDepth > 0) ? rawDepth : (() => { clack.log.warn(`Invalid --depth "${options.depth}", defaulting to 4`); return 4; })();
186
+
187
+ const rawActive = parseInt(options.activeDays, 10);
188
+ const activeDays = (!isNaN(rawActive) && rawActive > 0) ? rawActive : (() => { clack.log.warn(`Invalid --active-days "${options.activeDays}", defaulting to 30`); return 30; })();
189
+
190
+ const rawPaused = parseInt(options.pausedDays, 10);
191
+ const pausedDays = (!isNaN(rawPaused) && rawPaused > 0) ? rawPaused : (() => { clack.log.warn(`Invalid --paused-days "${options.pausedDays}", defaulting to 180`); return 180; })();
161
192
 
162
193
  clack.intro(pc.bgMagenta(pc.white(' 🧠 BMAD+ Project Scanner ')));
163
194
 
@@ -220,22 +251,7 @@ module.exports = {
220
251
 
221
252
  let indexed = 0;
222
253
  for (const proj of projects) {
223
- const hash = crypto.createHash('sha256').update(proj.path).digest('hex').slice(0, 8);
224
- const meta = {
225
- path: proj.path,
226
- name: proj.name,
227
- hash,
228
- stack: proj.stack,
229
- status: proj.status,
230
- bmad_installed: proj.bmad,
231
- has_git: proj.hasGit,
232
- last_scanned: new Date().toISOString().slice(0, 10),
233
- };
234
- fs.writeFileSync(
235
- path.join(globalBrainDir, `${hash}.yaml`),
236
- Object.entries(meta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
237
- 'utf8'
238
- );
254
+ indexProject(proj, globalBrainDir);
239
255
  indexed++;
240
256
  }
241
257
  clack.log.success(`✅ ${indexed} project(s) indexed in ${globalBrainDir}`);
@@ -281,22 +297,7 @@ module.exports = {
281
297
 
282
298
  let indexed = 0;
283
299
  for (const proj of toIndex) {
284
- const hash = crypto.createHash('sha256').update(proj.path).digest('hex').slice(0, 8);
285
- const meta = {
286
- path: proj.path,
287
- name: proj.name,
288
- hash,
289
- stack: proj.stack,
290
- status: proj.status,
291
- bmad_installed: proj.bmad,
292
- has_git: proj.hasGit,
293
- last_scanned: new Date().toISOString().slice(0, 10),
294
- };
295
- fs.writeFileSync(
296
- path.join(globalBrainDir, `${hash}.yaml`),
297
- Object.entries(meta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
298
- 'utf8'
299
- );
300
+ indexProject(proj, globalBrainDir);
300
301
  indexed++;
301
302
  }
302
303
 
@@ -356,5 +357,6 @@ module.exports = {
356
357
  getProjectName,
357
358
  hasBmadInstalled,
358
359
  scanDirectory,
360
+ indexProject,
359
361
  },
360
362
  };
@@ -11,29 +11,10 @@ const fsExtra = require('fs-extra');
11
11
  const clack = require('@clack/prompts');
12
12
  const pc = require('picocolors');
13
13
  const { t } = require('../i18n');
14
+ const { PACKS } = require('../lib/packs');
15
+ const { copyPackFiles } = require('../lib/pack-copy');
14
16
 
15
- // Same pack definitions as install.js keep in sync
16
- const PACKS = {
17
- core: {
18
- agents: ['agent-strategist', 'agent-architect-dev', 'agent-quality', 'agent-orchestrator'],
19
- skills: ['bmad-plus-autopilot', 'bmad-plus-parallel', 'bmad-plus-sync'],
20
- data: ['role-triggers.yaml'],
21
- },
22
- osint: {
23
- agents: ['agent-shadow'],
24
- skills: [],
25
- externalPackage: 'osint-agent-package',
26
- },
27
- maker: {
28
- agents: ['agent-maker'],
29
- skills: [],
30
- data: [],
31
- },
32
- seo: { agents: [], skills: [], packDir: 'pack-seo' },
33
- backup: { agents: [], skills: [], packDir: 'pack-backup' },
34
- animated: { agents: [], skills: [], packDir: 'pack-animated' },
35
- shield: { agents: [], skills: [], packDir: 'pack-shield', packSrcDir: 'packs' },
36
- };
17
+ // Pack definitions imported from shared module: require('../lib/packs').PACKS
37
18
 
38
19
  module.exports = {
39
20
  command: 'update',
@@ -92,59 +73,20 @@ module.exports = {
92
73
 
93
74
  let updated = 0;
94
75
 
76
+ const projectRoot = path.join(bmadSrc, '..', '..');
77
+
95
78
  for (const packId of selectedPacks) {
96
79
  const pack = PACKS[packId];
97
80
  if (!pack) continue;
98
81
 
99
- // Update agents
100
- for (const agent of (pack.agents || [])) {
101
- const src = path.join(bmadSrc, 'agents', agent);
102
- const dest = path.join(targetAgentsDir, agent);
103
- if (fs.existsSync(src)) {
104
- fsExtra.copySync(src, dest, { overwrite: true });
105
- updated++;
106
- }
107
- }
108
-
109
- // Update skills
110
- for (const skill of (pack.skills || [])) {
111
- const src = path.join(bmadSrc, 'skills', skill);
112
- const dest = path.join(targetAgentsDir, skill);
113
- if (fs.existsSync(src)) {
114
- fsExtra.copySync(src, dest, { overwrite: true });
115
- updated++;
116
- }
117
- }
118
-
119
- // Update data files
120
- for (const dataFile of (pack.data || [])) {
121
- const src = path.join(bmadSrc, 'data', dataFile);
122
- const dest = path.join(targetDataDir, dataFile);
123
- if (fs.existsSync(src)) {
124
- fsExtra.copySync(src, dest, { overwrite: true });
125
- updated++;
126
- }
127
- }
128
-
129
- // Update external package (OSINT)
130
- if (pack.externalPackage) {
131
- const extSrc = path.join(__dirname, '..', '..', '..', pack.externalPackage, 'skills');
132
- if (fs.existsSync(extSrc)) {
133
- fsExtra.copySync(extSrc, targetAgentsDir, { overwrite: true });
134
- updated++;
135
- }
136
- }
137
-
138
- // Update pack directory (SEO, Backup, Animated, Shield)
139
- if (pack.packDir) {
140
- const srcParent = pack.packSrcDir || 'agents';
141
- const packSrc = path.join(bmadSrc, srcParent, pack.packDir);
142
- const packDest = path.join(targetAgentsDir, pack.packDir);
143
- if (fs.existsSync(packSrc)) {
144
- fsExtra.copySync(packSrc, packDest, { overwrite: true });
145
- updated++;
146
- }
147
- }
82
+ const result = copyPackFiles({
83
+ bmadSrc,
84
+ targetAgentsDir,
85
+ targetDataDir,
86
+ projectRoot,
87
+ pack,
88
+ });
89
+ updated += result.copiedAgents + result.copiedSkills + result.copiedFiles;
148
90
  }
149
91
 
150
92
  // Update module config (always)