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.
- package/CHANGELOG.md +39 -0
- package/README.md +144 -144
- package/package.json +1 -1
- package/readme-international/README.de.md +125 -125
- package/readme-international/README.es.md +215 -215
- package/readme-international/README.fr.md +213 -213
- package/src/bmad-plus/module.yaml +13 -0
- package/src/bmad-plus/packs/pack-memory/README.md +106 -0
- package/src/bmad-plus/packs/pack-memory/memory-orchestrator.md +79 -0
- package/src/bmad-plus/packs/pack-memory/shared/karpathy-guardrails.md +86 -0
- package/src/bmad-plus/packs/pack-memory/shared/memory-protocol.md +143 -0
- package/src/bmad-plus/packs/pack-memory/templates/context.md +39 -0
- package/src/bmad-plus/packs/pack-memory/templates/decisions.md +25 -0
- package/src/bmad-plus/packs/pack-memory/templates/identity.yaml +39 -0
- package/src/bmad-plus/packs/pack-memory/templates/lessons.md +31 -0
- package/src/bmad-plus/packs/pack-memory/templates/patterns.md +24 -0
- package/src/bmad-plus/packs/pack-memory/templates/session-handoff.md +25 -0
- package/src/bmad-plus/packs/pack-memory/zecher-agent.md +157 -0
- package/tools/cli/bmad-plus-cli.js +22 -0
- package/tools/cli/commands/install.js +169 -1
- package/tools/cli/commands/memory.js +194 -0
- package/tools/cli/commands/scan.js +335 -0
- package/tools/cli/i18n.js +30 -10
|
@@ -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
|
+
};
|