bmad-plus 0.16.0 → 0.17.1
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.
- package/CHANGELOG.md +114 -29
- package/README.md +45 -27
- package/SECURITY.md +9 -1
- package/osint-agent-package/README.md +13 -0
- package/osint-agent-package/SETUP_KEYS.md +13 -0
- package/package.json +32 -8
- package/readme-international/README.de.md +134 -537
- package/readme-international/README.es.md +136 -556
- package/readme-international/README.fr.md +135 -553
- package/src/bmad-plus/agents/agent-shadow/SKILL.md +6 -0
- package/src/bmad-plus/packs/pack-memory/README.md +1 -1
- package/src/bmad-plus/packs/pack-memory/memory-orchestrator.md +1 -1
- package/src/bmad-plus/packs/pack-memory/shared/memory-protocol.md +1 -1
- package/src/bmad-plus/packs/pack-shield/README.md +2 -0
- package/src/bmad-plus/packs/pack-shield/SKILL.md +2 -0
- package/src/bmad-plus/packs/pack-shield/shared/audit-report-template.md +6 -0
- package/src/bmad-plus/packs/pack-shield/shield-orchestrator.md +4 -0
- package/src/bmad-plus/skills/bmad-plus-uat/SKILL.md +18 -4
- package/src/bmad-plus/skills/bmad-plus-uat/template/page.html +95 -19
- package/src/bmad-plus/skills/bmad-plus-uat/template/strings.json +90 -10
- package/src/bmad-plus/skills/bmad-plus-uat/templates/README.md +63 -0
- package/src/bmad-plus/skills/bmad-plus-uat/templates/example-uat-spec.json +95 -0
- package/src/bmad-plus/skills/bmad-plus-uat/templates/uat-release-gate.mjs +127 -0
- package/src/bmad-plus/skills/bmad-plus-uat/templates/uat-spec-present.mjs +77 -0
- package/tools/build/adapters.config.js +1 -1
- package/tools/build/generate-adapters.js +186 -60
- package/tools/build/generate.js +49 -13
- package/tools/cli/bmad-plus-cli.js +3 -6
- package/tools/cli/commands/autoconfig.js +66 -24
- package/tools/cli/commands/install.js +620 -356
- package/tools/cli/commands/memory.js +40 -13
- package/tools/cli/commands/scan.js +94 -30
- package/tools/cli/commands/uat.js +21 -5
- package/tools/cli/commands/uninstall.js +65 -16
- package/tools/cli/commands/update-check.js +8 -3
- package/tools/cli/commands/update-policy.js +14 -5
- package/tools/cli/commands/update.js +89 -20
- package/tools/cli/i18n.js +128 -66
- package/tools/cli/lib/ide-config.js +3 -1
- package/tools/cli/lib/install-manifest.js +13 -6
- package/tools/cli/lib/installation-health.js +33 -1
- package/tools/cli/lib/installed-adapters.js +29 -6
- package/tools/cli/lib/memory-init.js +19 -4
- package/tools/cli/lib/packs.js +17 -28
- package/tools/cli/lib/path-hash.js +5 -1
- package/tools/cli/lib/python-provision.js +51 -12
- package/tools/cli/lib/stack-detect.js +51 -25
- package/tools/cli/lib/uat.js +18 -0
- package/tools/cli/lib/update-check.js +132 -34
- package/tools/cli/lib/update-dispatch.js +2 -2
- package/tools/cli/lib/update-policy.js +57 -19
- package/tools/cli/lib/update-transaction.js +439 -85
- package/tools/build/README.md +0 -78
- package/tools/build/check-counts.js +0 -626
- package/tools/build/check-install-contract.js +0 -648
- package/tools/build/generated-adapters/.codex/AGENTS.md +0 -134
- package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +0 -139
- package/tools/build/generated-adapters/.opencode/AGENTS.md +0 -134
- package/tools/build/generated-adapters/AGENTS.md +0 -132
- package/tools/build/generated-adapters/CLAUDE.md +0 -135
- package/tools/build/generated-adapters/CONVENTIONS.md +0 -134
- package/tools/build/generated-adapters/GEMINI.md +0 -139
- package/tools/build/generated-adapters/README.md +0 -79
- package/tools/maintain/upstream-candidate.js +0 -456
- package/tools/release/publication-content.js +0 -903
- package/tools/release/supply-chain.js +0 -282
|
@@ -43,7 +43,12 @@
|
|
|
43
43
|
const fs = require('node:fs');
|
|
44
44
|
const path = require('node:path');
|
|
45
45
|
const { loadRegistry, buildDerived, DEFAULT_REGISTRY_PATH } = require('./generate');
|
|
46
|
-
const {
|
|
46
|
+
const {
|
|
47
|
+
PROJECT_INSTRUCTIONS,
|
|
48
|
+
MEMORY_INSTRUCTIONS,
|
|
49
|
+
UPDATE_INSTRUCTIONS,
|
|
50
|
+
TOOL_METADATA,
|
|
51
|
+
} = require('./adapters.config');
|
|
47
52
|
|
|
48
53
|
const DEFAULT_OUT_DIR = path.join(__dirname, 'generated-adapters');
|
|
49
54
|
const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
@@ -108,7 +113,13 @@ function validateTargets(registry) {
|
|
|
108
113
|
throw new Error('registry.yaml: targets.adapters must be a non-empty list');
|
|
109
114
|
}
|
|
110
115
|
t.adapters.forEach((a, i) => {
|
|
111
|
-
if (
|
|
116
|
+
if (
|
|
117
|
+
!a ||
|
|
118
|
+
typeof a.tool !== 'string' ||
|
|
119
|
+
a.tool === '' ||
|
|
120
|
+
typeof a.file !== 'string' ||
|
|
121
|
+
a.file === ''
|
|
122
|
+
) {
|
|
112
123
|
throw new Error(`registry.yaml: targets.adapters[${i}] must have string "tool" and "file"`);
|
|
113
124
|
}
|
|
114
125
|
validateRelativeFile(a.file);
|
|
@@ -118,13 +129,19 @@ function validateTargets(registry) {
|
|
|
118
129
|
|
|
119
130
|
/** Convert the canonical derivation into the adapter presentation shape. */
|
|
120
131
|
function factsFromDerived(derived, packs = derived.packOrder) {
|
|
121
|
-
const selected = packs.map(id => {
|
|
132
|
+
const selected = packs.map((id) => {
|
|
122
133
|
const pack = derived.packs[id];
|
|
123
134
|
return {
|
|
124
|
-
id,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
135
|
+
id,
|
|
136
|
+
name: pack.name,
|
|
137
|
+
displayName: pack.displayName,
|
|
138
|
+
desc: pack.desc,
|
|
139
|
+
required: pack.required,
|
|
140
|
+
agentCount: pack.installerAgentCount,
|
|
141
|
+
categoryAgentCount: pack.categoryAgentCount,
|
|
142
|
+
subAgentCount: pack.subAgentCount,
|
|
143
|
+
workflowCount: pack.workflowCount,
|
|
144
|
+
frameworkCount: pack.frameworkCount,
|
|
128
145
|
personas: pack.personas,
|
|
129
146
|
};
|
|
130
147
|
});
|
|
@@ -133,9 +150,10 @@ function factsFromDerived(derived, packs = derived.packOrder) {
|
|
|
133
150
|
spine: derived.targets.spine,
|
|
134
151
|
modelsSupported: derived.targets.models_supported,
|
|
135
152
|
packCount: selected.length,
|
|
136
|
-
installerAgentCount:
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
installerAgentCount:
|
|
154
|
+
packs.length === derived.packOrder.length
|
|
155
|
+
? derived.installerAgents
|
|
156
|
+
: selected.reduce((total, pack) => total + pack.agentCount, 0),
|
|
139
157
|
packs: selected,
|
|
140
158
|
};
|
|
141
159
|
}
|
|
@@ -153,8 +171,15 @@ function renderRoster(facts) {
|
|
|
153
171
|
const identity = persona.alias
|
|
154
172
|
? '(' + persona.alias + ') — ' + persona.role
|
|
155
173
|
: '(' + persona.role + ')';
|
|
156
|
-
lines.push(
|
|
157
|
-
|
|
174
|
+
lines.push(
|
|
175
|
+
'- **' +
|
|
176
|
+
persona.name +
|
|
177
|
+
'** ' +
|
|
178
|
+
identity +
|
|
179
|
+
' — ' +
|
|
180
|
+
persona.description +
|
|
181
|
+
(pack.required ? '' : ' (' + pack.name + ')')
|
|
182
|
+
);
|
|
158
183
|
}
|
|
159
184
|
}
|
|
160
185
|
return lines;
|
|
@@ -346,40 +371,66 @@ function generateAllFiles(registry) {
|
|
|
346
371
|
/** Installable tools and paths come from the registry; only detection is local. */
|
|
347
372
|
function buildIDEConfigs(derived) {
|
|
348
373
|
validateTargets({ targets: derived.targets });
|
|
349
|
-
return Object.fromEntries(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
374
|
+
return Object.fromEntries(
|
|
375
|
+
derived.targets.adapters.map(({ tool, file }) => [
|
|
376
|
+
tool,
|
|
377
|
+
{
|
|
378
|
+
name: TOOL_METADATA[tool]?.name || tool,
|
|
379
|
+
configFile: file,
|
|
380
|
+
detect: [...(TOOL_METADATA[tool]?.detect || [])],
|
|
381
|
+
},
|
|
382
|
+
])
|
|
383
|
+
);
|
|
353
384
|
}
|
|
354
385
|
|
|
355
386
|
function selectedPacks(derived, packs) {
|
|
356
|
-
if (packs !== undefined && !Array.isArray(packs))
|
|
387
|
+
if (packs !== undefined && !Array.isArray(packs))
|
|
388
|
+
throw new Error('packs must be an array of pack IDs');
|
|
357
389
|
const selected = new Set(packs === undefined ? derived.packOrder : packs);
|
|
358
390
|
for (const id of selected) {
|
|
359
391
|
if (!Object.hasOwn(derived.packs, id)) throw new Error('Unknown pack: ' + id);
|
|
360
392
|
}
|
|
361
393
|
for (const id of derived.packOrder) if (derived.packs[id].required) selected.add(id);
|
|
362
|
-
return derived.packOrder.filter(id => selected.has(id));
|
|
394
|
+
return derived.packOrder.filter((id) => selected.has(id));
|
|
363
395
|
}
|
|
364
396
|
|
|
365
397
|
function renderUserSections(derived, { packs, userName = 'Developer', language = 'English' } = {}) {
|
|
366
398
|
const ids = selectedPacks(derived, packs);
|
|
367
399
|
const facts = factsFromDerived(derived, ids);
|
|
368
400
|
const lines = [
|
|
369
|
-
'## Project Context',
|
|
370
|
-
'
|
|
371
|
-
'
|
|
372
|
-
|
|
373
|
-
|
|
401
|
+
'## Project Context',
|
|
402
|
+
'',
|
|
403
|
+
'This project uses ' +
|
|
404
|
+
facts.product.displayName +
|
|
405
|
+
', an augmented AI-driven development framework.',
|
|
406
|
+
'Based on ' +
|
|
407
|
+
facts.product.derivedFrom +
|
|
408
|
+
' with multi-role agents, autopilot mode, and parallel execution.',
|
|
409
|
+
'',
|
|
410
|
+
...renderRoster(facts),
|
|
411
|
+
'',
|
|
412
|
+
'## Skills',
|
|
413
|
+
'',
|
|
374
414
|
'- Load skills from `.agents/skills/`.',
|
|
375
415
|
'- Each agent has a SKILL.md with capabilities, activation protocol, and role-switching rules.',
|
|
376
416
|
'- Auto-activation triggers: `.agents/data/role-triggers.yaml`.',
|
|
377
|
-
'',
|
|
417
|
+
'',
|
|
418
|
+
'## Communication',
|
|
419
|
+
'',
|
|
378
420
|
'- User name: ' + String(userName).replace(/[\r\n]/g, ' '),
|
|
379
|
-
'- Default language: ' +
|
|
421
|
+
'- Default language: ' +
|
|
422
|
+
String(language).replace(/[\r\n]/g, ' ') +
|
|
423
|
+
' for user-facing content, English for code and technical docs.',
|
|
380
424
|
];
|
|
381
425
|
if (ids.includes('memory')) lines.push('', ...MEMORY_INSTRUCTIONS);
|
|
382
|
-
lines.push(
|
|
426
|
+
lines.push(
|
|
427
|
+
'',
|
|
428
|
+
...UPDATE_INSTRUCTIONS,
|
|
429
|
+
'',
|
|
430
|
+
...renderFactsSection(facts),
|
|
431
|
+
'',
|
|
432
|
+
...renderKeyCommands()
|
|
433
|
+
);
|
|
383
434
|
return lines;
|
|
384
435
|
}
|
|
385
436
|
|
|
@@ -395,36 +446,61 @@ function userMarkerLines() {
|
|
|
395
446
|
function renderUserConfig(derived, options = {}) {
|
|
396
447
|
validateTargets({ targets: derived.targets });
|
|
397
448
|
return [
|
|
398
|
-
...userMarkerLines(),
|
|
399
|
-
'
|
|
400
|
-
'
|
|
401
|
-
|
|
449
|
+
...userMarkerLines(),
|
|
450
|
+
'',
|
|
451
|
+
'# ' + derived.product.displayName + ' — Agent Spine',
|
|
452
|
+
'',
|
|
453
|
+
'This file is the common project instruction spine for the installed ' +
|
|
454
|
+
derived.product.displayName +
|
|
455
|
+
' tools.',
|
|
456
|
+
'',
|
|
457
|
+
...renderUserSections(derived, options),
|
|
458
|
+
'',
|
|
402
459
|
].join('\n');
|
|
403
460
|
}
|
|
404
461
|
|
|
405
462
|
/** Pure render of the common spine and every selected registry adapter. */
|
|
406
463
|
function generateUserFiles(derived, options = {}) {
|
|
407
464
|
validateTargets({ targets: derived.targets });
|
|
408
|
-
const available = derived.targets.adapters.map(adapter => adapter.tool);
|
|
465
|
+
const available = derived.targets.adapters.map((adapter) => adapter.tool);
|
|
409
466
|
const requested = options.tools === undefined ? available : options.tools;
|
|
410
467
|
if (!Array.isArray(requested)) throw new Error('tools must be an array of tool IDs');
|
|
411
468
|
for (const tool of requested) {
|
|
412
469
|
if (!available.includes(tool)) throw new Error('Unknown tool: ' + tool);
|
|
413
470
|
}
|
|
414
|
-
const targets = {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
471
|
+
const targets = {
|
|
472
|
+
...derived.targets,
|
|
473
|
+
adapters: derived.targets.adapters.filter((adapter) => requested.includes(adapter.tool)),
|
|
474
|
+
};
|
|
475
|
+
const files = [
|
|
476
|
+
{
|
|
477
|
+
file: targets.spine,
|
|
478
|
+
tools: ['spine'],
|
|
479
|
+
spine: true,
|
|
480
|
+
content: renderUserConfig(derived, options),
|
|
481
|
+
},
|
|
482
|
+
];
|
|
418
483
|
for (const { file, tools } of groupAdaptersByFile({ targets })) {
|
|
419
484
|
if (file === targets.spine) continue;
|
|
420
485
|
const reference = path.posix.relative(path.posix.dirname(file), targets.spine);
|
|
421
486
|
const lines = [];
|
|
422
|
-
if (file.endsWith('.mdc'))
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
487
|
+
if (file.endsWith('.mdc'))
|
|
488
|
+
lines.push(
|
|
489
|
+
'---',
|
|
490
|
+
'description: "' + derived.product.displayName + ' agents adapter"',
|
|
491
|
+
'alwaysApply: true',
|
|
492
|
+
'---',
|
|
493
|
+
''
|
|
494
|
+
);
|
|
495
|
+
lines.push(
|
|
496
|
+
...userMarkerLines(),
|
|
497
|
+
'',
|
|
498
|
+
'# ' + derived.product.displayName + ' — Adapter for ' + tools.join(' + '),
|
|
499
|
+
'',
|
|
426
500
|
'Read the project spine first: [' + targets.spine + '](' + reference + ').',
|
|
427
|
-
'Paths in the shared project instructions below are relative to the project root.',
|
|
501
|
+
'Paths in the shared project instructions below are relative to the project root.',
|
|
502
|
+
''
|
|
503
|
+
);
|
|
428
504
|
for (const tool of tools) {
|
|
429
505
|
lines.push('## Tool notes — ' + tool, '');
|
|
430
506
|
for (const note of TOOL_NOTES[tool] || buildDefaultNotes(tool, file)) lines.push('- ' + note);
|
|
@@ -438,8 +514,14 @@ function generateUserFiles(derived, options = {}) {
|
|
|
438
514
|
|
|
439
515
|
/** Refuse portable-path escapes even when generated input has been modified. */
|
|
440
516
|
function validateRelativeFile(file) {
|
|
441
|
-
if (
|
|
442
|
-
|
|
517
|
+
if (
|
|
518
|
+
typeof file !== 'string' ||
|
|
519
|
+
!file ||
|
|
520
|
+
/[\\:]/.test(file) ||
|
|
521
|
+
file.includes('\0') ||
|
|
522
|
+
path.posix.isAbsolute(file) ||
|
|
523
|
+
file.split('/').some((part) => !part || part === '.' || part === '..')
|
|
524
|
+
) {
|
|
443
525
|
throw new Error('Adapter file must be a relative project path: ' + file);
|
|
444
526
|
}
|
|
445
527
|
}
|
|
@@ -450,20 +532,30 @@ function safeTarget(outDir, file) {
|
|
|
450
532
|
const root = path.resolve(outDir);
|
|
451
533
|
const target = path.resolve(root, file);
|
|
452
534
|
const relative = path.relative(root, target);
|
|
453
|
-
if (
|
|
535
|
+
if (
|
|
536
|
+
!relative ||
|
|
537
|
+
relative === '..' ||
|
|
538
|
+
relative.startsWith('..' + path.sep) ||
|
|
539
|
+
path.isAbsolute(relative)
|
|
540
|
+
) {
|
|
454
541
|
throw new Error('Adapter path escapes the target project: ' + file);
|
|
455
542
|
}
|
|
456
543
|
let current = path.parse(target).root;
|
|
457
544
|
for (const part of target.slice(current.length).split(path.sep)) {
|
|
458
545
|
current = path.join(current, part);
|
|
459
546
|
let stat;
|
|
460
|
-
try {
|
|
547
|
+
try {
|
|
548
|
+
stat = fs.lstatSync(current);
|
|
549
|
+
} catch (error) {
|
|
461
550
|
if (error.code === 'ENOENT') break;
|
|
462
551
|
throw error;
|
|
463
552
|
}
|
|
464
|
-
if (stat.isSymbolicLink())
|
|
465
|
-
|
|
466
|
-
if (current
|
|
553
|
+
if (stat.isSymbolicLink())
|
|
554
|
+
throw new Error('Adapter path contains a symlink or junction: ' + current);
|
|
555
|
+
if (current === target && !stat.isFile())
|
|
556
|
+
throw new Error('Adapter target is not a regular file: ' + current);
|
|
557
|
+
if (current !== target && !stat.isDirectory())
|
|
558
|
+
throw new Error('Adapter parent is not a directory: ' + current);
|
|
467
559
|
}
|
|
468
560
|
return target;
|
|
469
561
|
}
|
|
@@ -480,14 +572,24 @@ function writeFiles(outDir, files) {
|
|
|
480
572
|
});
|
|
481
573
|
}
|
|
482
574
|
|
|
483
|
-
function writeUserAdapters({
|
|
484
|
-
|
|
575
|
+
function writeUserAdapters({
|
|
576
|
+
outDir,
|
|
577
|
+
derived = require('../cli/lib/packs').DERIVED,
|
|
578
|
+
packs,
|
|
579
|
+
userName,
|
|
580
|
+
language,
|
|
581
|
+
tools,
|
|
582
|
+
force = false,
|
|
583
|
+
} = {}) {
|
|
485
584
|
if (!outDir) throw new Error('A target project directory is required');
|
|
486
585
|
const files = generateUserFiles(derived, { packs, userName, language, tools });
|
|
487
586
|
const blockers = listAdoptionBlockers(outDir, files, USER_CONFIG_MARKER);
|
|
488
587
|
if (blockers.length && !force) {
|
|
489
|
-
throw new Error(
|
|
490
|
-
|
|
588
|
+
throw new Error(
|
|
589
|
+
'REFUSING to overwrite files not owned by this user configuration: ' +
|
|
590
|
+
blockers.join(', ') +
|
|
591
|
+
'. Review them and use --force to replace them explicitly.'
|
|
592
|
+
);
|
|
491
593
|
}
|
|
492
594
|
return writeFiles(outDir, files);
|
|
493
595
|
}
|
|
@@ -546,13 +648,17 @@ function parseOutDir(args) {
|
|
|
546
648
|
const idx = args.indexOf('--out-dir');
|
|
547
649
|
const targetIdx = args.indexOf('--target');
|
|
548
650
|
if (targetIdx !== -1) {
|
|
549
|
-
if (args.includes('--adopt') || idx !== -1)
|
|
651
|
+
if (args.includes('--adopt') || idx !== -1)
|
|
652
|
+
throw new Error('--target, --adopt and --out-dir are mutually exclusive');
|
|
550
653
|
const value = args[targetIdx + 1];
|
|
551
654
|
if (!value || value.startsWith('--')) throw new Error('--target requires a directory path');
|
|
552
655
|
return path.resolve(value);
|
|
553
656
|
}
|
|
554
657
|
if (args.includes('--adopt')) {
|
|
555
|
-
if (idx !== -1)
|
|
658
|
+
if (idx !== -1)
|
|
659
|
+
throw new Error(
|
|
660
|
+
'--adopt and --out-dir are mutually exclusive (--adopt targets the repo root)'
|
|
661
|
+
);
|
|
556
662
|
return REPO_ROOT;
|
|
557
663
|
}
|
|
558
664
|
if (idx === -1) return DEFAULT_OUT_DIR;
|
|
@@ -581,9 +687,18 @@ function main(argv) {
|
|
|
581
687
|
};
|
|
582
688
|
const packs = option('--packs');
|
|
583
689
|
const tools = option('--tools');
|
|
584
|
-
const options = {
|
|
585
|
-
|
|
586
|
-
|
|
690
|
+
const options = {
|
|
691
|
+
packs:
|
|
692
|
+
!packs || packs === 'all' ? undefined : packs.split(',').map((value) => value.trim()),
|
|
693
|
+
tools:
|
|
694
|
+
!tools || tools === 'all'
|
|
695
|
+
? undefined
|
|
696
|
+
: tools === 'none'
|
|
697
|
+
? []
|
|
698
|
+
: tools.split(',').map((value) => value.trim()),
|
|
699
|
+
userName: option('--user-name'),
|
|
700
|
+
language: option('--language'),
|
|
701
|
+
};
|
|
587
702
|
const derived = require('../cli/lib/packs').DERIVED;
|
|
588
703
|
const files = generateUserFiles(derived, options);
|
|
589
704
|
if (args.includes('--print')) {
|
|
@@ -593,12 +708,21 @@ function main(argv) {
|
|
|
593
708
|
if (args.includes('--check')) {
|
|
594
709
|
const mismatches = files.filter(({ file, content }) => {
|
|
595
710
|
const target = safeTarget(outDir, file);
|
|
596
|
-
return
|
|
711
|
+
return (
|
|
712
|
+
!fs.existsSync(target) ||
|
|
713
|
+
normalizeEol(fs.readFileSync(target, 'utf8')) !== normalizeEol(content)
|
|
714
|
+
);
|
|
597
715
|
});
|
|
598
|
-
if (mismatches.length)
|
|
716
|
+
if (mismatches.length)
|
|
717
|
+
console.error('User adapter drift: ' + mismatches.map((entry) => entry.file).join(', '));
|
|
599
718
|
return mismatches.length ? 1 : 0;
|
|
600
719
|
}
|
|
601
|
-
const written = writeUserAdapters({
|
|
720
|
+
const written = writeUserAdapters({
|
|
721
|
+
outDir,
|
|
722
|
+
derived,
|
|
723
|
+
...options,
|
|
724
|
+
force: args.includes('--force'),
|
|
725
|
+
});
|
|
602
726
|
for (const target of written) console.log('Generated ' + target);
|
|
603
727
|
return 0;
|
|
604
728
|
} catch (error) {
|
|
@@ -658,7 +782,9 @@ module.exports = {
|
|
|
658
782
|
GENERATED_MARKER,
|
|
659
783
|
USER_CONFIG_MARKER,
|
|
660
784
|
buildIDEConfigs,
|
|
661
|
-
get IDE_CONFIGS() {
|
|
785
|
+
get IDE_CONFIGS() {
|
|
786
|
+
return buildIDEConfigs(require('../cli/lib/packs').DERIVED);
|
|
787
|
+
},
|
|
662
788
|
renderUserConfig,
|
|
663
789
|
generateUserFiles,
|
|
664
790
|
writeUserAdapters,
|
package/tools/build/generate.js
CHANGED
|
@@ -58,8 +58,8 @@ function assertResourcePath(file, where) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** Installed entry points, derived from the same layout used by pack-copy.js. */
|
|
61
|
-
function buildRequiredResources(pack) {
|
|
62
|
-
const where = `registry.yaml: pack "${
|
|
61
|
+
function buildRequiredResources(pack, id = pack.pack_dir) {
|
|
62
|
+
const where = `registry.yaml: pack "${id}"`;
|
|
63
63
|
for (const file of [
|
|
64
64
|
...pack.agents,
|
|
65
65
|
...(pack.skills || []),
|
|
@@ -135,12 +135,15 @@ function validateRegistry(registry) {
|
|
|
135
135
|
if (pack.skills !== undefined && !Array.isArray(pack.skills)) {
|
|
136
136
|
throw new Error(`${where}: "skills" must be a list`);
|
|
137
137
|
}
|
|
138
|
-
if (typeof pack.pack_dir !== 'string' || pack.pack_dir === '') {
|
|
139
|
-
throw new Error(`${where}: missing "pack_dir"`);
|
|
140
|
-
}
|
|
141
138
|
if (!INSTALL_LAYOUTS.includes(pack.install_layout)) {
|
|
142
139
|
throw new Error(`${where}: "install_layout" must be one of ${INSTALL_LAYOUTS.join('|')}`);
|
|
143
140
|
}
|
|
141
|
+
if (
|
|
142
|
+
(pack.install_layout === 'packaged' || pack.pack_dir !== undefined) &&
|
|
143
|
+
(typeof pack.pack_dir !== 'string' || pack.pack_dir === '')
|
|
144
|
+
) {
|
|
145
|
+
throw new Error(`${where}: missing "pack_dir"`);
|
|
146
|
+
}
|
|
144
147
|
if (pack.install_layout === 'packaged' && !Array.isArray((pack.doctor || {}).pack_agents)) {
|
|
145
148
|
throw new Error(`${where}: packaged layout requires "doctor.pack_agents" list`);
|
|
146
149
|
}
|
|
@@ -157,7 +160,7 @@ function validateRegistry(registry) {
|
|
|
157
160
|
) {
|
|
158
161
|
throw new Error(`${where}: runtime must declare known runtimes`);
|
|
159
162
|
}
|
|
160
|
-
buildRequiredResources(pack);
|
|
163
|
+
buildRequiredResources(pack, id);
|
|
161
164
|
if (pack.personas !== undefined) {
|
|
162
165
|
if (!Array.isArray(pack.personas)) throw new Error(`${where}: personas must be a list`);
|
|
163
166
|
const roster = new Set([...pack.agents, ...(pack.sub_agents || [])]);
|
|
@@ -253,13 +256,16 @@ function buildPacks(registry) {
|
|
|
253
256
|
const entry = {
|
|
254
257
|
name: p.cli.name,
|
|
255
258
|
icon: p.cli.icon,
|
|
256
|
-
agents
|
|
259
|
+
// Only loose packs install agent directories; packaged agents live in packDir.
|
|
260
|
+
agents: p.install_layout === 'loose' ? [...p.agents] : [],
|
|
257
261
|
skills: [...(p.skills || [])],
|
|
258
262
|
};
|
|
259
263
|
if ('data' in p) entry.data = [...p.data];
|
|
260
264
|
if (p.external_package) entry.externalPackage = p.external_package;
|
|
261
|
-
|
|
262
|
-
|
|
265
|
+
if (p.pack_dir) {
|
|
266
|
+
entry.packDir = p.pack_dir;
|
|
267
|
+
entry.packSrcDir = p.pack_src_dir || 'packs';
|
|
268
|
+
}
|
|
263
269
|
if (p.required === true) entry.required = true;
|
|
264
270
|
entry.desc = derived.packs[id].desc;
|
|
265
271
|
packs[id] = entry;
|
|
@@ -313,6 +319,7 @@ function buildDerived(registry, { sourceRoot = path.join(REPO_ROOT, 'src', 'bmad
|
|
|
313
319
|
order: pack.order,
|
|
314
320
|
name: pack.cli.name,
|
|
315
321
|
displayName: pack.display_name || pack.cli.name,
|
|
322
|
+
iconEmoji: pack.icon_emoji || '',
|
|
316
323
|
required: pack.required === true,
|
|
317
324
|
installerAgentCount,
|
|
318
325
|
categoryAgentCount,
|
|
@@ -324,12 +331,14 @@ function buildDerived(registry, { sourceRoot = path.join(REPO_ROOT, 'src', 'bmad
|
|
|
324
331
|
frameworkCount: (pack.compliance_tags || []).length,
|
|
325
332
|
categoryCount: categories.length,
|
|
326
333
|
skillCount: (pack.skills || []).length,
|
|
327
|
-
referenceFiles:
|
|
328
|
-
|
|
329
|
-
|
|
334
|
+
referenceFiles: pack.pack_dir
|
|
335
|
+
? countMarkdownFiles(
|
|
336
|
+
path.join(sourceRoot, pack.pack_src_dir || 'packs', pack.pack_dir, 'references')
|
|
337
|
+
)
|
|
338
|
+
: 0,
|
|
330
339
|
categoryAgentCounts,
|
|
331
340
|
runtimes: [...pack.runtime],
|
|
332
|
-
requiredResources: buildRequiredResources(pack),
|
|
341
|
+
requiredResources: buildRequiredResources(pack, id),
|
|
333
342
|
personas: (pack.personas || []).map(({ id, name, role, description, alias }) => ({
|
|
334
343
|
id,
|
|
335
344
|
name,
|
|
@@ -528,6 +537,31 @@ function describeDiff(label, generated, current, mismatches) {
|
|
|
528
537
|
}
|
|
529
538
|
}
|
|
530
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Every source path the installer copies from (see pack-copy.js listPackFiles)
|
|
542
|
+
* must exist; the copier tolerates a missing path, so the build must not.
|
|
543
|
+
*/
|
|
544
|
+
function checkSourcePaths(packs, packageRoot = REPO_ROOT) {
|
|
545
|
+
const sourceRoot = path.join(packageRoot, 'src', 'bmad-plus');
|
|
546
|
+
const missing = [];
|
|
547
|
+
for (const [id, pack] of Object.entries(packs)) {
|
|
548
|
+
const declared = [
|
|
549
|
+
...pack.agents.map((name) => path.join(sourceRoot, 'agents', name)),
|
|
550
|
+
...pack.skills.map((name) => path.join(sourceRoot, 'skills', name)),
|
|
551
|
+
...(pack.data || []).map((name) => path.join(sourceRoot, 'data', name)),
|
|
552
|
+
...(pack.externalPackage ? [path.join(packageRoot, pack.externalPackage, 'skills')] : []),
|
|
553
|
+
...(pack.packDir ? [path.join(sourceRoot, pack.packSrcDir, pack.packDir)] : []),
|
|
554
|
+
];
|
|
555
|
+
for (const source of declared) {
|
|
556
|
+
if (!fs.existsSync(source)) {
|
|
557
|
+
const relative = path.relative(packageRoot, source).split(path.sep).join('/');
|
|
558
|
+
missing.push(`PACKS.${id}: declared source path does not exist: ${relative}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return missing;
|
|
563
|
+
}
|
|
564
|
+
|
|
531
565
|
/**
|
|
532
566
|
* `--check` mode: prove registry.yaml reproduces the live packs.js exactly,
|
|
533
567
|
* and that product.version matches package.json.
|
|
@@ -546,6 +580,7 @@ function check({
|
|
|
546
580
|
const mismatches = [];
|
|
547
581
|
|
|
548
582
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
583
|
+
mismatches.push(...checkSourcePaths(generated.PACKS));
|
|
549
584
|
const { checkPythonDelivery } = require('./pack-delivery');
|
|
550
585
|
mismatches.push(...checkPythonDelivery(registry, { packageRoot: REPO_ROOT, packageJson: pkg }));
|
|
551
586
|
try {
|
|
@@ -642,6 +677,7 @@ module.exports = {
|
|
|
642
677
|
generateModuleSource,
|
|
643
678
|
generatePacksModuleSource,
|
|
644
679
|
check,
|
|
680
|
+
checkSourcePaths,
|
|
645
681
|
main,
|
|
646
682
|
};
|
|
647
683
|
|
|
@@ -29,10 +29,11 @@ const doctor = require('./commands/doctor');
|
|
|
29
29
|
const scan = require('./commands/scan');
|
|
30
30
|
const memory = require('./commands/memory');
|
|
31
31
|
|
|
32
|
+
// `--lang` is declared per command: a program-level copy would swallow it.
|
|
32
33
|
program
|
|
34
|
+
.name('bmad-plus')
|
|
33
35
|
.version(packageJson.version)
|
|
34
|
-
.description('BMAD+ — Augmented AI-Driven Development Framework')
|
|
35
|
-
.option('-l, --lang <code>', 'Language code: en, fr, es, de, pt-br, ru, zh, he, ja, it');
|
|
36
|
+
.description('BMAD+ — Augmented AI-Driven Development Framework');
|
|
36
37
|
|
|
37
38
|
// Install command
|
|
38
39
|
const installCmd = program
|
|
@@ -131,7 +132,3 @@ program.parseAsync(process.argv).catch((error) => {
|
|
|
131
132
|
console.error(`BMAD+: ${error.message}`);
|
|
132
133
|
process.exitCode = 1;
|
|
133
134
|
});
|
|
134
|
-
|
|
135
|
-
if (process.argv.slice(2).length === 0) {
|
|
136
|
-
program.outputHelp();
|
|
137
|
-
}
|