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
|
@@ -31,8 +31,10 @@ function countEntries(filePath) {
|
|
|
31
31
|
function countSessions(dir) {
|
|
32
32
|
if (!fs.existsSync(dir)) return 0;
|
|
33
33
|
try {
|
|
34
|
-
return fs.readdirSync(dir).filter(f => f.endsWith('.md')).length;
|
|
35
|
-
} catch {
|
|
34
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).length;
|
|
35
|
+
} catch {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
module.exports = {
|
|
@@ -67,11 +69,21 @@ module.exports = {
|
|
|
67
69
|
const sessions = countSessions(path.join(memoryDir, 'sessions'));
|
|
68
70
|
const brainLink = fs.existsSync(path.join(memoryDir, '.brain-link'));
|
|
69
71
|
|
|
70
|
-
clack.log.info(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
clack.log.info(
|
|
74
|
-
|
|
72
|
+
clack.log.info(
|
|
73
|
+
` decisions.md ${decisions.exists ? pc.green('✓') : pc.red('✗')} ${decisions.count} entries ${pc.dim(decisions.lastModified || '')}`
|
|
74
|
+
);
|
|
75
|
+
clack.log.info(
|
|
76
|
+
` lessons.md ${lessons.exists ? pc.green('✓') : pc.red('✗')} ${lessons.count} entries ${pc.dim(lessons.lastModified || '')}`
|
|
77
|
+
);
|
|
78
|
+
clack.log.info(
|
|
79
|
+
` patterns.md ${patterns.exists ? pc.green('✓') : pc.red('✗')} ${patterns.count} entries ${pc.dim(patterns.lastModified || '')}`
|
|
80
|
+
);
|
|
81
|
+
clack.log.info(
|
|
82
|
+
` context.md ${context.exists ? pc.green('✓') : pc.red('✗')} ${pc.dim(context.lastModified || '')}`
|
|
83
|
+
);
|
|
84
|
+
clack.log.info(
|
|
85
|
+
` sessions/ ${sessions > 0 ? pc.green('✓') : pc.dim('·')} ${sessions} handoff(s)`
|
|
86
|
+
);
|
|
75
87
|
|
|
76
88
|
if (brainLink) {
|
|
77
89
|
try {
|
|
@@ -100,15 +112,21 @@ module.exports = {
|
|
|
100
112
|
const projectsDir = path.join(globalBrainDir, 'projects');
|
|
101
113
|
let projectCount = 0;
|
|
102
114
|
if (fs.existsSync(projectsDir)) {
|
|
103
|
-
projectCount = fs.readdirSync(projectsDir).filter(f => f.endsWith('.yaml')).length;
|
|
115
|
+
projectCount = fs.readdirSync(projectsDir).filter((f) => f.endsWith('.yaml')).length;
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
const indexExists = fs.existsSync(path.join(globalBrainDir, 'projects-index.md'));
|
|
107
119
|
|
|
108
120
|
clack.log.info(` identity.yaml ${identity ? pc.green('✓') : pc.yellow('✗ missing')}`);
|
|
109
|
-
clack.log.info(
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
clack.log.info(
|
|
122
|
+
` decisions.md ${gDecisions.exists ? pc.green('✓') : pc.dim('·')} ${gDecisions.count} entries`
|
|
123
|
+
);
|
|
124
|
+
clack.log.info(
|
|
125
|
+
` lessons.md ${gLessons.exists ? pc.green('✓') : pc.dim('·')} ${gLessons.count} entries`
|
|
126
|
+
);
|
|
127
|
+
clack.log.info(
|
|
128
|
+
` patterns.md ${gPatterns.exists ? pc.green('✓') : pc.dim('·')} ${gPatterns.count} entries`
|
|
129
|
+
);
|
|
112
130
|
clack.log.info(` projects/ ${pc.green('✓')} ${projectCount} project(s) indexed`);
|
|
113
131
|
clack.log.info(` projects-index ${indexExists ? pc.green('✓') : pc.dim('·')}`);
|
|
114
132
|
} else {
|
|
@@ -160,7 +178,10 @@ module.exports = {
|
|
|
160
178
|
return;
|
|
161
179
|
}
|
|
162
180
|
|
|
163
|
-
const exportPath = path.join(
|
|
181
|
+
const exportPath = path.join(
|
|
182
|
+
projectDir,
|
|
183
|
+
`bmad-brain-export-${new Date().toISOString().slice(0, 10)}.md`
|
|
184
|
+
);
|
|
164
185
|
const sections = [];
|
|
165
186
|
|
|
166
187
|
sections.push('# BMAD+ Brain Export', `> Exported: ${new Date().toISOString()}`, '');
|
|
@@ -178,7 +199,13 @@ module.exports = {
|
|
|
178
199
|
sections.push('## Projects', '');
|
|
179
200
|
for (const f of fs.readdirSync(projectsDir)) {
|
|
180
201
|
if (f.endsWith('.yaml')) {
|
|
181
|
-
sections.push(
|
|
202
|
+
sections.push(
|
|
203
|
+
`### ${f}`,
|
|
204
|
+
'```yaml',
|
|
205
|
+
fs.readFileSync(path.join(projectsDir, f), 'utf8').trim(),
|
|
206
|
+
'```',
|
|
207
|
+
''
|
|
208
|
+
);
|
|
182
209
|
}
|
|
183
210
|
}
|
|
184
211
|
}
|
|
@@ -16,12 +16,32 @@ const { normalizePathForHash, projectHash } = require('../lib/path-hash');
|
|
|
16
16
|
|
|
17
17
|
// Directories to skip during scanning
|
|
18
18
|
const SKIP_DIRS = new Set([
|
|
19
|
-
'node_modules',
|
|
20
|
-
'.
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
19
|
+
'node_modules',
|
|
20
|
+
'.git',
|
|
21
|
+
'vendor',
|
|
22
|
+
'__pycache__',
|
|
23
|
+
'dist',
|
|
24
|
+
'build',
|
|
25
|
+
'.next',
|
|
26
|
+
'.nuxt',
|
|
27
|
+
'.svelte-kit',
|
|
28
|
+
'target',
|
|
29
|
+
'.venv',
|
|
30
|
+
'venv',
|
|
31
|
+
'.cache',
|
|
32
|
+
'.output',
|
|
33
|
+
'coverage',
|
|
34
|
+
'.turbo',
|
|
35
|
+
'.angular',
|
|
36
|
+
'$RECYCLE.BIN',
|
|
37
|
+
'System Volume Information',
|
|
38
|
+
'Windows',
|
|
39
|
+
'Program Files',
|
|
40
|
+
'Program Files (x86)',
|
|
41
|
+
'ProgramData',
|
|
42
|
+
'AppData',
|
|
43
|
+
'Recovery',
|
|
44
|
+
'PerfLogs',
|
|
25
45
|
]);
|
|
26
46
|
|
|
27
47
|
function getProjectStatus(dir, activeDays = 30, pausedDays = 180) {
|
|
@@ -31,15 +51,21 @@ function getProjectStatus(dir, activeDays = 30, pausedDays = 180) {
|
|
|
31
51
|
if (daysSince < activeDays) return 'active';
|
|
32
52
|
if (daysSince < pausedDays) return 'paused';
|
|
33
53
|
return 'archived';
|
|
34
|
-
} catch {
|
|
54
|
+
} catch {
|
|
55
|
+
return 'unknown';
|
|
56
|
+
}
|
|
35
57
|
}
|
|
36
58
|
|
|
37
59
|
function getStatusIcon(status) {
|
|
38
60
|
switch (status) {
|
|
39
|
-
case 'active':
|
|
40
|
-
|
|
41
|
-
case '
|
|
42
|
-
|
|
61
|
+
case 'active':
|
|
62
|
+
return pc.green('●');
|
|
63
|
+
case 'paused':
|
|
64
|
+
return pc.yellow('◐');
|
|
65
|
+
case 'archived':
|
|
66
|
+
return pc.dim('○');
|
|
67
|
+
default:
|
|
68
|
+
return pc.dim('?');
|
|
43
69
|
}
|
|
44
70
|
}
|
|
45
71
|
|
|
@@ -57,8 +83,7 @@ function getProjectName(dir) {
|
|
|
57
83
|
}
|
|
58
84
|
|
|
59
85
|
function hasBmadInstalled(dir) {
|
|
60
|
-
return fs.existsSync(path.join(dir, '.agents')) ||
|
|
61
|
-
fs.existsSync(path.join(dir, '_bmad'));
|
|
86
|
+
return fs.existsSync(path.join(dir, '.agents')) || fs.existsSync(path.join(dir, '_bmad'));
|
|
62
87
|
}
|
|
63
88
|
|
|
64
89
|
function scanDirectory(rootDir, maxDepth = 4, currentDepth = 0, activeDays = 30, pausedDays = 180) {
|
|
@@ -138,7 +163,9 @@ function indexProject(project, globalBrainDir) {
|
|
|
138
163
|
};
|
|
139
164
|
fs.writeFileSync(
|
|
140
165
|
path.join(globalBrainDir, `${hash}.yaml`),
|
|
141
|
-
Object.entries(meta)
|
|
166
|
+
Object.entries(meta)
|
|
167
|
+
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
|
|
168
|
+
.join('\n'),
|
|
142
169
|
'utf8'
|
|
143
170
|
);
|
|
144
171
|
}
|
|
@@ -164,8 +191,16 @@ module.exports = {
|
|
|
164
191
|
options: [
|
|
165
192
|
['-d, --directory <path>', 'Directory to scan (default: current directory)'],
|
|
166
193
|
['--depth <n>', 'Max depth to scan (default: 4)', '4'],
|
|
167
|
-
[
|
|
168
|
-
|
|
194
|
+
[
|
|
195
|
+
'--active-days <n>',
|
|
196
|
+
'Days since last modified to consider a project "active" (default: 30)',
|
|
197
|
+
'30',
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
'--paused-days <n>',
|
|
201
|
+
'Days since last modified to consider a project "paused" (default: 180)',
|
|
202
|
+
'180',
|
|
203
|
+
],
|
|
169
204
|
['-y, --yes', 'Index all projects without prompting'],
|
|
170
205
|
['-l, --lang <code>', 'Language code: en, fr, es, de, pt-br, ru, zh, he, ja, it'],
|
|
171
206
|
],
|
|
@@ -173,13 +208,31 @@ module.exports = {
|
|
|
173
208
|
const scanDir = path.resolve(options.directory || process.cwd());
|
|
174
209
|
|
|
175
210
|
const rawDepth = parseInt(options.depth, 10);
|
|
176
|
-
const maxDepth =
|
|
211
|
+
const maxDepth =
|
|
212
|
+
!isNaN(rawDepth) && rawDepth > 0
|
|
213
|
+
? rawDepth
|
|
214
|
+
: (() => {
|
|
215
|
+
clack.log.warn(`Invalid --depth "${options.depth}", defaulting to 4`);
|
|
216
|
+
return 4;
|
|
217
|
+
})();
|
|
177
218
|
|
|
178
219
|
const rawActive = parseInt(options.activeDays, 10);
|
|
179
|
-
const activeDays =
|
|
220
|
+
const activeDays =
|
|
221
|
+
!isNaN(rawActive) && rawActive > 0
|
|
222
|
+
? rawActive
|
|
223
|
+
: (() => {
|
|
224
|
+
clack.log.warn(`Invalid --active-days "${options.activeDays}", defaulting to 30`);
|
|
225
|
+
return 30;
|
|
226
|
+
})();
|
|
180
227
|
|
|
181
228
|
const rawPaused = parseInt(options.pausedDays, 10);
|
|
182
|
-
const pausedDays =
|
|
229
|
+
const pausedDays =
|
|
230
|
+
!isNaN(rawPaused) && rawPaused > 0
|
|
231
|
+
? rawPaused
|
|
232
|
+
: (() => {
|
|
233
|
+
clack.log.warn(`Invalid --paused-days "${options.pausedDays}", defaulting to 180`);
|
|
234
|
+
return 180;
|
|
235
|
+
})();
|
|
183
236
|
|
|
184
237
|
clack.intro(pc.bgMagenta(pc.white(' 🧠 BMAD+ Project Scanner ')));
|
|
185
238
|
|
|
@@ -205,15 +258,21 @@ module.exports = {
|
|
|
205
258
|
spinner.stop(`Found ${pc.bold(projects.length)} project(s)`);
|
|
206
259
|
|
|
207
260
|
// Display legend
|
|
208
|
-
const activeCount = projects.filter(p => p.status === 'active').length;
|
|
209
|
-
const pausedCount = projects.filter(p => p.status === 'paused').length;
|
|
210
|
-
const archivedCount = projects.filter(p => p.status === 'archived').length;
|
|
261
|
+
const activeCount = projects.filter((p) => p.status === 'active').length;
|
|
262
|
+
const pausedCount = projects.filter((p) => p.status === 'paused').length;
|
|
263
|
+
const archivedCount = projects.filter((p) => p.status === 'archived').length;
|
|
211
264
|
|
|
212
265
|
clack.log.info('');
|
|
213
266
|
clack.log.info(pc.dim(' Legend:'));
|
|
214
|
-
clack.log.info(
|
|
215
|
-
|
|
216
|
-
|
|
267
|
+
clack.log.info(
|
|
268
|
+
` ${pc.green('●')} active modified < ${activeDays} days ago ${pc.dim(`(${activeCount} found)`)}`
|
|
269
|
+
);
|
|
270
|
+
clack.log.info(
|
|
271
|
+
` ${pc.yellow('◐')} paused modified ${activeDays}–${pausedDays} days ago ${pc.dim(`(${pausedCount} found)`)}`
|
|
272
|
+
);
|
|
273
|
+
clack.log.info(
|
|
274
|
+
` ${pc.dim('○')} archived modified > ${pausedDays} days ago ${pc.dim(`(${archivedCount} found)`)}`
|
|
275
|
+
);
|
|
217
276
|
clack.log.info('');
|
|
218
277
|
|
|
219
278
|
// Display table
|
|
@@ -249,7 +308,7 @@ module.exports = {
|
|
|
249
308
|
options: [
|
|
250
309
|
{ value: 'all', label: `✅ Index all ${projects.length} projects` },
|
|
251
310
|
{ value: 'select', label: '✏️ Select which to index' },
|
|
252
|
-
{ value: 'none', label:
|
|
311
|
+
{ value: 'none', label: "⏭️ Skip — don't index anything" },
|
|
253
312
|
],
|
|
254
313
|
});
|
|
255
314
|
|
|
@@ -279,7 +338,7 @@ module.exports = {
|
|
|
279
338
|
return;
|
|
280
339
|
}
|
|
281
340
|
|
|
282
|
-
toIndex = selected.map(i => projects[i]);
|
|
341
|
+
toIndex = selected.map((i) => projects[i]);
|
|
283
342
|
}
|
|
284
343
|
|
|
285
344
|
const indexed = indexProjects(toIndex, globalBrainDir);
|
|
@@ -298,7 +357,11 @@ module.exports = {
|
|
|
298
357
|
for (const line of content.split('\n')) {
|
|
299
358
|
const m = line.match(/^(\w+):\s*(.+)$/);
|
|
300
359
|
if (m) {
|
|
301
|
-
try {
|
|
360
|
+
try {
|
|
361
|
+
meta[m[1]] = JSON.parse(m[2]);
|
|
362
|
+
} catch {
|
|
363
|
+
meta[m[1]] = m[2];
|
|
364
|
+
}
|
|
302
365
|
}
|
|
303
366
|
}
|
|
304
367
|
existingProjects.push(meta);
|
|
@@ -321,8 +384,9 @@ module.exports = {
|
|
|
321
384
|
'',
|
|
322
385
|
'| Status | Name | Stack | BMAD+ | Path |',
|
|
323
386
|
'|--------|------|-------|-------|------|',
|
|
324
|
-
...existingProjects.map(
|
|
325
|
-
|
|
387
|
+
...existingProjects.map(
|
|
388
|
+
(p) =>
|
|
389
|
+
`| ${p.status || '?'} | ${p.name || '?'} | ${p.stack || '?'} | ${p.bmad_installed ? '✓' : '·'} | \`${p.path || '?'}\` |`
|
|
326
390
|
),
|
|
327
391
|
'',
|
|
328
392
|
];
|
|
@@ -17,11 +17,27 @@ function fail(message) {
|
|
|
17
17
|
|
|
18
18
|
function resolveSpec(target, paths) {
|
|
19
19
|
if (!target) fail('an id or a spec file is required');
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
if (target.endsWith('.json')) {
|
|
21
|
+
const file = path.resolve(target);
|
|
22
|
+
if (!fs.existsSync(file)) fail(`no spec at ${file}`);
|
|
23
|
+
return uat.loadSpec(file);
|
|
24
|
+
}
|
|
25
|
+
const direct = path.join(paths.specs, `${target}.json`);
|
|
26
|
+
if (fs.existsSync(direct)) return uat.loadSpec(direct);
|
|
27
|
+
// A recipe is named by its id, not by its file: a copied template keeps its own name
|
|
28
|
+
// (the shipped example-uat-spec.json holds the id example-1.0.0).
|
|
29
|
+
if (fs.existsSync(paths.specs)) {
|
|
30
|
+
for (const name of fs.readdirSync(paths.specs).filter((entry) => entry.endsWith('.json'))) {
|
|
31
|
+
try {
|
|
32
|
+
const loaded = uat.loadSpec(path.join(paths.specs, name));
|
|
33
|
+
if (loaded.spec.id === target) return loaded;
|
|
34
|
+
} catch {
|
|
35
|
+
// an unreadable file cannot be the recipe asked for
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
fail(`no spec with the id "${target}" in ${paths.specs}`);
|
|
40
|
+
return null;
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
function readStdin() {
|
|
@@ -17,7 +17,12 @@ const { contentHash } = require('../lib/installed-adapters');
|
|
|
17
17
|
// Never follow a junction/symlink in a path used for deletion, including parents.
|
|
18
18
|
function safeStat(projectDir, target) {
|
|
19
19
|
const relative = path.relative(projectDir, target);
|
|
20
|
-
if (
|
|
20
|
+
if (
|
|
21
|
+
!relative ||
|
|
22
|
+
relative === '..' ||
|
|
23
|
+
relative.startsWith('..' + path.sep) ||
|
|
24
|
+
path.isAbsolute(relative)
|
|
25
|
+
) {
|
|
21
26
|
throw new Error('Path outside the project: ' + target);
|
|
22
27
|
}
|
|
23
28
|
let current = projectDir;
|
|
@@ -54,7 +59,8 @@ function removalPlan(projectDir, manifest, manifestPath) {
|
|
|
54
59
|
const sourceStat = fs.lstatSync(source);
|
|
55
60
|
if (sourceStat.isDirectory() && targetStat.isDirectory()) {
|
|
56
61
|
directories.add(target);
|
|
57
|
-
for (const name of fs.readdirSync(source))
|
|
62
|
+
for (const name of fs.readdirSync(source))
|
|
63
|
+
matchSource(path.join(source, name), path.join(target, name));
|
|
58
64
|
} else if (sourceStat.isFile() && targetStat.isFile()) {
|
|
59
65
|
const expected = fs.readFileSync(source);
|
|
60
66
|
if (fs.readFileSync(target).equals(expected)) files.set(target, expected);
|
|
@@ -69,26 +75,46 @@ function removalPlan(projectDir, manifest, manifestPath) {
|
|
|
69
75
|
for (const packId of manifest.packs) {
|
|
70
76
|
if (!Object.hasOwn(PACKS, packId)) throw new Error('Unknown installed pack: ' + packId);
|
|
71
77
|
const pack = PACKS[packId];
|
|
72
|
-
for (const name of pack.agents || [])
|
|
73
|
-
|
|
74
|
-
for (const name of pack.
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
for (const name of pack.agents || [])
|
|
79
|
+
matchSource(path.join(sourceRoot, 'agents', name), targetPath('.agents', 'skills', name));
|
|
80
|
+
for (const name of pack.skills || [])
|
|
81
|
+
matchSource(path.join(sourceRoot, 'skills', name), targetPath('.agents', 'skills', name));
|
|
82
|
+
for (const name of pack.data || [])
|
|
83
|
+
matchSource(path.join(sourceRoot, 'data', name), targetPath('.agents', 'data', name));
|
|
84
|
+
if (pack.packDir)
|
|
85
|
+
matchSource(
|
|
86
|
+
path.join(sourceRoot, pack.packSrcDir || 'agents', pack.packDir),
|
|
87
|
+
targetPath('.agents', 'skills', pack.packDir)
|
|
88
|
+
);
|
|
89
|
+
if (pack.externalPackage)
|
|
90
|
+
matchSource(
|
|
91
|
+
path.join(packageRoot, pack.externalPackage, 'skills'),
|
|
92
|
+
targetPath('.agents', 'skills')
|
|
93
|
+
);
|
|
77
94
|
}
|
|
78
|
-
for (const name of ['module.yaml', 'module-help.csv'])
|
|
95
|
+
for (const name of ['module.yaml', 'module-help.csv'])
|
|
96
|
+
matchSource(path.join(sourceRoot, name), targetPath('_bmad', name));
|
|
79
97
|
const renderOptions = {
|
|
80
98
|
packs: manifest.packs,
|
|
81
99
|
userName: typeof manifest.user === 'string' ? manifest.user : 'Developer',
|
|
82
100
|
language: typeof manifest.language === 'string' ? manifest.language : 'English',
|
|
83
101
|
};
|
|
84
|
-
const expectedFiles = new Map(
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
const expectedFiles = new Map(
|
|
103
|
+
generateUserFiles(DERIVED, renderOptions).map((entry) => [entry.file, entry.content])
|
|
104
|
+
);
|
|
105
|
+
const hashes =
|
|
106
|
+
manifest.adapterHashes &&
|
|
107
|
+
typeof manifest.adapterHashes === 'object' &&
|
|
108
|
+
!Array.isArray(manifest.adapterHashes)
|
|
109
|
+
? manifest.adapterHashes
|
|
110
|
+
: {};
|
|
87
111
|
// Only registry-declared paths can become deletion candidates, even when the
|
|
88
112
|
// manifest contains arbitrary hash keys. Keep previously owned tool targets.
|
|
89
|
-
const claimed = new Set(Object.keys(hashes).filter(file => expectedFiles.has(file)));
|
|
113
|
+
const claimed = new Set(Object.keys(hashes).filter((file) => expectedFiles.has(file)));
|
|
90
114
|
if (Array.isArray(manifest.ides)) {
|
|
91
|
-
const tools = manifest.ides.filter(id =>
|
|
115
|
+
const tools = manifest.ides.filter((id) =>
|
|
116
|
+
DERIVED.targets.adapters.some((adapter) => adapter.tool === id)
|
|
117
|
+
);
|
|
92
118
|
if (tools.length) {
|
|
93
119
|
for (const entry of generateUserFiles(DERIVED, { ...renderOptions, tools })) {
|
|
94
120
|
claimed.add(entry.file);
|
|
@@ -132,13 +158,27 @@ module.exports = {
|
|
|
132
158
|
command: 'uninstall',
|
|
133
159
|
description: 'Remove BMAD+ from your project',
|
|
134
160
|
options: [
|
|
161
|
+
['-d, --directory <path>', 'Project directory (default: current directory)'],
|
|
135
162
|
['-y, --yes', 'Confirm removal without prompting'],
|
|
136
163
|
['-l, --lang <code>', 'Language code: en, fr, es, de, pt-br, ru, zh, he, ja, it'],
|
|
137
164
|
],
|
|
138
165
|
action: async (options) => {
|
|
139
|
-
const projectDir = fs.realpathSync(process.cwd());
|
|
140
166
|
let i = t(options.lang || 'en');
|
|
141
167
|
clack.intro(pc.bgRed(pc.white(' BMAD+ Uninstaller ')));
|
|
168
|
+
let projectDir;
|
|
169
|
+
try {
|
|
170
|
+
projectDir = fs.realpathSync(path.resolve(options.directory || process.cwd()));
|
|
171
|
+
} catch (err) {
|
|
172
|
+
clack.log.error(
|
|
173
|
+
'Project directory not found: ' +
|
|
174
|
+
(options.directory || process.cwd()) +
|
|
175
|
+
' (' +
|
|
176
|
+
err.code +
|
|
177
|
+
')'
|
|
178
|
+
);
|
|
179
|
+
process.exitCode = 1;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
142
182
|
|
|
143
183
|
const manifestPath = path.join(projectDir, '_bmad', '.bmad-plus-install.json');
|
|
144
184
|
let manifest;
|
|
@@ -169,7 +209,15 @@ module.exports = {
|
|
|
169
209
|
process.exitCode = 1;
|
|
170
210
|
return;
|
|
171
211
|
}
|
|
172
|
-
clack.log.info(
|
|
212
|
+
clack.log.info(
|
|
213
|
+
'BMAD+ v' +
|
|
214
|
+
manifest.version +
|
|
215
|
+
' (' +
|
|
216
|
+
i.installed_on +
|
|
217
|
+
' ' +
|
|
218
|
+
manifest.installed.split('T')[0] +
|
|
219
|
+
')'
|
|
220
|
+
);
|
|
173
221
|
clack.log.info(i.selected_packs + ': ' + manifest.packs.join(', '));
|
|
174
222
|
if (!options.yes) {
|
|
175
223
|
const confirm = await clack.confirm({ message: i.uninstall_confirm });
|
|
@@ -184,7 +232,8 @@ module.exports = {
|
|
|
184
232
|
let removed = 0;
|
|
185
233
|
try {
|
|
186
234
|
// Revalidate after the prompt, before deleting any file.
|
|
187
|
-
for (const target of [...plan.files.keys(), ...plan.directories])
|
|
235
|
+
for (const target of [...plan.files.keys(), ...plan.directories])
|
|
236
|
+
safeStat(projectDir, target);
|
|
188
237
|
for (const [target, expected] of plan.files) {
|
|
189
238
|
if (safeStat(projectDir, target)?.isFile() && fs.readFileSync(target).equals(expected)) {
|
|
190
239
|
fs.unlinkSync(target);
|
|
@@ -15,7 +15,8 @@ module.exports = {
|
|
|
15
15
|
const result = await checkForUpdate({
|
|
16
16
|
projectDir: path.resolve(options.directory || process.cwd()),
|
|
17
17
|
runningVersion: require('../../../package.json').version,
|
|
18
|
-
refresh: Boolean(options.refresh),
|
|
18
|
+
refresh: Boolean(options.refresh),
|
|
19
|
+
offline: Boolean(options.offline),
|
|
19
20
|
checkReadiness: ({ projectDir }) => {
|
|
20
21
|
const { evaluateUpdateReadiness } = require('../lib/update-transaction');
|
|
21
22
|
return evaluateUpdateReadiness({ projectDir });
|
|
@@ -24,8 +25,12 @@ module.exports = {
|
|
|
24
25
|
if (result.status.startsWith('invalid-')) process.exitCode = 1;
|
|
25
26
|
if (options.json) console.log(JSON.stringify(result));
|
|
26
27
|
else {
|
|
27
|
-
console.log(
|
|
28
|
-
|
|
28
|
+
console.log(
|
|
29
|
+
`Installed: ${result.installedVersion || 'unknown'}; running: ${result.runningVersion}; channel ${result.channel || 'unknown'}: ${result.targetVersion || 'unknown'}`
|
|
30
|
+
);
|
|
31
|
+
console.log(
|
|
32
|
+
`${result.status}: ${result.reason}${result.stale ? ' (cached result is stale)' : ''}`
|
|
33
|
+
);
|
|
29
34
|
}
|
|
30
35
|
},
|
|
31
36
|
};
|
|
@@ -16,12 +16,18 @@ module.exports = {
|
|
|
16
16
|
],
|
|
17
17
|
action: async (options = {}) => {
|
|
18
18
|
try {
|
|
19
|
-
if (options.allowPrerelease && options.stableOnly)
|
|
20
|
-
|
|
19
|
+
if (options.allowPrerelease && options.stableOnly)
|
|
20
|
+
throw new Error('Choose --allow-prerelease or --stable-only.');
|
|
21
|
+
if (options.mode === 'auto' && !options.range)
|
|
22
|
+
throw new Error('--mode auto requires an explicit --range.');
|
|
21
23
|
const projectDir = path.resolve(options.directory || process.cwd());
|
|
22
24
|
const policy = readUpdatePolicy(projectDir);
|
|
23
|
-
const changing =
|
|
24
|
-
options.
|
|
25
|
+
const changing =
|
|
26
|
+
options.mode !== undefined ||
|
|
27
|
+
options.range !== undefined ||
|
|
28
|
+
options.channel !== undefined ||
|
|
29
|
+
options.allowPrerelease ||
|
|
30
|
+
options.stableOnly;
|
|
25
31
|
if (options.mode !== undefined) policy.mode = options.mode;
|
|
26
32
|
if (options.range !== undefined) policy.allowedRange = options.range;
|
|
27
33
|
if (options.channel !== undefined) policy.channel = options.channel;
|
|
@@ -29,7 +35,10 @@ module.exports = {
|
|
|
29
35
|
if (options.stableOnly) policy.allowPrerelease = false;
|
|
30
36
|
const saved = changing ? writeUpdatePolicy(projectDir, policy) : policy;
|
|
31
37
|
if (options.json) console.log(JSON.stringify({ policy: saved, changed: Boolean(changing) }));
|
|
32
|
-
else
|
|
38
|
+
else
|
|
39
|
+
console.log(
|
|
40
|
+
`Update policy${changing ? ' saved' : ''}: ${saved.mode}; channel=${saved.channel}; range=${saved.allowedRange || 'none'}; prereleases=${saved.allowPrerelease}`
|
|
41
|
+
);
|
|
33
42
|
} catch (error) {
|
|
34
43
|
process.exitCode = 1;
|
|
35
44
|
if (options.json) console.log(JSON.stringify({ error: error.message }));
|