bmad-plus 0.9.1 → 0.9.2
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 +21 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/tools/cli/commands/autoconfig.js +18 -1
- package/tools/cli/commands/doctor.js +12 -0
- package/tools/cli/commands/install.js +20 -0
- package/tools/cli/commands/scan.js +18 -1
- package/tools/cli/commands/uninstall.js +3 -1
- package/tools/cli/commands/update.js +19 -2
- package/tools/cli/lib/packs.js +122 -114
- package/tools/cli/lib/validate.js +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to BMAD+ will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.9.2] — 2026-07-01
|
|
9
|
+
|
|
10
|
+
### Security — Phase 1 remediation (exploitable findings)
|
|
11
|
+
- **RCE closed** (`ci_cd.py`): command allowlist now uses exact-token matching (shlex.split) + realpath/commonpath containment instead of prefix/`startswith` — defeats argument injection (`make -f attacker.mk`) and sibling-path bypass; `shell=False` throughout; dead Makefile fallback removed.
|
|
12
|
+
- **SSRF hardened** (`seo_fetch.py`): fails **closed** on DNS error, blocks private/loopback/reserved/link-local IPs (incl. IPv4-mapped IPv6), re-validates every redirect hop manually.
|
|
13
|
+
- **Stored + DOM XSS closed** (`seo_report.py`, `dashboard.html`): all audited/external values HTML-escaped in the report; dashboard renders via `textContent`/`createElement` + `addEventListener` instead of `innerHTML`/inline `onclick`.
|
|
14
|
+
- **MCP server** (`server.py`): constant-time `hmac.compare_digest` for token + dashboard password; app assembled in module-level `create_app()` so `uvicorn server:app` runs with auth intact; dead `verify_mcp_token` removed; rate-limit map eviction.
|
|
15
|
+
- **XXE**: `seo_crawl.py` hard-fails without `defusedxml` (no unsafe stdlib fallback).
|
|
16
|
+
|
|
17
|
+
### Supply chain
|
|
18
|
+
- `requests>=2.32.4` (CVE-2024-35195) across all requirements files.
|
|
19
|
+
- `PyPDF2`→`pypdf==4.3.1` (dep + consumer import in `gamma_report.py`).
|
|
20
|
+
- `defusedxml==0.7.1` added; Dockerfile non-root user + tag pin; docker-compose off `:latest`; GitHub Actions pinned to commit SHAs; Dependabot covers pip + docker + github-actions.
|
|
21
|
+
|
|
22
|
+
### Fixed — robustness
|
|
23
|
+
- Non-destructive install (backs up existing CLAUDE/GEMINI/AGENTS.md); marker-based uninstall; IDE-scoped autoconfig; `doctor` no longer emits false "Missing agent" warnings; guarded manifest `JSON.parse`; Windows-safe project-path hashing; hardened `validateUserName`; lazy RAG model load; async gamma polling; repo-URL validation; `git_ops` path confinement.
|
|
24
|
+
|
|
25
|
+
### Notes
|
|
26
|
+
- Documented residual follow-ups (SSRF crawler-class redirect hops, DNS-rebinding IP pinning, Dockerfile digest pin) are tracked in `audit/2026-07-01/PHASE-1-STATUS.md`.
|
|
27
|
+
- Verified: `npm test` 176/176, `npm run lint` 0 errors, all edited Python parses; SEO report smoke-tested (renders + XSS escaped).
|
|
28
|
+
|
|
8
29
|
## [0.9.1] — 2026-07-01
|
|
9
30
|
|
|
10
31
|
### Fixed — Credibility (honest status)
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🚀 BMAD+ — Augmented Multi-Agent AI Framework
|
|
2
2
|
|
|
3
|
-
[](CHANGELOG.md)
|
|
4
4
|
[](https://github.com/bmad-code-org/BMAD-METHOD)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-plus",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.2",
|
|
5
5
|
"description": "BMAD+ — Augmented AI-Driven Development Framework with multi-role agents, autopilot, and parallel execution",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"bmad",
|
|
@@ -11,6 +11,7 @@ const fs = require('node:fs');
|
|
|
11
11
|
const clack = require('@clack/prompts');
|
|
12
12
|
const pc = require('picocolors');
|
|
13
13
|
const { detectStack } = require('../lib/stack-detect');
|
|
14
|
+
const { IDE_CONFIGS } = require('../lib/ide-config');
|
|
14
15
|
|
|
15
16
|
// ── Project Analysis Engine ──
|
|
16
17
|
|
|
@@ -282,7 +283,23 @@ module.exports = {
|
|
|
282
283
|
|
|
283
284
|
// Use the install module directly
|
|
284
285
|
const installModule = require('./install');
|
|
285
|
-
|
|
286
|
+
|
|
287
|
+
// NODE-06: only generate IDE configs for IDEs this project actually uses.
|
|
288
|
+
// - If IDE config files already exist, preserve them ('none').
|
|
289
|
+
// - Otherwise detect IDE markers and pass only those; never let install's
|
|
290
|
+
// --yes fallback write ALL three configs into a project that uses none.
|
|
291
|
+
let toolsArg;
|
|
292
|
+
if (structure.hasIdeConfigs.length > 0) {
|
|
293
|
+
toolsArg = 'none';
|
|
294
|
+
} else {
|
|
295
|
+
const detectedIDEs = [];
|
|
296
|
+
for (const [id, ide] of Object.entries(IDE_CONFIGS)) {
|
|
297
|
+
if (ide.detect.some(marker => fs.existsSync(path.join(projectDir, marker)))) {
|
|
298
|
+
detectedIDEs.push(id);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
toolsArg = detectedIDEs.length > 0 ? detectedIDEs.join(',') : 'none';
|
|
302
|
+
}
|
|
286
303
|
|
|
287
304
|
// Build install args
|
|
288
305
|
try {
|
|
@@ -88,6 +88,18 @@ module.exports = {
|
|
|
88
88
|
const packPath = path.join(agentsDir, entry.packDir);
|
|
89
89
|
if (fs.existsSync(packPath)) {
|
|
90
90
|
passed++;
|
|
91
|
+
|
|
92
|
+
// These packs bundle their agents as files inside packDir — verify
|
|
93
|
+
// them there (not as loose directories, which would falsely warn).
|
|
94
|
+
for (const agentFile of (entry.packAgents || [])) {
|
|
95
|
+
checks++;
|
|
96
|
+
if (fs.existsSync(path.join(packPath, agentFile))) {
|
|
97
|
+
passed++;
|
|
98
|
+
} else {
|
|
99
|
+
clack.log.warn(`⚠️ Missing agent: ${agentFile} (pack: ${pack})`);
|
|
100
|
+
warnings++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
91
103
|
} else {
|
|
92
104
|
clack.log.warn(`⚠️ Missing pack directory: ${entry.packDir} (pack: ${pack})`);
|
|
93
105
|
warnings++;
|
|
@@ -246,12 +246,32 @@ module.exports = {
|
|
|
246
246
|
ideSpinner.start(i.configuring_ides);
|
|
247
247
|
|
|
248
248
|
const configContent = generateIDEConfig(userName, commLang, selectedPacks);
|
|
249
|
+
// Marker present in every BMAD+-generated IDE config (see lib/ide-config.js).
|
|
250
|
+
const BMAD_MARKER = 'BMAD+ — AI Agent Configuration';
|
|
249
251
|
|
|
250
252
|
for (const ideId of detectedIDEs) {
|
|
251
253
|
const ide = IDE_CONFIGS[ideId];
|
|
252
254
|
if (!ide) continue;
|
|
253
255
|
|
|
254
256
|
const configPath = path.join(projectDir, ide.configFile);
|
|
257
|
+
|
|
258
|
+
// NODE-02: never clobber a hand-authored config without protecting it.
|
|
259
|
+
if (fs.existsSync(configPath)) {
|
|
260
|
+
const existing = fs.readFileSync(configPath, 'utf8');
|
|
261
|
+
if (!existing.includes(BMAD_MARKER)) {
|
|
262
|
+
if (!options.yes) {
|
|
263
|
+
// Interactive: keep the user's file untouched.
|
|
264
|
+
clack.log.warn(`⚠️ ${ide.configFile} already exists and was not created by BMAD+ — kept your file (skipped). Re-run with --yes to back it up and overwrite.`);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
// Non-interactive: back up before overwriting, never destroy data.
|
|
268
|
+
let backupPath = `${configPath}.bak`;
|
|
269
|
+
if (fs.existsSync(backupPath)) backupPath = `${configPath}.${Date.now()}.bak`;
|
|
270
|
+
fs.copyFileSync(configPath, backupPath);
|
|
271
|
+
clack.log.warn(`⚠️ Backed up existing ${ide.configFile} → ${path.basename(backupPath)}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
255
275
|
fs.writeFileSync(configPath, configContent, 'utf8');
|
|
256
276
|
}
|
|
257
277
|
|
|
@@ -118,6 +118,22 @@ function scanDirectory(rootDir, maxDepth = 4, currentDepth = 0, activeDays = 30,
|
|
|
118
118
|
return projects;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Normalize a filesystem path for stable hashing.
|
|
123
|
+
* On Windows the same project can be referenced as `D:\proj` or `d:\proj`
|
|
124
|
+
* (case-insensitive drive) with mixed separators. Resolving + lowercasing the
|
|
125
|
+
* drive letter ensures the same project produces the same index key and is not
|
|
126
|
+
* double-indexed.
|
|
127
|
+
* @param {string} p - Raw filesystem path
|
|
128
|
+
* @returns {string} Normalized path suitable for hashing
|
|
129
|
+
*/
|
|
130
|
+
function normalizePathForHash(p) {
|
|
131
|
+
let resolved = path.resolve(p);
|
|
132
|
+
// Lowercase a leading Windows drive letter (e.g. "D:" → "d:").
|
|
133
|
+
resolved = resolved.replace(/^([A-Za-z]):/, (_m, drive) => drive.toLowerCase() + ':');
|
|
134
|
+
return resolved;
|
|
135
|
+
}
|
|
136
|
+
|
|
121
137
|
/**
|
|
122
138
|
* Index a single project in the global brain by writing its metadata YAML file.
|
|
123
139
|
* @param {object} project - Project metadata object
|
|
@@ -125,7 +141,7 @@ function scanDirectory(rootDir, maxDepth = 4, currentDepth = 0, activeDays = 30,
|
|
|
125
141
|
* @returns {void}
|
|
126
142
|
*/
|
|
127
143
|
function indexProject(project, globalBrainDir) {
|
|
128
|
-
const hash = crypto.createHash('sha256').update(project.path).digest('hex').slice(0, 8);
|
|
144
|
+
const hash = crypto.createHash('sha256').update(normalizePathForHash(project.path)).digest('hex').slice(0, 8);
|
|
129
145
|
const meta = {
|
|
130
146
|
path: project.path,
|
|
131
147
|
name: project.name,
|
|
@@ -341,6 +357,7 @@ module.exports = {
|
|
|
341
357
|
getProjectName,
|
|
342
358
|
hasBmadInstalled,
|
|
343
359
|
scanDirectory,
|
|
360
|
+
normalizePathForHash,
|
|
344
361
|
indexProject,
|
|
345
362
|
indexProjects,
|
|
346
363
|
},
|
|
@@ -86,7 +86,9 @@ module.exports = {
|
|
|
86
86
|
const p = path.join(projectDir, configFile);
|
|
87
87
|
if (fs.existsSync(p)) {
|
|
88
88
|
const content = fs.readFileSync(p, 'utf8');
|
|
89
|
-
|
|
89
|
+
// NODE-05: only remove a config BMAD+ actually generated (it carries the
|
|
90
|
+
// marker below). A hand-authored file that merely mentions "BMAD+" is kept.
|
|
91
|
+
if (content.includes('BMAD+ — AI Agent Configuration')) {
|
|
90
92
|
fs.unlinkSync(p);
|
|
91
93
|
removed++;
|
|
92
94
|
}
|
|
@@ -39,7 +39,21 @@ module.exports = {
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
let manifest;
|
|
43
|
+
try {
|
|
44
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
45
|
+
} catch (err) {
|
|
46
|
+
clack.log.error(`Install manifest is unreadable or corrupt: ${err.message}`);
|
|
47
|
+
clack.log.info('Re-run `npx bmad-plus install` to repair the installation.');
|
|
48
|
+
clack.outro(pc.red('Update aborted.'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!manifest || typeof manifest !== 'object') {
|
|
52
|
+
clack.log.error('Install manifest is malformed (not an object).');
|
|
53
|
+
clack.log.info('Re-run `npx bmad-plus install` to repair the installation.');
|
|
54
|
+
clack.outro(pc.red('Update aborted.'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
43
57
|
const lang = options.lang || manifest.uiLanguage || 'en';
|
|
44
58
|
const i = t(lang);
|
|
45
59
|
|
|
@@ -51,7 +65,10 @@ module.exports = {
|
|
|
51
65
|
return;
|
|
52
66
|
}
|
|
53
67
|
|
|
54
|
-
|
|
68
|
+
// Guard against a missing/malformed `packs` field (NODE-04): coerce to an array.
|
|
69
|
+
const selectedPacks = Array.isArray(manifest.packs) && manifest.packs.length > 0
|
|
70
|
+
? manifest.packs
|
|
71
|
+
: ['core'];
|
|
55
72
|
clack.log.info(`${i.selected_packs}: ${selectedPacks.join(', ')}`);
|
|
56
73
|
|
|
57
74
|
const confirm = await clack.confirm({
|
package/tools/cli/lib/packs.js
CHANGED
|
@@ -1,114 +1,122 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BMAD+ Shared PACKS Module
|
|
3
|
-
* Single source of truth for pack definitions, expected agents, and pack ordering.
|
|
4
|
-
*
|
|
5
|
-
* Author: Laurent Rochetta
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const PACKS = {
|
|
9
|
-
core: {
|
|
10
|
-
name: 'Core',
|
|
11
|
-
icon: 'b',
|
|
12
|
-
agents: ['agent-strategist', 'agent-architect-dev', 'agent-quality', 'agent-orchestrator'],
|
|
13
|
-
skills: ['bmad-plus-autopilot', 'bmad-plus-parallel', 'bmad-plus-sync'],
|
|
14
|
-
data: ['role-triggers.yaml'],
|
|
15
|
-
packDir: 'pack-core',
|
|
16
|
-
packSrcDir: 'packs',
|
|
17
|
-
required: true,
|
|
18
|
-
desc: 'Core agents & skills',
|
|
19
|
-
},
|
|
20
|
-
osint: {
|
|
21
|
-
name: 'OSINT',
|
|
22
|
-
icon: 'j',
|
|
23
|
-
agents: ['agent-shadow'],
|
|
24
|
-
skills: [],
|
|
25
|
-
externalPackage: 'osint-agent-package',
|
|
26
|
-
packDir: 'pack-osint',
|
|
27
|
-
packSrcDir: 'packs',
|
|
28
|
-
desc: 'OSINT & investigation',
|
|
29
|
-
},
|
|
30
|
-
maker: {
|
|
31
|
-
name: 'Maker',
|
|
32
|
-
icon: 'f',
|
|
33
|
-
agents: ['agent-maker'],
|
|
34
|
-
skills: [],
|
|
35
|
-
data: [],
|
|
36
|
-
packDir: 'pack-maker',
|
|
37
|
-
packSrcDir: 'packs',
|
|
38
|
-
desc: 'Agent creation toolkit',
|
|
39
|
-
},
|
|
40
|
-
shield: {
|
|
41
|
-
name: 'Shield',
|
|
42
|
-
icon: 'm',
|
|
43
|
-
agents: ['shield-orchestrator'],
|
|
44
|
-
skills: [],
|
|
45
|
-
packDir: 'pack-shield',
|
|
46
|
-
packSrcDir: 'packs',
|
|
47
|
-
desc: 'GRC compliance (25+ frameworks)',
|
|
48
|
-
},
|
|
49
|
-
seo: {
|
|
50
|
-
name: 'SEO',
|
|
51
|
-
icon: 'k',
|
|
52
|
-
agents: ['seo-scout', 'seo-chief', 'seo-judge'],
|
|
53
|
-
skills: [],
|
|
54
|
-
packDir: 'pack-seo',
|
|
55
|
-
packSrcDir: 'packs',
|
|
56
|
-
desc: 'SEO audit & optimization',
|
|
57
|
-
},
|
|
58
|
-
memory: {
|
|
59
|
-
name: 'Memory',
|
|
60
|
-
icon: 'x',
|
|
61
|
-
agents: ['zecher'],
|
|
62
|
-
skills: [],
|
|
63
|
-
packDir: 'pack-memory',
|
|
64
|
-
packSrcDir: 'packs',
|
|
65
|
-
desc: 'Persistent cross-session memory',
|
|
66
|
-
},
|
|
67
|
-
'dev-studio': {
|
|
68
|
-
name: 'Dev Studio',
|
|
69
|
-
icon: 'v',
|
|
70
|
-
agents: ['dev-studio-orchestrator'],
|
|
71
|
-
skills: ['dev-studio'],
|
|
72
|
-
packDir: 'pack-dev-studio',
|
|
73
|
-
packSrcDir: 'packs',
|
|
74
|
-
desc: 'SDLC automation (6 agents, 56+ skills)',
|
|
75
|
-
},
|
|
76
|
-
backup: {
|
|
77
|
-
name: 'Backup',
|
|
78
|
-
icon: 'y',
|
|
79
|
-
agents: ['backup-agent'],
|
|
80
|
-
skills: [],
|
|
81
|
-
packDir: 'pack-backup',
|
|
82
|
-
packSrcDir: 'packs',
|
|
83
|
-
desc: 'Backup & restore',
|
|
84
|
-
},
|
|
85
|
-
animated: {
|
|
86
|
-
name: 'Animated',
|
|
87
|
-
icon: 'z',
|
|
88
|
-
agents: ['animated-website-agent'],
|
|
89
|
-
skills: [],
|
|
90
|
-
packDir: 'pack-animated',
|
|
91
|
-
packSrcDir: 'packs',
|
|
92
|
-
desc: 'Animated website agents',
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const PACK_ORDER = ['core', 'osint', 'maker', 'shield', 'seo', 'memory', 'dev-studio', 'backup', 'animated'];
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Maps each pack to
|
|
100
|
-
* after installation.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
1
|
+
/**
|
|
2
|
+
* BMAD+ Shared PACKS Module
|
|
3
|
+
* Single source of truth for pack definitions, expected agents, and pack ordering.
|
|
4
|
+
*
|
|
5
|
+
* Author: Laurent Rochetta
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const PACKS = {
|
|
9
|
+
core: {
|
|
10
|
+
name: 'Core',
|
|
11
|
+
icon: 'b',
|
|
12
|
+
agents: ['agent-strategist', 'agent-architect-dev', 'agent-quality', 'agent-orchestrator'],
|
|
13
|
+
skills: ['bmad-plus-autopilot', 'bmad-plus-parallel', 'bmad-plus-sync'],
|
|
14
|
+
data: ['role-triggers.yaml'],
|
|
15
|
+
packDir: 'pack-core',
|
|
16
|
+
packSrcDir: 'packs',
|
|
17
|
+
required: true,
|
|
18
|
+
desc: 'Core agents & skills',
|
|
19
|
+
},
|
|
20
|
+
osint: {
|
|
21
|
+
name: 'OSINT',
|
|
22
|
+
icon: 'j',
|
|
23
|
+
agents: ['agent-shadow'],
|
|
24
|
+
skills: [],
|
|
25
|
+
externalPackage: 'osint-agent-package',
|
|
26
|
+
packDir: 'pack-osint',
|
|
27
|
+
packSrcDir: 'packs',
|
|
28
|
+
desc: 'OSINT & investigation',
|
|
29
|
+
},
|
|
30
|
+
maker: {
|
|
31
|
+
name: 'Maker',
|
|
32
|
+
icon: 'f',
|
|
33
|
+
agents: ['agent-maker'],
|
|
34
|
+
skills: [],
|
|
35
|
+
data: [],
|
|
36
|
+
packDir: 'pack-maker',
|
|
37
|
+
packSrcDir: 'packs',
|
|
38
|
+
desc: 'Agent creation toolkit',
|
|
39
|
+
},
|
|
40
|
+
shield: {
|
|
41
|
+
name: 'Shield',
|
|
42
|
+
icon: 'm',
|
|
43
|
+
agents: ['shield-orchestrator'],
|
|
44
|
+
skills: [],
|
|
45
|
+
packDir: 'pack-shield',
|
|
46
|
+
packSrcDir: 'packs',
|
|
47
|
+
desc: 'GRC compliance (25+ frameworks)',
|
|
48
|
+
},
|
|
49
|
+
seo: {
|
|
50
|
+
name: 'SEO',
|
|
51
|
+
icon: 'k',
|
|
52
|
+
agents: ['seo-scout', 'seo-chief', 'seo-judge'],
|
|
53
|
+
skills: [],
|
|
54
|
+
packDir: 'pack-seo',
|
|
55
|
+
packSrcDir: 'packs',
|
|
56
|
+
desc: 'SEO audit & optimization',
|
|
57
|
+
},
|
|
58
|
+
memory: {
|
|
59
|
+
name: 'Memory',
|
|
60
|
+
icon: 'x',
|
|
61
|
+
agents: ['zecher'],
|
|
62
|
+
skills: [],
|
|
63
|
+
packDir: 'pack-memory',
|
|
64
|
+
packSrcDir: 'packs',
|
|
65
|
+
desc: 'Persistent cross-session memory',
|
|
66
|
+
},
|
|
67
|
+
'dev-studio': {
|
|
68
|
+
name: 'Dev Studio',
|
|
69
|
+
icon: 'v',
|
|
70
|
+
agents: ['dev-studio-orchestrator'],
|
|
71
|
+
skills: ['dev-studio'],
|
|
72
|
+
packDir: 'pack-dev-studio',
|
|
73
|
+
packSrcDir: 'packs',
|
|
74
|
+
desc: 'SDLC automation (6 agents, 56+ skills)',
|
|
75
|
+
},
|
|
76
|
+
backup: {
|
|
77
|
+
name: 'Backup',
|
|
78
|
+
icon: 'y',
|
|
79
|
+
agents: ['backup-agent'],
|
|
80
|
+
skills: [],
|
|
81
|
+
packDir: 'pack-backup',
|
|
82
|
+
packSrcDir: 'packs',
|
|
83
|
+
desc: 'Backup & restore',
|
|
84
|
+
},
|
|
85
|
+
animated: {
|
|
86
|
+
name: 'Animated',
|
|
87
|
+
icon: 'z',
|
|
88
|
+
agents: ['animated-website-agent'],
|
|
89
|
+
skills: [],
|
|
90
|
+
packDir: 'pack-animated',
|
|
91
|
+
packSrcDir: 'packs',
|
|
92
|
+
desc: 'Animated website agents',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const PACK_ORDER = ['core', 'osint', 'maker', 'shield', 'seo', 'memory', 'dev-studio', 'backup', 'animated'];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Maps each pack to what `bmad-plus doctor` should expect under .agents/skills/
|
|
100
|
+
* after installation.
|
|
101
|
+
*
|
|
102
|
+
* - `agents` — loose agent DIRECTORIES copied straight into .agents/skills/
|
|
103
|
+
* (only core/osint/maker ship their agents this way).
|
|
104
|
+
* - `packDir` — the pack DIRECTORY copied into .agents/skills/ (null when none).
|
|
105
|
+
* - `packAgents`— agent FILES that live INSIDE `packDir` (shield/seo/memory/
|
|
106
|
+
* dev-studio/backup/animated bundle their agents this way, so they
|
|
107
|
+
* must not be checked as loose directories — doing so produced
|
|
108
|
+
* false "Missing agent" warnings on every healthy install).
|
|
109
|
+
*/
|
|
110
|
+
const EXPECTED_AGENTS = {
|
|
111
|
+
core: { agents: ['agent-strategist', 'agent-architect-dev', 'agent-quality', 'agent-orchestrator'], packDir: null },
|
|
112
|
+
osint: { agents: ['agent-shadow'], packDir: null },
|
|
113
|
+
maker: { agents: ['agent-maker'], packDir: null },
|
|
114
|
+
shield: { agents: [], packDir: 'pack-shield', packAgents: ['shield-orchestrator.md'] },
|
|
115
|
+
seo: { agents: [], packDir: 'pack-seo', packAgents: ['seo-scout.md', 'seo-chief.md', 'seo-judge.md'] },
|
|
116
|
+
memory: { agents: [], packDir: 'pack-memory', packAgents: ['zecher-agent.md', 'memory-orchestrator.md'] },
|
|
117
|
+
'dev-studio': { agents: [], packDir: 'pack-dev-studio', packAgents: ['dev-studio-orchestrator.md'] },
|
|
118
|
+
backup: { agents: [], packDir: 'pack-backup', packAgents: ['backup-agent.md'] },
|
|
119
|
+
animated: { agents: [], packDir: 'pack-animated', packAgents: ['animated-website-agent.md'] },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
module.exports = { PACKS, PACK_ORDER, EXPECTED_AGENTS };
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
* Extracted from install.js for modularity and reuse.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// Shell metacharacters that are dangerous in user-provided names
|
|
7
|
-
|
|
6
|
+
// Shell/quote metacharacters that are dangerous in user-provided names.
|
|
7
|
+
// Includes single/double quotes so a name cannot break out of a quoted context.
|
|
8
|
+
const SHELL_META = /[;&|`$(){}[\]!#~<>*?\\'"\n\r]/;
|
|
9
|
+
// Global variant used to strip EVERY occurrence during sanitization.
|
|
10
|
+
// (A non-global regex in String.replace only removes the first match.)
|
|
11
|
+
const SHELL_META_GLOBAL = /[;&|`$(){}[\]!#~<>*?\\'"\n\r]/g;
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Validate and sanitize a user name.
|
|
@@ -31,7 +35,7 @@ function validateUserName(rawName, fallback) {
|
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
if (SHELL_META.test(rawName)) {
|
|
34
|
-
const sanitized = rawName.replace(
|
|
38
|
+
const sanitized = rawName.replace(SHELL_META_GLOBAL, '').trim() || 'Developer';
|
|
35
39
|
warnings.push('Name contains shell metacharacters. Using sanitized version.');
|
|
36
40
|
return { name: sanitized, warnings };
|
|
37
41
|
}
|
|
@@ -42,4 +46,5 @@ function validateUserName(rawName, fallback) {
|
|
|
42
46
|
module.exports = {
|
|
43
47
|
validateUserName,
|
|
44
48
|
SHELL_META,
|
|
49
|
+
SHELL_META_GLOBAL,
|
|
45
50
|
};
|