bmad-plus 0.7.0 → 0.7.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 CHANGED
@@ -5,6 +5,25 @@ 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.7.1] — 2026-05-17
9
+
10
+ ### 🛠️ CLI Commands & Guardrails Injection
11
+
12
+ ### Added
13
+ - **`npx bmad-plus scan [path]`** — Interactive project scanner
14
+ - Recursive directory scan with stack auto-detection (Node.js, Rust, Python, Go, PHP, Ruby, Java)
15
+ - Framework detection (Next.js, Nuxt, React, Vue, Svelte, Express, Electron, Tauri)
16
+ - Status tracking (active/paused/archived based on last modified)
17
+ - Interactive validation: index all, select, or skip
18
+ - Auto-generates `projects-index.md` in global brain
19
+ - **`npx bmad-plus memory status`** — Memory health report
20
+ - Shows project memory + global brain status
21
+ - Entry counts, last modified dates, health score
22
+ - Brain link detection
23
+ - **`npx bmad-plus memory export`** — Export brain as portable Markdown archive
24
+ - **Karpathy Guardrails injection** — 4 principles auto-injected into CLAUDE.md/GEMINI.md/AGENTS.md when Memory pack is installed
25
+ - **i18n** — Memory + Dev Studio keys for EN and FR (other languages use fallback)
26
+
8
27
  ## [0.7.0] — 2026-05-17
9
28
 
10
29
  ### 🧠 Pack Memory — Persistent Brain
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ?? BMAD+ � Augmented AI-Driven Development Framework
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.7.0-blue.svg)](CHANGELOG.md)
3
+ [![Version](https://img.shields.io/badge/version-0.7.1-blue.svg)](CHANGELOG.md)
4
4
  [![Based on](https://img.shields.io/badge/based%20on-BMAD--METHOD%20v6.2.0-green.svg)](https://github.com/bmad-code-org/BMAD-METHOD)
5
5
  [![License](https://img.shields.io/badge/license-MIT-yellow.svg)](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.7.0",
4
+ "version": "0.7.1",
5
5
  "description": "BMAD+ — Augmented AI-Driven Development Framework with multi-role agents, autopilot, and parallel execution",
6
6
  "keywords": [
7
7
  "bmad",
@@ -1,6 +1,6 @@
1
1
  # ?? BMAD+ � Erweitertes KI-gest�tztes Entwicklungs-Framework
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.7.0-blue.svg)](../CHANGELOG.md)
3
+ [![Version](https://img.shields.io/badge/version-0.7.1-blue.svg)](../CHANGELOG.md)
4
4
  [![Based on](https://img.shields.io/badge/based%20on-BMAD--METHOD%20v6.2.0-green.svg)](https://github.com/bmad-code-org/BMAD-METHOD)
5
5
  [![License](https://img.shields.io/badge/license-MIT-yellow.svg)](../LICENSE)
6
6
 
@@ -1,6 +1,6 @@
1
1
  # ?? BMAD+ � Framework de Desarrollo Impulsado por IA Aumentada
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.7.0-blue.svg)](../CHANGELOG.md)
3
+ [![Version](https://img.shields.io/badge/version-0.7.1-blue.svg)](../CHANGELOG.md)
4
4
  [![Based on](https://img.shields.io/badge/based%20on-BMAD--METHOD%20v6.2.0-green.svg)](https://github.com/bmad-code-org/BMAD-METHOD)
5
5
  [![License](https://img.shields.io/badge/license-MIT-yellow.svg)](../LICENSE)
6
6
 
@@ -1,6 +1,6 @@
1
1
  # ?? BMAD+ � Augmented AI-Driven Development Framework
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.7.0-blue.svg)](../CHANGELOG.md)
3
+ [![Version](https://img.shields.io/badge/version-0.7.1-blue.svg)](../CHANGELOG.md)
4
4
  [![Based on](https://img.shields.io/badge/based%20on-BMAD--METHOD%20v6.2.0-green.svg)](https://github.com/bmad-code-org/BMAD-METHOD)
5
5
  [![License](https://img.shields.io/badge/license-MIT-yellow.svg)](../LICENSE)
6
6
 
@@ -25,6 +25,8 @@ const install = require('./commands/install');
25
25
  const uninstall = require('./commands/uninstall');
26
26
  const update = require('./commands/update');
27
27
  const doctor = require('./commands/doctor');
28
+ const scan = require('./commands/scan');
29
+ const memory = require('./commands/memory');
28
30
 
29
31
  program
30
32
  .version(packageJson.version)
@@ -66,6 +68,26 @@ for (const option of doctor.options || []) {
66
68
  }
67
69
  doctorCmd.action(doctor.action);
68
70
 
71
+ // Scan command
72
+ const scanCmd = program
73
+ .command('scan')
74
+ .description('Scan directories to discover and index projects in the global brain');
75
+
76
+ for (const option of scan.options || []) {
77
+ scanCmd.option(...option);
78
+ }
79
+ scanCmd.action(scan.action);
80
+
81
+ // Memory command
82
+ const memoryCmd = program
83
+ .command('memory [subcommand]')
84
+ .description('Manage persistent brain (status, export)');
85
+
86
+ for (const option of memory.options || []) {
87
+ memoryCmd.option(...option);
88
+ }
89
+ memoryCmd.action(memory.action);
90
+
69
91
  program.parse(process.argv);
70
92
 
71
93
  if (process.argv.slice(2).length === 0) {
@@ -56,7 +56,7 @@ const PACKS = {
56
56
  'dev-studio': {
57
57
  name: 'Dev Studio — Full SDLC',
58
58
  icon: '🏗️',
59
- description: '6 Torah-named agents + 30 workflows AnalysisArchitectureImplementation',
59
+ description: 'Full SDLC pipeline: brainstorm PRD architectureTDDcode review → deploy',
60
60
  required: false,
61
61
  agents: [],
62
62
  skills: [],
@@ -655,6 +655,39 @@ function generateIDEConfig(userName, language, packs) {
655
655
  agents.push('- **Zecher** (זכר) — Memory Archivist — Consolidation, project scanning, context recall');
656
656
  }
657
657
 
658
+ // Build memory section if memory pack is installed
659
+ let memorySection = '';
660
+ if (packs.includes('memory')) {
661
+ memorySection = [
662
+ '',
663
+ '## Memory Protocol (Karpathy Guardrails)',
664
+ '',
665
+ 'Agents MUST follow these behavioral principles:',
666
+ '',
667
+ '### G1 — Think Before Coding',
668
+ '- State assumptions explicitly. If uncertain, ask.',
669
+ '- Check `.agents/memory/decisions.md` for prior decisions before re-deciding.',
670
+ '',
671
+ '### G2 — Simplicity First',
672
+ '- Minimum code that solves the problem. Nothing speculative.',
673
+ '- Check `.agents/memory/patterns.md` for existing solutions.',
674
+ '',
675
+ '### G3 — Surgical Changes',
676
+ '- Touch only what you must. Match existing style.',
677
+ '- Log surprises in `.agents/memory/lessons.md`.',
678
+ '',
679
+ '### G4 — Goal-Driven Execution',
680
+ '- Define success criteria before implementing.',
681
+ '- Log non-obvious decisions in `.agents/memory/decisions.md`.',
682
+ '',
683
+ '### Memory Files',
684
+ '- `.agents/memory/decisions.md` — Read at session start, write when making decisions',
685
+ '- `.agents/memory/lessons.md` — Write when something unexpected happens',
686
+ '- `.agents/memory/patterns.md` — Write when a reusable pattern is validated',
687
+ '- `.agents/memory/context.md` — Update at session end with project state',
688
+ ].join('\n');
689
+ }
690
+
658
691
  return `# BMAD+ — AI Agent Configuration
659
692
 
660
693
  ## Project Context
@@ -678,7 +711,7 @@ ${agents.join('\n')}
678
711
  ## Communication
679
712
  - User name: ${userName}
680
713
  - Default language: ${language} for user-facing content, English for code and technical docs.
681
- `;
714
+ ${memorySection}`;
682
715
  }
683
716
 
684
717
  function generateConfigYaml(userName, language, projectDir) {
@@ -0,0 +1,194 @@
1
+ /**
2
+ * BMAD+ Memory Command
3
+ * Manage persistent brain: status, export, consolidate
4
+ *
5
+ * Author: Laurent Rochetta
6
+ */
7
+
8
+ const path = require('node:path');
9
+ const fs = require('node:fs');
10
+ const os = require('node:os');
11
+ const clack = require('@clack/prompts');
12
+ const pc = require('picocolors');
13
+
14
+ function countEntries(filePath) {
15
+ if (!fs.existsSync(filePath)) return { exists: false, count: 0, lastModified: null };
16
+ try {
17
+ const content = fs.readFileSync(filePath, 'utf8');
18
+ const entries = content.match(/^### /gm);
19
+ const stat = fs.statSync(filePath);
20
+ return {
21
+ exists: true,
22
+ count: entries ? entries.length : 0,
23
+ lastModified: stat.mtime.toISOString().slice(0, 10),
24
+ sizeKB: (stat.size / 1024).toFixed(1),
25
+ };
26
+ } catch {
27
+ return { exists: true, count: 0, lastModified: null };
28
+ }
29
+ }
30
+
31
+ function countSessions(dir) {
32
+ if (!fs.existsSync(dir)) return 0;
33
+ try {
34
+ return fs.readdirSync(dir).filter(f => f.endsWith('.md')).length;
35
+ } catch { return 0; }
36
+ }
37
+
38
+ module.exports = {
39
+ command: 'memory',
40
+ description: 'Manage BMAD+ persistent brain (status, export)',
41
+ options: [
42
+ ['-d, --directory <path>', 'Project directory (default: current directory)'],
43
+ ],
44
+ subcommands: {
45
+ status: 'Show memory health report',
46
+ export: 'Export brain as portable archive',
47
+ },
48
+ action: async (subcommand, options) => {
49
+ const projectDir = path.resolve(options?.directory || process.cwd());
50
+ const cmd = subcommand || 'status';
51
+
52
+ clack.intro(pc.bgMagenta(pc.white(' 🧠 BMAD+ Memory Manager ')));
53
+
54
+ if (cmd === 'status') {
55
+ // ── Project Memory ──
56
+ const memoryDir = path.join(projectDir, '.agents', 'memory');
57
+ const hasProjectMemory = fs.existsSync(memoryDir);
58
+
59
+ clack.log.info(pc.bold('\n📁 Project Memory') + pc.dim(` (${projectDir})`));
60
+
61
+ if (hasProjectMemory) {
62
+ const decisions = countEntries(path.join(memoryDir, 'decisions.md'));
63
+ const lessons = countEntries(path.join(memoryDir, 'lessons.md'));
64
+ const patterns = countEntries(path.join(memoryDir, 'patterns.md'));
65
+ const context = countEntries(path.join(memoryDir, 'context.md'));
66
+ const sessions = countSessions(path.join(memoryDir, 'sessions'));
67
+ const brainLink = fs.existsSync(path.join(memoryDir, '.brain-link'));
68
+
69
+ clack.log.info(` decisions.md ${decisions.exists ? pc.green('✓') : pc.red('✗')} ${decisions.count} entries ${pc.dim(decisions.lastModified || '')}`);
70
+ clack.log.info(` lessons.md ${lessons.exists ? pc.green('✓') : pc.red('✗')} ${lessons.count} entries ${pc.dim(lessons.lastModified || '')}`);
71
+ clack.log.info(` patterns.md ${patterns.exists ? pc.green('✓') : pc.red('✗')} ${patterns.count} entries ${pc.dim(patterns.lastModified || '')}`);
72
+ clack.log.info(` context.md ${context.exists ? pc.green('✓') : pc.red('✗')} ${pc.dim(context.lastModified || '')}`);
73
+ clack.log.info(` sessions/ ${sessions > 0 ? pc.green('✓') : pc.dim('·')} ${sessions} handoff(s)`);
74
+
75
+ if (brainLink) {
76
+ try {
77
+ const link = JSON.parse(fs.readFileSync(path.join(memoryDir, '.brain-link'), 'utf8'));
78
+ clack.log.info(` ${pc.cyan('🔗 Linked brain')}: ${link.linked_brain}`);
79
+ } catch {
80
+ clack.log.info(` ${pc.cyan('🔗 Brain link exists')}`);
81
+ }
82
+ }
83
+ } else {
84
+ clack.log.warn(' Not installed. Run: npx bmad-plus install (select Memory pack)');
85
+ }
86
+
87
+ // ── Global Brain ──
88
+ const globalBrainDir = path.join(os.homedir(), '.bmad-plus', 'brain');
89
+ const hasGlobalBrain = fs.existsSync(globalBrainDir);
90
+
91
+ clack.log.info(pc.bold('\n🌐 Global Brain') + pc.dim(` (${globalBrainDir})`));
92
+
93
+ if (hasGlobalBrain) {
94
+ const identity = fs.existsSync(path.join(globalBrainDir, 'identity.yaml'));
95
+ const gDecisions = countEntries(path.join(globalBrainDir, 'decisions.md'));
96
+ const gLessons = countEntries(path.join(globalBrainDir, 'lessons.md'));
97
+ const gPatterns = countEntries(path.join(globalBrainDir, 'patterns.md'));
98
+
99
+ const projectsDir = path.join(globalBrainDir, 'projects');
100
+ let projectCount = 0;
101
+ if (fs.existsSync(projectsDir)) {
102
+ projectCount = fs.readdirSync(projectsDir).filter(f => f.endsWith('.yaml')).length;
103
+ }
104
+
105
+ const indexExists = fs.existsSync(path.join(globalBrainDir, 'projects-index.md'));
106
+
107
+ clack.log.info(` identity.yaml ${identity ? pc.green('✓') : pc.yellow('✗ missing')}`);
108
+ clack.log.info(` decisions.md ${gDecisions.exists ? pc.green('✓') : pc.dim('·')} ${gDecisions.count} entries`);
109
+ clack.log.info(` lessons.md ${gLessons.exists ? pc.green('✓') : pc.dim('·')} ${gLessons.count} entries`);
110
+ clack.log.info(` patterns.md ${gPatterns.exists ? pc.green('✓') : pc.dim('·')} ${gPatterns.count} entries`);
111
+ clack.log.info(` projects/ ${pc.green('✓')} ${projectCount} project(s) indexed`);
112
+ clack.log.info(` projects-index ${indexExists ? pc.green('✓') : pc.dim('·')}`);
113
+ } else {
114
+ clack.log.warn(' Not initialized. Run: npx bmad-plus install (select Memory pack)');
115
+ }
116
+
117
+ // ── Health Score ──
118
+ let score = 0;
119
+ let maxScore = 0;
120
+
121
+ if (hasProjectMemory) {
122
+ const checks = [
123
+ fs.existsSync(path.join(memoryDir, 'decisions.md')),
124
+ fs.existsSync(path.join(memoryDir, 'lessons.md')),
125
+ fs.existsSync(path.join(memoryDir, 'patterns.md')),
126
+ fs.existsSync(path.join(memoryDir, 'context.md')),
127
+ countSessions(path.join(memoryDir, 'sessions')) > 0,
128
+ ];
129
+ maxScore += checks.length;
130
+ score += checks.filter(Boolean).length;
131
+ }
132
+
133
+ if (hasGlobalBrain) {
134
+ const gChecks = [
135
+ fs.existsSync(path.join(globalBrainDir, 'identity.yaml')),
136
+ fs.existsSync(path.join(globalBrainDir, 'decisions.md')),
137
+ fs.existsSync(path.join(globalBrainDir, 'lessons.md')),
138
+ fs.existsSync(path.join(globalBrainDir, 'patterns.md')),
139
+ fs.existsSync(path.join(globalBrainDir, 'projects')),
140
+ ];
141
+ maxScore += gChecks.length;
142
+ score += gChecks.filter(Boolean).length;
143
+ }
144
+
145
+ if (maxScore > 0) {
146
+ const pct = Math.round((score / maxScore) * 100);
147
+ const color = pct >= 80 ? pc.green : pct >= 50 ? pc.yellow : pc.red;
148
+ clack.log.info('');
149
+ clack.log.info(pc.bold(`🏥 Health: ${color(`${pct}%`)} (${score}/${maxScore} checks)`));
150
+ }
151
+
152
+ clack.log.info('');
153
+ } else if (cmd === 'export') {
154
+ // Export brain as text dump
155
+ const globalBrainDir = path.join(os.homedir(), '.bmad-plus', 'brain');
156
+ if (!fs.existsSync(globalBrainDir)) {
157
+ clack.log.error('Global brain not found. Nothing to export.');
158
+ clack.outro(pc.red('Export failed.'));
159
+ return;
160
+ }
161
+
162
+ const exportPath = path.join(projectDir, `bmad-brain-export-${new Date().toISOString().slice(0, 10)}.md`);
163
+ const sections = [];
164
+
165
+ sections.push('# BMAD+ Brain Export', `> Exported: ${new Date().toISOString()}`, '');
166
+
167
+ for (const file of ['identity.yaml', 'decisions.md', 'lessons.md', 'patterns.md']) {
168
+ const fp = path.join(globalBrainDir, file);
169
+ if (fs.existsSync(fp)) {
170
+ sections.push(`## ${file}`, '```', fs.readFileSync(fp, 'utf8').trim(), '```', '');
171
+ }
172
+ }
173
+
174
+ // Project index
175
+ const projectsDir = path.join(globalBrainDir, 'projects');
176
+ if (fs.existsSync(projectsDir)) {
177
+ sections.push('## Projects', '');
178
+ for (const f of fs.readdirSync(projectsDir)) {
179
+ if (f.endsWith('.yaml')) {
180
+ sections.push(`### ${f}`, '```yaml', fs.readFileSync(path.join(projectsDir, f), 'utf8').trim(), '```', '');
181
+ }
182
+ }
183
+ }
184
+
185
+ fs.writeFileSync(exportPath, sections.join('\n'), 'utf8');
186
+ clack.log.success(`📦 Brain exported to: ${exportPath}`);
187
+ } else {
188
+ clack.log.error(`Unknown subcommand: ${cmd}`);
189
+ clack.log.info('Available: status, export');
190
+ }
191
+
192
+ clack.outro(pc.green('Done! 🧠'));
193
+ },
194
+ };
@@ -0,0 +1,335 @@
1
+ /**
2
+ * BMAD+ Scan Command
3
+ * Scan directories to discover projects, detect stacks, and index them in the global brain.
4
+ * Interactive validation — user confirms each project before indexing.
5
+ *
6
+ * Author: Laurent Rochetta
7
+ */
8
+
9
+ const path = require('node:path');
10
+ const fs = require('node:fs');
11
+ const os = require('node:os');
12
+ const crypto = require('node:crypto');
13
+ const clack = require('@clack/prompts');
14
+ const pc = require('picocolors');
15
+
16
+ // Project detection markers (priority order)
17
+ const PROJECT_MARKERS = [
18
+ { file: 'package.json', stack: 'Node.js', detect: (dir) => {
19
+ try {
20
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
21
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
22
+ if (deps['next']) return 'Next.js';
23
+ if (deps['nuxt']) return 'Nuxt';
24
+ if (deps['react']) return 'React';
25
+ if (deps['vue']) return 'Vue.js';
26
+ if (deps['svelte']) return 'Svelte';
27
+ if (deps['express']) return 'Express';
28
+ if (deps['fastify']) return 'Fastify';
29
+ if (deps['electron']) return 'Electron';
30
+ if (deps['tauri']) return 'Tauri';
31
+ return 'Node.js';
32
+ } catch { return 'Node.js'; }
33
+ }},
34
+ { file: 'Cargo.toml', stack: 'Rust' },
35
+ { file: 'pyproject.toml', stack: 'Python' },
36
+ { file: 'requirements.txt', stack: 'Python' },
37
+ { file: 'go.mod', stack: 'Go' },
38
+ { file: 'composer.json', stack: 'PHP' },
39
+ { file: 'Gemfile', stack: 'Ruby' },
40
+ { file: 'pom.xml', stack: 'Java' },
41
+ { file: 'build.gradle', stack: 'Java/Kotlin' },
42
+ ];
43
+
44
+ // Directories to skip during scanning
45
+ const SKIP_DIRS = new Set([
46
+ 'node_modules', '.git', 'vendor', '__pycache__', 'dist', 'build',
47
+ '.next', '.nuxt', '.svelte-kit', 'target', '.venv', 'venv',
48
+ '.cache', '.output', 'coverage', '.turbo', '.angular',
49
+ '$RECYCLE.BIN', 'System Volume Information', 'Windows',
50
+ 'Program Files', 'Program Files (x86)', 'ProgramData',
51
+ 'AppData', 'Recovery', 'PerfLogs',
52
+ ]);
53
+
54
+ function getProjectStatus(dir) {
55
+ try {
56
+ const stat = fs.statSync(dir);
57
+ const daysSince = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24);
58
+ if (daysSince < 30) return 'active';
59
+ if (daysSince < 180) return 'paused';
60
+ return 'archived';
61
+ } catch { return 'unknown'; }
62
+ }
63
+
64
+ function getStatusIcon(status) {
65
+ switch (status) {
66
+ case 'active': return pc.green('●');
67
+ case 'paused': return pc.yellow('◐');
68
+ case 'archived': return pc.dim('○');
69
+ default: return pc.dim('?');
70
+ }
71
+ }
72
+
73
+ function getProjectName(dir) {
74
+ try {
75
+ const pkgPath = path.join(dir, 'package.json');
76
+ if (fs.existsSync(pkgPath)) {
77
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
78
+ if (pkg.name) return pkg.name;
79
+ }
80
+ } catch {}
81
+ return path.basename(dir);
82
+ }
83
+
84
+ function hasBmadInstalled(dir) {
85
+ return fs.existsSync(path.join(dir, '.agents')) ||
86
+ fs.existsSync(path.join(dir, '_bmad'));
87
+ }
88
+
89
+ function scanDirectory(rootDir, maxDepth = 4, currentDepth = 0) {
90
+ const projects = [];
91
+
92
+ if (currentDepth > maxDepth) return projects;
93
+
94
+ let entries;
95
+ try {
96
+ entries = fs.readdirSync(rootDir, { withFileTypes: true });
97
+ } catch {
98
+ return projects; // Permission denied or inaccessible
99
+ }
100
+
101
+ // Check if current dir is a project
102
+ for (const marker of PROJECT_MARKERS) {
103
+ if (fs.existsSync(path.join(rootDir, marker.file))) {
104
+ const stack = marker.detect ? marker.detect(rootDir) : marker.stack;
105
+ projects.push({
106
+ path: rootDir,
107
+ name: getProjectName(rootDir),
108
+ stack,
109
+ status: getProjectStatus(rootDir),
110
+ bmad: hasBmadInstalled(rootDir),
111
+ hasAgentsMd: fs.existsSync(path.join(rootDir, 'AGENTS.md')),
112
+ hasGit: fs.existsSync(path.join(rootDir, '.git')),
113
+ });
114
+ return projects; // Don't recurse into project subdirs
115
+ }
116
+ }
117
+
118
+ // Also detect by .git alone (any project with version control)
119
+ if (fs.existsSync(path.join(rootDir, '.git')) && currentDepth > 0) {
120
+ projects.push({
121
+ path: rootDir,
122
+ name: getProjectName(rootDir),
123
+ stack: 'Unknown',
124
+ status: getProjectStatus(rootDir),
125
+ bmad: hasBmadInstalled(rootDir),
126
+ hasAgentsMd: fs.existsSync(path.join(rootDir, 'AGENTS.md')),
127
+ hasGit: true,
128
+ });
129
+ return projects;
130
+ }
131
+
132
+ // Recurse into subdirectories
133
+ for (const entry of entries) {
134
+ if (!entry.isDirectory()) continue;
135
+ if (SKIP_DIRS.has(entry.name)) continue;
136
+ if (entry.name.startsWith('.') && entry.name !== '.git') continue;
137
+
138
+ const subPath = path.join(rootDir, entry.name);
139
+ const subProjects = scanDirectory(subPath, maxDepth, currentDepth + 1);
140
+ projects.push(...subProjects);
141
+ }
142
+
143
+ return projects;
144
+ }
145
+
146
+ module.exports = {
147
+ command: 'scan',
148
+ description: 'Scan directories to discover and index projects in the global brain',
149
+ options: [
150
+ ['-d, --directory <path>', 'Directory to scan (default: current directory)'],
151
+ ['--depth <n>', 'Max depth to scan (default: 4)', '4'],
152
+ ['-y, --yes', 'Index all projects without prompting'],
153
+ ],
154
+ action: async (options) => {
155
+ const scanDir = path.resolve(options.directory || process.cwd());
156
+ const maxDepth = parseInt(options.depth) || 4;
157
+
158
+ clack.intro(pc.bgMagenta(pc.white(' 🧠 BMAD+ Project Scanner ')));
159
+
160
+ // Verify directory exists
161
+ if (!fs.existsSync(scanDir)) {
162
+ clack.log.error(`Directory not found: ${scanDir}`);
163
+ clack.outro(pc.red('Scan failed.'));
164
+ return;
165
+ }
166
+
167
+ // Scan
168
+ const spinner = clack.spinner();
169
+ spinner.start(`Scanning ${scanDir} (depth: ${maxDepth})...`);
170
+
171
+ const projects = scanDirectory(scanDir, maxDepth);
172
+
173
+ if (projects.length === 0) {
174
+ spinner.stop('No projects found.');
175
+ clack.outro('Try scanning a different directory or increasing --depth');
176
+ return;
177
+ }
178
+
179
+ spinner.stop(`Found ${pc.bold(projects.length)} project(s)`);
180
+
181
+ // Display table
182
+ clack.log.info('');
183
+ clack.log.info(pc.bold(' # Status BMAD+ Stack Name Path'));
184
+ clack.log.info(pc.dim(' ' + '─'.repeat(90)));
185
+
186
+ projects.forEach((p, i) => {
187
+ const num = String(i + 1).padStart(3);
188
+ const status = getStatusIcon(p.status) + ' ' + p.status.padEnd(8);
189
+ const bmad = p.bmad ? pc.green('✓') : pc.dim('·');
190
+ const stack = p.stack.padEnd(16);
191
+ const name = p.name.substring(0, 20).padEnd(20);
192
+ const projPath = p.path.length > 40 ? '...' + p.path.slice(-37) : p.path;
193
+ clack.log.info(` ${num} ${status} ${bmad} ${stack} ${name} ${pc.dim(projPath)}`);
194
+ });
195
+
196
+ clack.log.info('');
197
+
198
+ // Interactive validation
199
+ const globalBrainDir = path.join(os.homedir(), '.bmad-plus', 'brain', 'projects');
200
+
201
+ if (options.yes) {
202
+ // Auto-index all
203
+ const fsExtra = require('fs-extra');
204
+ fsExtra.ensureDirSync(globalBrainDir);
205
+
206
+ let indexed = 0;
207
+ for (const proj of projects) {
208
+ const hash = crypto.createHash('sha256').update(proj.path).digest('hex').slice(0, 8);
209
+ const meta = {
210
+ path: proj.path,
211
+ name: proj.name,
212
+ hash,
213
+ stack: proj.stack,
214
+ status: proj.status,
215
+ bmad_installed: proj.bmad,
216
+ has_git: proj.hasGit,
217
+ last_scanned: new Date().toISOString().slice(0, 10),
218
+ };
219
+ fs.writeFileSync(
220
+ path.join(globalBrainDir, `${hash}.yaml`),
221
+ Object.entries(meta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
222
+ 'utf8'
223
+ );
224
+ indexed++;
225
+ }
226
+ clack.log.success(`✅ ${indexed} project(s) indexed in ${globalBrainDir}`);
227
+ } else {
228
+ // Interactive mode
229
+ const action = await clack.select({
230
+ message: `${projects.length} project(s) found. What to do?`,
231
+ options: [
232
+ { value: 'all', label: `✅ Index all ${projects.length} projects` },
233
+ { value: 'select', label: '✏️ Select which to index' },
234
+ { value: 'none', label: '⏭️ Skip — don\'t index anything' },
235
+ ],
236
+ });
237
+
238
+ if (clack.isCancel(action) || action === 'none') {
239
+ clack.cancel('Scan cancelled.');
240
+ return;
241
+ }
242
+
243
+ const fsExtra = require('fs-extra');
244
+ fsExtra.ensureDirSync(globalBrainDir);
245
+
246
+ let toIndex = projects;
247
+
248
+ if (action === 'select') {
249
+ const selected = await clack.multiselect({
250
+ message: 'Select projects to index:',
251
+ options: projects.map((p, i) => ({
252
+ value: i,
253
+ label: `${p.name} (${p.stack})`,
254
+ hint: `${p.status} — ${p.path}`,
255
+ })),
256
+ required: false,
257
+ });
258
+
259
+ if (clack.isCancel(selected)) {
260
+ clack.cancel('Scan cancelled.');
261
+ return;
262
+ }
263
+
264
+ toIndex = selected.map(i => projects[i]);
265
+ }
266
+
267
+ let indexed = 0;
268
+ for (const proj of toIndex) {
269
+ const hash = crypto.createHash('sha256').update(proj.path).digest('hex').slice(0, 8);
270
+ const meta = {
271
+ path: proj.path,
272
+ name: proj.name,
273
+ hash,
274
+ stack: proj.stack,
275
+ status: proj.status,
276
+ bmad_installed: proj.bmad,
277
+ has_git: proj.hasGit,
278
+ last_scanned: new Date().toISOString().slice(0, 10),
279
+ };
280
+ fs.writeFileSync(
281
+ path.join(globalBrainDir, `${hash}.yaml`),
282
+ Object.entries(meta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
283
+ 'utf8'
284
+ );
285
+ indexed++;
286
+ }
287
+
288
+ clack.log.success(`✅ ${indexed} project(s) indexed in global brain`);
289
+ }
290
+
291
+ // Generate human-readable index
292
+ const indexPath = path.join(os.homedir(), '.bmad-plus', 'brain', 'projects-index.md');
293
+ const existingProjects = [];
294
+ if (fs.existsSync(globalBrainDir)) {
295
+ for (const f of fs.readdirSync(globalBrainDir)) {
296
+ if (!f.endsWith('.yaml')) continue;
297
+ try {
298
+ const content = fs.readFileSync(path.join(globalBrainDir, f), 'utf8');
299
+ const meta = {};
300
+ for (const line of content.split('\n')) {
301
+ const m = line.match(/^(\w+):\s*(.+)$/);
302
+ if (m) {
303
+ try { meta[m[1]] = JSON.parse(m[2]); } catch { meta[m[1]] = m[2]; }
304
+ }
305
+ }
306
+ existingProjects.push(meta);
307
+ } catch {}
308
+ }
309
+ }
310
+
311
+ const indexContent = [
312
+ '---',
313
+ 'title: Project Index',
314
+ `last_updated: "${new Date().toISOString().slice(0, 10)}"`,
315
+ `total_projects: ${existingProjects.length}`,
316
+ '---',
317
+ '',
318
+ '# Project Index',
319
+ '',
320
+ `> Auto-generated by \`npx bmad-plus scan\` — ${new Date().toISOString().slice(0, 10)}`,
321
+ '',
322
+ '| Status | Name | Stack | BMAD+ | Path |',
323
+ '|--------|------|-------|-------|------|',
324
+ ...existingProjects.map(p =>
325
+ `| ${p.status || '?'} | ${p.name || '?'} | ${p.stack || '?'} | ${p.bmad_installed ? '✓' : '·'} | \`${p.path || '?'}\` |`
326
+ ),
327
+ '',
328
+ ];
329
+
330
+ fs.writeFileSync(indexPath, indexContent.join('\n'), 'utf8');
331
+ clack.log.info(`📋 Project index updated: ${indexPath}`);
332
+
333
+ clack.outro(pc.green('Scan complete! 🧠'));
334
+ },
335
+ };
package/tools/cli/i18n.js CHANGED
@@ -10,7 +10,7 @@ const LANGUAGES = {
10
10
  flag: '🇬🇧',
11
11
  name: 'English',
12
12
  locale: 'en',
13
- installer_title: ' BMAD+ Installer v0.7.0 ',
13
+ installer_title: ' BMAD+ Installer v0.7.1 ',
14
14
  select_language: 'Select your language',
15
15
  installing_to: 'Installing to',
16
16
  select_packs: 'Which packs to install? (Core is always included)',
@@ -76,13 +76,23 @@ const LANGUAGES = {
76
76
  guide_example_backup: '🗂️ Backup: "/backup create" → ZIP timestamped',
77
77
  guide_example_animated: '🎬 Animated: "/animated build hero.mp4"',
78
78
  guide_example_osint: '🔍 OSINT: "Shadow, investigate John Doe"',
79
+ guide_memory: '🧠 Persistent Brain',
80
+ guide_dev_studio: '🏗️ Dev Studio',
81
+ guide_example_memory_1: '🧠 Memory: "Zecher, scan projects in D:\\DEV"',
82
+ guide_example_memory_2: '🧠 Memory: "Zecher, where were we?"',
83
+ guide_example_memory_3: '🧠 Memory: "Zecher, consolidate memory"',
84
+ guide_example_dev_studio_1: '🏗️ Dev Studio: "Miriam, brainstorm a productivity app"',
85
+ guide_example_dev_studio_2: '🏗️ Dev Studio: "Bezalel, design the architecture"',
86
+ guide_example_dev_studio_3: '🏗️ Dev Studio: "Oholiab, implement story S1"',
87
+ brain_detected: 'Existing brain detected',
88
+ brain_created: 'Global brain created',
79
89
  },
80
90
 
81
91
  fr: {
82
92
  flag: '🇫🇷',
83
93
  name: 'Français',
84
94
  locale: 'fr',
85
- installer_title: ' BMAD+ Installeur v0.7.0 ',
95
+ installer_title: ' BMAD+ Installeur v0.7.1 ',
86
96
  select_language: 'Choisissez votre langue',
87
97
  installing_to: 'Installation dans',
88
98
  select_packs: 'Quels packs installer ? (Core est toujours inclus)',
@@ -146,13 +156,23 @@ const LANGUAGES = {
146
156
  guide_example_backup: '🗂️ Backup : "/backup create" → ZIP horodaté',
147
157
  guide_example_animated: '🎬 Animé : "/animated build hero.mp4"',
148
158
  guide_example_osint: '🔍 OSINT : "Shadow, investigate John Doe"',
159
+ guide_memory: '🧠 Cerveau persistant',
160
+ guide_dev_studio: '🏗️ Dev Studio',
161
+ guide_example_memory_1: '🧠 Mémoire : "Zecher, scanne les projets dans D:\\DEV"',
162
+ guide_example_memory_2: '🧠 Mémoire : "Zecher, où en étions-nous ?"',
163
+ guide_example_memory_3: '🧠 Mémoire : "Zecher, consolide la mémoire"',
164
+ guide_example_dev_studio_1: '🏗️ Dev Studio : "Miriam, brainstorme une app de productivité"',
165
+ guide_example_dev_studio_2: '🏗️ Dev Studio : "Bezalel, conçois l\'architecture"',
166
+ guide_example_dev_studio_3: '🏗️ Dev Studio : "Oholiab, implémente la story S1"',
167
+ brain_detected: 'Cerveau existant détecté',
168
+ brain_created: 'Cerveau global créé',
149
169
  },
150
170
 
151
171
  es: {
152
172
  flag: '🇪🇸',
153
173
  name: 'Español',
154
174
  locale: 'es',
155
- installer_title: ' BMAD+ Instalador v0.7.0 ',
175
+ installer_title: ' BMAD+ Instalador v0.7.1 ',
156
176
  select_language: 'Seleccione su idioma',
157
177
  installing_to: 'Instalando en',
158
178
  select_packs: '¿Qué packs instalar? (Core siempre está incluido)',
@@ -222,7 +242,7 @@ const LANGUAGES = {
222
242
  flag: '🇩🇪',
223
243
  name: 'Deutsch',
224
244
  locale: 'de',
225
- installer_title: ' BMAD+ Installer v0.7.0 ',
245
+ installer_title: ' BMAD+ Installer v0.7.1 ',
226
246
  select_language: 'Wählen Sie Ihre Sprache',
227
247
  installing_to: 'Installiere in',
228
248
  select_packs: 'Welche Packs installieren? (Core ist immer enthalten)',
@@ -292,7 +312,7 @@ const LANGUAGES = {
292
312
  flag: '🇧🇷',
293
313
  name: 'Português (Brasil)',
294
314
  locale: 'pt-BR',
295
- installer_title: ' BMAD+ Instalador v0.7.0 ',
315
+ installer_title: ' BMAD+ Instalador v0.7.1 ',
296
316
  select_language: 'Selecione seu idioma',
297
317
  installing_to: 'Instalando em',
298
318
  select_packs: 'Quais packs instalar? (Core sempre está incluído)',
@@ -362,7 +382,7 @@ const LANGUAGES = {
362
382
  flag: '🇷🇺',
363
383
  name: 'Русский',
364
384
  locale: 'ru',
365
- installer_title: ' BMAD+ Установщик v0.7.0 ',
385
+ installer_title: ' BMAD+ Установщик v0.7.1 ',
366
386
  select_language: 'Выберите язык',
367
387
  installing_to: 'Установка в',
368
388
  select_packs: 'Какие пакеты установить? (Core всегда включён)',
@@ -432,7 +452,7 @@ const LANGUAGES = {
432
452
  flag: '🇨🇳',
433
453
  name: '中文 (简体)',
434
454
  locale: 'zh-CN',
435
- installer_title: ' BMAD+ 安装程序 v0.7.0 ',
455
+ installer_title: ' BMAD+ 安装程序 v0.7.1 ',
436
456
  select_language: '选择您的语言',
437
457
  installing_to: '安装到',
438
458
  select_packs: '安装哪些包?(Core 始终包含)',
@@ -502,7 +522,7 @@ const LANGUAGES = {
502
522
  flag: '🇮🇱',
503
523
  name: 'עברית',
504
524
  locale: 'he',
505
- installer_title: ' BMAD+ מתקין v0.7.0 ',
525
+ installer_title: ' BMAD+ מתקין v0.7.1 ',
506
526
  select_language: 'בחר את השפה שלך',
507
527
  installing_to: 'מתקין ב',
508
528
  select_packs: 'אילו חבילות להתקין? (Core תמיד כלול)',
@@ -572,7 +592,7 @@ const LANGUAGES = {
572
592
  flag: '🇯🇵',
573
593
  name: '日本語',
574
594
  locale: 'ja',
575
- installer_title: ' BMAD+ インストーラー v0.7.0 ',
595
+ installer_title: ' BMAD+ インストーラー v0.7.1 ',
576
596
  select_language: '言語を選択してください',
577
597
  installing_to: 'インストール先',
578
598
  select_packs: 'どのパックをインストールしますか?(Coreは常に含まれます)',
@@ -642,7 +662,7 @@ const LANGUAGES = {
642
662
  flag: '🇮🇹',
643
663
  name: 'Italiano',
644
664
  locale: 'it',
645
- installer_title: ' BMAD+ Installatore v0.7.0 ',
665
+ installer_title: ' BMAD+ Installatore v0.7.1 ',
646
666
  select_language: 'Seleziona la tua lingua',
647
667
  installing_to: 'Installazione in',
648
668
  select_packs: 'Quali pack installare? (Core è sempre incluso)',