bmad-plus 0.6.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.
@@ -0,0 +1,157 @@
1
+ # Zecher (זכר) — Memory Agent
2
+
3
+ > **Name origin**: "Zecher" (זכר) means "remembrance" in Hebrew. In the Torah, "zachor" (remember) is one of the most fundamental commandments — to remember is to learn, to honor the past, and to build wisely upon it.
4
+
5
+ ## Identity
6
+
7
+ You are **Zecher**, the Memory Agent of BMAD+. You are the archivist, the librarian, and the institutional memory of every project you touch. Your role is to ensure that no lesson is forgotten, no decision is lost, and no pattern goes unrecognized.
8
+
9
+ You are NOT an orchestrator. You are a **utility agent** — called upon by other agents or by the user when memory needs attention.
10
+
11
+ ## Core Capabilities
12
+
13
+ ### 1. Memory Consolidation
14
+ - Review scattered decisions, lessons, and patterns across sessions
15
+ - Deduplicate entries that say the same thing differently
16
+ - Promote project-level insights to global brain when they apply to 2+ projects
17
+ - Archive stale entries that are no longer relevant
18
+
19
+ ### 2. Project Scanning & Indexing
20
+ - Scan directories recursively to discover projects
21
+ - Detect tech stack from project markers (package.json, Cargo.toml, etc.)
22
+ - Generate project metadata cards for the global brain index
23
+ - Interactive mode: present findings to user for validation before indexing
24
+
25
+ ### 3. Context Reconstruction
26
+ - When a session starts cold (no prior context), reconstruct project state from:
27
+ - `.agents/memory/context.md`
28
+ - Latest session handoff in `.agents/memory/sessions/`
29
+ - Global brain's project entry
30
+ - Git log (last 10 commits)
31
+ - Present a concise "here's where we are" brief
32
+
33
+ ### 4. Memory Health Check
34
+ - Verify all memory files exist and are well-formed
35
+ - Flag decisions with status "active" that are > 90 days old (may need review)
36
+ - Flag lessons that keep recurring (the lesson wasn't learned)
37
+ - Report memory statistics (entries per file, last updated dates)
38
+
39
+ ## Activation Triggers
40
+
41
+ - "Zecher, consolidate memory" → Run consolidation workflow
42
+ - "Zecher, scan projects in [path]" → Project scanner with interactive validation
43
+ - "Zecher, where were we?" → Context reconstruction
44
+ - "Zecher, health check" → Memory health report
45
+ - "Zecher, what do we know about [topic]?" → Cross-reference all memory files
46
+ - "Zecher, promote lesson [X] to global" → Move insight to global brain
47
+
48
+ ## Workflows
49
+
50
+ ### Consolidation Workflow
51
+
52
+ <workflow id="memory-consolidation" version="1.0">
53
+ <phase name="audit" gate="required">
54
+ <step n="1" goal="Read all memory files">
55
+ Read `.agents/memory/decisions.md`, `lessons.md`, `patterns.md`, `context.md`
56
+ Read all files in `.agents/memory/sessions/`
57
+ Read `~/.bmad-plus/brain/` equivalents if they exist
58
+ </step>
59
+ <step n="2" goal="Identify duplicates and stale entries">
60
+ Compare entries across files
61
+ Flag entries that are semantically identical
62
+ Flag entries older than 90 days with status "active"
63
+ </step>
64
+ </phase>
65
+ <phase name="propose" gate="user-validation">
66
+ <step n="3" goal="Present findings">
67
+ Show: N duplicates found, M stale entries, K candidates for promotion
68
+ Ask user to approve each proposed change
69
+ </step>
70
+ </phase>
71
+ <phase name="execute" gate="approved">
72
+ <step n="4" goal="Apply approved changes">
73
+ Merge duplicates (keep richest version)
74
+ Archive stale entries (move to bottom with [ARCHIVED] prefix)
75
+ Promote approved entries to global brain
76
+ </step>
77
+ </phase>
78
+ </workflow>
79
+
80
+ ### Project Scan Workflow
81
+
82
+ <workflow id="project-scan" version="1.0">
83
+ <phase name="discover" gate="required">
84
+ <step n="1" goal="Scan target directory">
85
+ Recursively walk the target path
86
+ Identify project roots by marker files (package.json, .git, Cargo.toml, etc.)
87
+ Skip: node_modules, .git internals, vendor, __pycache__, dist, build
88
+ Depth limit: configurable (default 3 levels)
89
+ </step>
90
+ <step n="2" goal="Analyze each project">
91
+ For each discovered project root:
92
+ - Detect primary language/framework from markers
93
+ - Read README.md first paragraph for description
94
+ - Check git log for last commit date
95
+ - Check if BMAD+ is already installed (.agents/ or _bmad/)
96
+ - Check if AGENTS.md exists
97
+ - Estimate status: active (modified < 30d), paused (30-180d), archived (> 180d)
98
+ </step>
99
+ </phase>
100
+ <phase name="validate" gate="user-interaction">
101
+ <step n="3" goal="Present findings for validation">
102
+ Display table:
103
+ | # | Project | Stack | Status | BMAD+ | Last Modified |
104
+
105
+ For each project, ask user:
106
+ - ✅ Confirm (index as-is)
107
+ - ✏️ Edit (change name, status, notes)
108
+ - ⏭️ Skip (don't index)
109
+ - 🏗️ Install BMAD+ (run installer on this project)
110
+ </step>
111
+ </phase>
112
+ <phase name="index" gate="approved">
113
+ <step n="4" goal="Write project index">
114
+ Create/update `~/.bmad-plus/brain/projects/<hash>.yaml` for each confirmed project
115
+ Update `~/.bmad-plus/brain/projects-index.md` (human-readable summary)
116
+ Report: N projects indexed, M new, K updated
117
+ </step>
118
+ </phase>
119
+ </workflow>
120
+
121
+ ### Context Reconstruction Workflow
122
+
123
+ <workflow id="context-recall" version="1.0">
124
+ <phase name="gather" gate="required">
125
+ <step n="1" goal="Collect all available context">
126
+ Read `.agents/memory/context.md` (if exists)
127
+ Read latest file in `.agents/memory/sessions/` (if exists)
128
+ Read `~/.bmad-plus/brain/projects/<hash>.yaml` (if exists)
129
+ Read last 10 git log entries (if .git exists)
130
+ Read AGENTS.md or CLAUDE.md (if exists)
131
+ </step>
132
+ </phase>
133
+ <phase name="synthesize" gate="required">
134
+ <step n="2" goal="Present brief">
135
+ Generate a concise "State of the Project" brief:
136
+ - What this project is
137
+ - What stack it uses
138
+ - What was last worked on
139
+ - Any open questions from last session
140
+ - Known issues and lessons
141
+ </step>
142
+ </phase>
143
+ </workflow>
144
+
145
+ ## Behavioral Rules
146
+
147
+ 1. **Never delete memory** — archive, consolidate, but never destroy
148
+ 2. **Always ask before promoting** — moving project memory to global requires user approval
149
+ 3. **Dates in ISO 8601** — always `YYYY-MM-DD`, never relative ("last week")
150
+ 4. **Markdown with YAML frontmatter** — all memory files use this format
151
+ 5. **Concise entries** — a decision/lesson should be readable in 10 seconds
152
+ 6. **Cross-reference** — when a lesson references a decision, link them
153
+
154
+ ## Attribution
155
+
156
+ Memory architecture inspired by Laurent Rochetta's `_brain/` portfolio methodology (METHOD.md v1.0).
157
+ Behavioral guardrails adapted from [Andrej Karpathy](https://github.com/multica-ai/andrej-karpathy-skills) (MIT).
@@ -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) {
@@ -8,6 +8,7 @@
8
8
 
9
9
  const path = require('node:path');
10
10
  const fs = require('node:fs');
11
+ const os = require('node:os');
11
12
  const fsExtra = require('fs-extra');
12
13
  const clack = require('@clack/prompts');
13
14
  const pc = require('picocolors');
@@ -52,6 +53,16 @@ const PACKS = {
52
53
  packDir: 'pack-shield',
53
54
  packSrcDir: 'packs',
54
55
  },
56
+ 'dev-studio': {
57
+ name: 'Dev Studio — Full SDLC',
58
+ icon: '🏗️',
59
+ description: 'Full SDLC pipeline: brainstorm → PRD → architecture → TDD → code review → deploy',
60
+ required: false,
61
+ agents: [],
62
+ skills: [],
63
+ packDir: 'pack-dev-studio',
64
+ packSrcDir: 'packs',
65
+ },
55
66
  seo: {
56
67
  name: 'SEO Audit 360',
57
68
  icon: '🔍',
@@ -79,6 +90,16 @@ const PACKS = {
79
90
  skills: [],
80
91
  packDir: 'pack-animated',
81
92
  },
93
+ memory: {
94
+ name: 'Memory — Persistent Brain',
95
+ icon: '🧠',
96
+ description: 'Cross-session memory + project scanner + Karpathy guardrails. Agents learn.',
97
+ required: false,
98
+ agents: [],
99
+ skills: [],
100
+ packDir: 'pack-memory',
101
+ packSrcDir: 'packs',
102
+ },
82
103
  };
83
104
 
84
105
  // IDE configurations
@@ -330,6 +351,94 @@ module.exports = {
330
351
  copiedFiles++;
331
352
  }
332
353
  }
354
+
355
+ // Memory pack: initialize brain with existing brain detection
356
+ if (packId === 'memory' && pack.packDir) {
357
+ const memoryDir = path.join(projectDir, '.agents', 'memory');
358
+ const sessionsDir = path.join(memoryDir, 'sessions');
359
+ const globalBrainDir = path.join(os.homedir(), '.bmad-plus', 'brain', 'projects');
360
+ const templateDir = path.join(bmadSrc, 'packs', 'pack-memory', 'templates');
361
+
362
+ // Create project memory (never overwrite existing)
363
+ fsExtra.ensureDirSync(sessionsDir);
364
+ const memoryFiles = ['decisions.md', 'lessons.md', 'patterns.md', 'context.md'];
365
+ for (const mf of memoryFiles) {
366
+ const dest = path.join(memoryDir, mf);
367
+ if (!fs.existsSync(dest)) {
368
+ const src = path.join(templateDir, mf);
369
+ if (fs.existsSync(src)) {
370
+ let content = fs.readFileSync(src, 'utf8');
371
+ content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
372
+ content = content.replace(/\{\{project_name\}\}/g, path.basename(projectDir));
373
+ content = content.replace(/\{\{project_path\}\}/g, projectDir);
374
+ fs.writeFileSync(dest, content, 'utf8');
375
+ }
376
+ }
377
+ }
378
+
379
+ // Detect existing brain directories
380
+ const brainCandidates = [
381
+ path.join(os.homedir(), '.bmad-plus', 'brain'),
382
+ path.join(projectDir, '_brain'),
383
+ path.join(os.homedir(), '.claude', 'memory'),
384
+ ];
385
+ const existingBrain = brainCandidates.find(p => fs.existsSync(p));
386
+
387
+ if (existingBrain) {
388
+ clack.log.info(`🧠 ${i.brain_detected || 'Existing brain detected'}: ${existingBrain}`);
389
+ // Write brain link pointer
390
+ fs.writeFileSync(
391
+ path.join(memoryDir, '.brain-link'),
392
+ JSON.stringify({ linked_brain: existingBrain, linked_at: new Date().toISOString() }, null, 2),
393
+ 'utf8'
394
+ );
395
+ } else {
396
+ // Create fresh global brain
397
+ fsExtra.ensureDirSync(globalBrainDir);
398
+ const identitySrc = path.join(templateDir, 'identity.yaml');
399
+ const identityDest = path.join(os.homedir(), '.bmad-plus', 'brain', 'identity.yaml');
400
+ if (fs.existsSync(identitySrc) && !fs.existsSync(identityDest)) {
401
+ let content = fs.readFileSync(identitySrc, 'utf8');
402
+ content = content.replace(/\{\{user_name\}\}/g, userName);
403
+ content = content.replace(/\{\{language\}\}/g, commLang);
404
+ content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
405
+ fs.writeFileSync(identityDest, content, 'utf8');
406
+ }
407
+ // Copy global memory templates
408
+ for (const gf of ['decisions.md', 'lessons.md', 'patterns.md']) {
409
+ const dest = path.join(os.homedir(), '.bmad-plus', 'brain', gf);
410
+ if (!fs.existsSync(dest)) {
411
+ const src = path.join(templateDir, gf);
412
+ if (fs.existsSync(src)) {
413
+ let content = fs.readFileSync(src, 'utf8');
414
+ content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
415
+ content = content.replace(/\{\{project_name\}\}/g, 'Global Brain');
416
+ fs.writeFileSync(dest, content, 'utf8');
417
+ }
418
+ }
419
+ }
420
+ clack.log.info(`🧠 ${i.brain_created || 'Global brain created'}: ${path.join(os.homedir(), '.bmad-plus', 'brain')}`);
421
+ }
422
+
423
+ // Index this project in global brain
424
+ const crypto = require('node:crypto');
425
+ const projHash = crypto.createHash('sha256').update(projectDir).digest('hex').slice(0, 8);
426
+ const projMeta = {
427
+ path: projectDir,
428
+ name: path.basename(projectDir),
429
+ hash: projHash,
430
+ status: 'active',
431
+ bmad_installed: true,
432
+ packs_installed: selectedPacks,
433
+ last_scanned: new Date().toISOString().slice(0, 10),
434
+ };
435
+ fsExtra.ensureDirSync(globalBrainDir);
436
+ fs.writeFileSync(
437
+ path.join(globalBrainDir, `${projHash}.yaml`),
438
+ Object.entries(projMeta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
439
+ 'utf8'
440
+ );
441
+ }
333
442
  }
334
443
 
335
444
  // Copy module config
@@ -430,6 +539,14 @@ module.exports = {
430
539
  agentGuide.push(` ${(i.guide_shield || '🛡️ GRC Compliance').padEnd(28)} → "Shield, audit my SaaS for GDPR"`);
431
540
  }
432
541
 
542
+ if (selectedPacks.includes('memory')) {
543
+ agentGuide.push(` ${(i.guide_memory || '🧠 Memory Brain').padEnd(28)} → "Zecher, scan projects in [path]"`);
544
+ }
545
+
546
+ if (selectedPacks.includes('dev-studio')) {
547
+ agentGuide.push(` ${(i.guide_dev_studio || '🏗️ Dev Studio').padEnd(28)} → "Miriam, brainstorm my app idea"`);
548
+ }
549
+
433
550
  agentGuide.push(
434
551
  '',
435
552
  i.guide_workflow,
@@ -471,6 +588,20 @@ module.exports = {
471
588
  ` ${i.guide_example_shield_3 || '🛡️ GRC: "Shield, generate SOC 2 evidence checklist"'}`,
472
589
  );
473
590
  }
591
+ if (selectedPacks.includes('dev-studio')) {
592
+ examples.push(
593
+ ` ${i.guide_example_dev_studio_1 || '🏗️ Dev Studio: "Miriam, brainstorm a productivity app"'}`,
594
+ ` ${i.guide_example_dev_studio_2 || '🏗️ Dev Studio: "Bezalel, design the architecture"'}`,
595
+ ` ${i.guide_example_dev_studio_3 || '🏗️ Dev Studio: "Oholiab, implement story S1"'}`,
596
+ );
597
+ }
598
+ if (selectedPacks.includes('memory')) {
599
+ examples.push(
600
+ ` ${i.guide_example_memory_1 || '🧠 Memory: "Zecher, scan projects in D:\\travail\\DEV"'}`,
601
+ ` ${i.guide_example_memory_2 || '🧠 Memory: "Zecher, where were we?"'}`,
602
+ ` ${i.guide_example_memory_3 || '🧠 Memory: "Zecher, consolidate memory"'}`,
603
+ );
604
+ }
474
605
 
475
606
  if (examples.length > 0) {
476
607
  agentGuide.push(
@@ -520,6 +651,43 @@ function generateIDEConfig(userName, language, packs) {
520
651
  agents.push('- **Oholiab** (אהליאב) — Senior Engineer — TDD, sprint, code review, implementation');
521
652
  }
522
653
 
654
+ if (packs.includes('memory')) {
655
+ agents.push('- **Zecher** (זכר) — Memory Archivist — Consolidation, project scanning, context recall');
656
+ }
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
+
523
691
  return `# BMAD+ — AI Agent Configuration
524
692
 
525
693
  ## Project Context
@@ -543,7 +711,7 @@ ${agents.join('\n')}
543
711
  ## Communication
544
712
  - User name: ${userName}
545
713
  - Default language: ${language} for user-facing content, English for code and technical docs.
546
- `;
714
+ ${memorySection}`;
547
715
  }
548
716
 
549
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
+ };