bmad-plus 0.12.0 → 0.12.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/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.12.0",
4
+ "version": "0.12.1",
5
5
  "description": "BMAD+ — Augmented AI-Driven Development Framework with multi-role agents, autopilot, and parallel execution",
6
6
  "keywords": [
7
7
  "bmad",
@@ -45,10 +45,9 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@clack/prompts": "^1.4.0",
48
- "chalk": "^4.1.2",
49
48
  "commander": "^14.0.0",
50
- "fs-extra": "^11.3.5",
51
- "js-yaml": "^4.1.0",
49
+ "fs-extra": "^11.3.6",
50
+ "js-yaml": "^5.2.1",
52
51
  "picocolors": "^1.1.1"
53
52
  },
54
53
  "engines": {
@@ -83,7 +82,8 @@
83
82
  "testEnvironment": "node"
84
83
  },
85
84
  "devDependencies": {
86
- "eslint": "^9.0.0",
85
+ "@eslint/js": "^10.0.1",
86
+ "eslint": "^10.6.0",
87
87
  "husky": "^9.0.0",
88
88
  "jest": "^30.4.2",
89
89
  "prettier": "^3.4.0"
@@ -1,6 +1,6 @@
1
1
  # 🚀 BMAD+ — Erweitertes Multi-Agent KI-Framework
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.9.0-blue.svg)](../CHANGELOG.md)
3
+ [![Version](https://img.shields.io/badge/version-0.12.0-blue.svg)](../CHANGELOG.md)
4
4
  [![Based on](https://img.shields.io/badge/based%20on-BMAD--METHOD-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
 
@@ -8,7 +8,7 @@
8
8
  <a href="../README.md">English</a> | <a href="README.fr.md">Français</a> | <a href="README.es.md">Español</a> | 🌐 <b>Deutsch</b>
9
9
  </div>
10
10
 
11
- > **56+ Multirole-Agenten · 9 modulare Packs · Autopilot-Modus · Parallele Ausführung · 143 Tests**
11
+ > **56+ Multirole-Agenten · 9 modulare Packs · Autopilot-Modus · Parallele Ausführung · 333 Tests**
12
12
  > Intelligenter Fork von [BMAD-METHOD](https://github.com/bmad-code-org/BMAD-METHOD) — Selbstaktivierende Agenten mit 3-Stufen-Kontexterkennung, GRC-Compliance (Shield), vollständige SDLC-Pipeline (Dev Studio), OSINT-Intelligence, SEO-Audit, persistentes Cross-Session-Gedächtnis und 10-Sprachen CLI-Installer.
13
13
 
14
14
  ---
@@ -556,8 +556,13 @@ BMAD+/
556
556
  | **0.4.4** | 2026-05-17 | 🔧 UTF-8-Kodierungsfix, vollständige i18n 10 Sprachen, 62 Unit-Tests |
557
557
  | **0.5.0** | 2026-05-17 | 🛡️ **Pack Shield** — 38 GRC-Compliance-Agenten, 7 Kategorien, 25+ Frameworks (DSGVO, ISO 27001, SOC 2, EU AI Act...) |
558
558
  | **0.6.0** | 2026-05-17 | 🏗️ **Pack Dev Studio** — 6 SDLC-Agenten (Miriam, Yosef, Bezalel...) + 30 SDLC-Workflows, BWML DSL |
559
- | **0.9.0** | 2026-06-24 | 🚀 **Erweitert & Sicher** — 3 neue Packs (animated, backup, seo), P0 Sicherheitsbehebung, 143/143 Tests |
560
- | **0.8.0** | 2026-06-24 | 🚀 **Erweitert & Sicher** — 3 neue Packs (animated, backup, seo), P0 Sicherheitsbehebung, 143/143 Tests |
559
+ | **0.8.0** | 2026-06-24 | 🚀 **Erweitert** — 3 neue Packs (animated, backup, seo), Sicherheits-Remediation, i18n-Sync |
560
+ | **0.9.0** | 2026-06-24 | 🔒 **Sicher** — Audit-Remediation-Zyklus, npm audit sauber, gemeinsames Pack-Registry (`lib/packs.js`) |
561
+ | **0.9.1** | 2026-07-01 | ✅ **Glaubwürdigkeit** — echte CI bei Push/PR, Jest-ESM-Harness repariert, ESLint-Flat-Config, falsche Badges entfernt |
562
+ | **0.9.2** | 2026-07-01 | 🔐 **Sicherheit** — RCE (ci_cd-Allowlist), SSRF, Stored/DOM-XSS, XXE, Python-CVE-Bumps (~30 Findings) |
563
+ | **0.10.0** | 2026-07-02 | 🧭 **Plattform-Rückgrat** — SSOT `registry.yaml` (generiertes `packs.js` + CI-Drift-Gate), Python-Provisioning, Eval- + Memory-Fundament |
564
+ | **0.11.0** | 2026-07-02 | 🧠 **Karpathy-Schleife** — `bmad mem` recall/write/reinforce, Multi-CLI-Adapter-Generator, OSINT-Rechtsgrundlagen-Gate (CG-02) |
565
+ | **0.12.0** | 2026-07-02 | 🏛️ **Load-bearing** — Root-Adapter aus dem Registry adoptiert, Memory über MCP, provider-neutrales Eval-Gateway; 333 Tests |
561
566
 
562
567
  Siehe [CHANGELOG.md](../CHANGELOG.md) für vollständige Details.
563
568
 
@@ -1,6 +1,6 @@
1
1
  # 🚀 BMAD+ — Framework Multi-Agente IA Aumentado
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.9.0-blue.svg)](../CHANGELOG.md)
3
+ [![Version](https://img.shields.io/badge/version-0.12.0-blue.svg)](../CHANGELOG.md)
4
4
  [![Based on](https://img.shields.io/badge/based%20on-BMAD--METHOD-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
 
@@ -8,7 +8,7 @@
8
8
  <a href="../README.md">English</a> | <a href="README.fr.md">Français</a> | 🌐 <b>Español</b> | <a href="README.de.md">Deutsch</a>
9
9
  </div>
10
10
 
11
- > **56+ agentes multirrol · 9 packs modulares · Modo Autopilot · Ejecución paralela · 143 tests**
11
+ > **56+ agentes multirrol · 9 packs modulares · Modo Autopilot · Ejecución paralela · 333 tests**
12
12
  > Fork inteligente de [BMAD-METHOD](https://github.com/bmad-code-org/BMAD-METHOD) — Agentes auto-activables con detección contextual en 3 niveles, cumplimiento GRC (Shield), pipeline SDLC completo (Dev Studio), inteligencia OSINT, auditoría SEO, memoria persistente cross-session e instalador CLI en 10 idiomas.
13
13
 
14
14
  ---
@@ -571,8 +571,13 @@ BMAD+/
571
571
  | **0.4.4** | 2026-05-17 | 🔧 Corrección codificación UTF-8, i18n completa 10 idiomas, 62 tests unitarios |
572
572
  | **0.5.0** | 2026-05-17 | 🛡️ **Pack Shield** — 38 agentes de cumplimiento GRC, 7 categorías, 25+ frameworks (GDPR, ISO 27001, SOC 2, EU AI Act...) |
573
573
  | **0.6.0** | 2026-05-17 | 🏗️ **Pack Dev Studio** — 6 agentes SDLC (Miriam, Yosef, Bezalel...) + 30 flujos de trabajo SDLC, BWML DSL |
574
- | **0.9.0** | 2026-06-24 | 🚀 **Aumentado & Seguro** — 3 nuevos packs (animated, backup, seo), corrección P0 seguridad, 143/143 tests |
575
- | **0.8.0** | 2026-06-24 | 🚀 **Aumentado & Seguro** — 3 nuevos packs (animated, backup, seo), corrección P0 seguridad, 143/143 tests |
574
+ | **0.8.0** | 2026-06-24 | 🚀 **Aumentado** — 3 nuevos packs (animated, backup, seo), pase de remediación de seguridad, sync i18n |
575
+ | **0.9.0** | 2026-06-24 | 🔒 **Seguro** — ciclo de remediación de auditoría, npm audit limpio, registro de packs compartido (`lib/packs.js`) |
576
+ | **0.9.1** | 2026-07-01 | ✅ **Credibilidad** — CI real en push/PR, arnés Jest ESM reparado, config ESLint flat, badges falsos retirados |
577
+ | **0.9.2** | 2026-07-01 | 🔐 **Seguridad** — RCE (allowlist ci_cd), SSRF, XSS almacenado/DOM, XXE, bumps CVE Python (~30 hallazgos) |
578
+ | **0.10.0** | 2026-07-02 | 🧭 **Columna vertebral de plataforma** — SSOT `registry.yaml` (`packs.js` generado + gate anti-drift en CI), provisioning Python, fundaciones eval + memoria |
579
+ | **0.11.0** | 2026-07-02 | 🧠 **Bucle Karpathy** — `bmad mem` recall/write/reinforce, generador de adapters multi-CLI, gate de base legal OSINT (CG-02) |
580
+ | **0.12.0** | 2026-07-02 | 🏛️ **Load-bearing** — adapters raíz adoptados desde el registry, memoria vía MCP, gateway eval multi-proveedor; 333 tests |
576
581
 
577
582
  Más descripciones a fondo en el archivo: [CHANGELOG.md](../CHANGELOG.md).
578
583
 
@@ -1,6 +1,6 @@
1
1
  # 🚀 BMAD+ — Framework Multi-Agent IA Augmenté
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.9.0-blue.svg)](../CHANGELOG.md)
3
+ [![Version](https://img.shields.io/badge/version-0.12.0-blue.svg)](../CHANGELOG.md)
4
4
  [![Based on](https://img.shields.io/badge/based%20on-BMAD--METHOD-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
 
@@ -8,7 +8,7 @@
8
8
  <a href="../README.md">English</a> | 🌐 <b>Français</b> | <a href="README.es.md">Español</a> | <a href="README.de.md">Deutsch</a>
9
9
  </div>
10
10
 
11
- > **56+ agents multi-rôles · 9 packs modulaires · Mode Autopilot · Exécution parallèle · 143 tests**
11
+ > **56+ agents multi-rôles · 9 packs modulaires · Mode Autopilot · Exécution parallèle · 333 tests**
12
12
  > Fork intelligent de [BMAD-METHOD](https://github.com/bmad-code-org/BMAD-METHOD) — Agents auto-activables avec détection contextuelle 3 niveaux, conformité GRC (Shield), pipeline SDLC complet (Dev Studio), intelligence OSINT, audit SEO, mémoire persistante cross-session, et installeur CLI 10 langues.
13
13
 
14
14
  ---
@@ -571,8 +571,13 @@ BMAD+/
571
571
  | **0.4.4** | 2026-05-17 | 🔧 Correction encodage UTF-8, i18n complète 10 langues, 62 tests unitaires |
572
572
  | **0.5.0** | 2026-05-17 | 🛡️ **Pack Shield** — 38 agents de conformité GRC, 7 catégories, 25+ frameworks (RGPD, ISO 27001, SOC 2, EU AI Act...) |
573
573
  | **0.6.0** | 2026-05-17 | 🏗️ **Pack Dev Studio** — 6 agents SDLC (Miriam, Yosef, Bezalel...) + 30 workflows SDLC, BWML DSL |
574
- | **0.9.0** | 2026-06-24 | 🚀 **Augmenté & Sécurisé** — 3 nouveaux packs (animated, backup, seo), correction P0 sécurité, 143/143 tests |
575
- | **0.8.0** | 2026-06-24 | 🚀 **Augmenté & Sécurisé** — 3 nouveaux packs (animated, backup, seo), correction P0 sécurité, 143/143 tests |
574
+ | **0.8.0** | 2026-06-24 | 🚀 **Augmenté** — 3 nouveaux packs (animated, backup, seo), passe de remédiation sécurité, sync i18n |
575
+ | **0.9.0** | 2026-06-24 | 🔒 **Sécurisé** — cycle de remédiation d'audit, npm audit propre, registre de packs partagé (`lib/packs.js`) |
576
+ | **0.9.1** | 2026-07-01 | ✅ **Crédibilité** — vraie CI sur push/PR, harnais Jest ESM réparé, config ESLint flat, faux badges retirés |
577
+ | **0.9.2** | 2026-07-01 | 🔐 **Sécurité** — RCE (allowlist ci_cd), SSRF, XSS stocké/DOM, XXE, bumps CVE Python (~30 findings) |
578
+ | **0.10.0** | 2026-07-02 | 🧭 **Épine dorsale plateforme** — SSOT `registry.yaml` (`packs.js` généré + gate anti-drift CI), provisioning Python, fondations eval + mémoire |
579
+ | **0.11.0** | 2026-07-02 | 🧠 **Boucle Karpathy** — `bmad mem` recall/write/reinforce, générateur d'adapters multi-CLI, gate base légale OSINT (CG-02) |
580
+ | **0.12.0** | 2026-07-02 | 🏛️ **Load-bearing** — adapters racine adoptés depuis le registry, mémoire via MCP, gateway eval multi-provider ; 333 tests |
576
581
 
577
582
  Voir [CHANGELOG.md](../CHANGELOG.md) pour le détail complet.
578
583
 
@@ -29,6 +29,20 @@
29
29
  └── <hash>.yaml ← Per-project metadata
30
30
  ```
31
31
 
32
+ ### Portfolio Brain (workspace-level, takes precedence)
33
+
34
+ If a `_brain/` directory exists in the project directory or any ancestor (e.g. `D:\workspace\_brain\` above `D:\workspace\clients\my-project\`), install links the project to it instead of `~/.bmad-plus/brain`. The `BMAD_PLUS_BRAIN` env var overrides all auto-detection.
35
+
36
+ The kind of brain that was linked is recorded in `.brain-link`:
37
+
38
+ ```json
39
+ { "linked_brain": "D:\\workspace\\_brain", "brain_type": "portfolio", "linked_at": "…" }
40
+ ```
41
+
42
+ `brain_type` values: `portfolio` (workspace `_brain/` or env var) · `bmad-global` (`~/.bmad-plus/brain`) · `claude-memory` (`~/.claude/memory`).
43
+
44
+ **Rule for agents**: a portfolio brain has its **own** conventions and layout — read its entry point (`README.md` / `AGENTS.md` / `HOT.md`) first and follow them. Do NOT assume the BMAD+ global layout above; in particular do not create `identity.yaml` or `projects/` inside a portfolio brain uninvited.
45
+
32
46
  ---
33
47
 
34
48
  ## Protocol: Session Start (READ)
@@ -100,7 +100,7 @@ Agents MUST follow these behavioral principles:
100
100
 
101
101
  ## Registry facts (computed from registry.yaml — never hand-typed)
102
102
 
103
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
103
+ - Product: BMAD+ v0.12.1 (derived from BMAD-METHOD v6.6.0)
104
104
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
105
105
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
106
106
  - Installer agents (14 across all packs):
@@ -105,7 +105,7 @@ Agents MUST follow these behavioral principles:
105
105
 
106
106
  ## Registry facts (computed from registry.yaml — never hand-typed)
107
107
 
108
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
108
+ - Product: BMAD+ v0.12.1 (derived from BMAD-METHOD v6.6.0)
109
109
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
110
110
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
111
111
  - Installer agents (14 across all packs):
@@ -100,7 +100,7 @@ Agents MUST follow these behavioral principles:
100
100
 
101
101
  ## Registry facts (computed from registry.yaml — never hand-typed)
102
102
 
103
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
103
+ - Product: BMAD+ v0.12.1 (derived from BMAD-METHOD v6.6.0)
104
104
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
105
105
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
106
106
  - Installer agents (14 across all packs):
@@ -98,7 +98,7 @@ Agents MUST follow these behavioral principles:
98
98
 
99
99
  ## Registry facts (computed from registry.yaml — never hand-typed)
100
100
 
101
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
101
+ - Product: BMAD+ v0.12.1 (derived from BMAD-METHOD v6.6.0)
102
102
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
103
103
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
104
104
  - Installer agents (14 across all packs):
@@ -101,7 +101,7 @@ Agents MUST follow these behavioral principles:
101
101
 
102
102
  ## Registry facts (computed from registry.yaml — never hand-typed)
103
103
 
104
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
104
+ - Product: BMAD+ v0.12.1 (derived from BMAD-METHOD v6.6.0)
105
105
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
106
106
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
107
107
  - Installer agents (14 across all packs):
@@ -100,7 +100,7 @@ Agents MUST follow these behavioral principles:
100
100
 
101
101
  ## Registry facts (computed from registry.yaml — never hand-typed)
102
102
 
103
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
103
+ - Product: BMAD+ v0.12.1 (derived from BMAD-METHOD v6.6.0)
104
104
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
105
105
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
106
106
  - Installer agents (14 across all packs):
@@ -105,7 +105,7 @@ Agents MUST follow these behavioral principles:
105
105
 
106
106
  ## Registry facts (computed from registry.yaml — never hand-typed)
107
107
 
108
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
108
+ - Product: BMAD+ v0.12.1 (derived from BMAD-METHOD v6.6.0)
109
109
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
110
110
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
111
111
  - Installer agents (14 across all packs):
@@ -264,8 +264,7 @@ module.exports = {
264
264
  clack.log.info('');
265
265
 
266
266
  // Confirm
267
- let confirmed = options.yes;
268
- if (!confirmed) {
267
+ if (!options.yes) {
269
268
  const answer = await clack.confirm({
270
269
  message: `Install ${packs.length} recommended packs?`,
271
270
  initialValue: true,
@@ -274,7 +273,6 @@ module.exports = {
274
273
  clack.cancel('Autoconfig cancelled.');
275
274
  return;
276
275
  }
277
- confirmed = true;
278
276
  }
279
277
 
280
278
  // Run install
@@ -235,9 +235,15 @@ module.exports = {
235
235
  copiedSkills += result.copiedSkills;
236
236
  copiedFiles += result.copiedFiles;
237
237
 
238
- // Memory pack: initialize brain with existing brain detection
238
+ // Memory pack: initialize brain with existing brain detection.
239
+ // Non-fatal: a read-only or missing home dir (containers, CI) must not
240
+ // abort the install mid-flight and leave a manifest-less partial tree.
239
241
  if (packId === 'memory' && pack.packDir) {
240
- initMemory({ projectDir, bmadSrc, userName, commLang, selectedPacks });
242
+ try {
243
+ initMemory({ projectDir, bmadSrc, userName, commLang, selectedPacks });
244
+ } catch (err) {
245
+ clack.log.warn(`Memory/brain initialization failed (non-fatal, install continues): ${err.message}`);
246
+ }
241
247
  }
242
248
  }
243
249
 
@@ -9,10 +9,10 @@
9
9
  const path = require('node:path');
10
10
  const fs = require('node:fs');
11
11
  const os = require('node:os');
12
- const crypto = require('node:crypto');
13
12
  const clack = require('@clack/prompts');
14
13
  const pc = require('picocolors');
15
14
  const { PROJECT_MARKERS } = require('../lib/stack-detect');
15
+ const { normalizePathForHash, projectHash } = require('../lib/path-hash');
16
16
 
17
17
  // Directories to skip during scanning
18
18
  const SKIP_DIRS = new Set([
@@ -118,22 +118,6 @@ 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
-
137
121
  /**
138
122
  * Index a single project in the global brain by writing its metadata YAML file.
139
123
  * @param {object} project - Project metadata object
@@ -141,7 +125,7 @@ function normalizePathForHash(p) {
141
125
  * @returns {void}
142
126
  */
143
127
  function indexProject(project, globalBrainDir) {
144
- const hash = crypto.createHash('sha256').update(normalizePathForHash(project.path)).digest('hex').slice(0, 8);
128
+ const hash = projectHash(project.path);
145
129
  const meta = {
146
130
  path: project.path,
147
131
  name: project.name,
@@ -1,113 +1,204 @@
1
- /**
2
- * BMAD+ Memory Initialization Module
3
- * Extracted from install.js — initializes brain with existing brain detection.
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 fsExtra = require('fs-extra');
12
- const clack = require('@clack/prompts');
13
-
14
- /**
15
- * Initialize the memory pack: create project memory files, detect or create global brain.
16
- *
17
- * @param {object} opts
18
- * @param {string} opts.projectDir - Target project directory
19
- * @param {string} opts.bmadSrc - Path to src/bmad-plus/
20
- * @param {string} opts.userName - User display name
21
- * @param {string} opts.commLang - Communication language
22
- * @param {string[]} opts.selectedPacks - Array of pack IDs being installed
23
- * @param {object} opts.i - i18n translations object
24
- * @returns {void}
25
- */
26
- function initMemory({ projectDir, bmadSrc, userName, commLang, selectedPacks }) {
27
- const memoryDir = path.join(projectDir, '.agents', 'memory');
28
- const sessionsDir = path.join(memoryDir, 'sessions');
29
- const globalBrainDir = path.join(os.homedir(), '.bmad-plus', 'brain', 'projects');
30
- const templateDir = path.join(bmadSrc, 'packs', 'pack-memory', 'templates');
31
-
32
- // Create project memory (never overwrite existing)
33
- fsExtra.ensureDirSync(sessionsDir);
34
- const memoryFiles = ['decisions.md', 'lessons.md', 'patterns.md', 'context.md'];
35
- for (const mf of memoryFiles) {
36
- const dest = path.join(memoryDir, mf);
37
- if (!fs.existsSync(dest)) {
38
- const src = path.join(templateDir, mf);
39
- if (fs.existsSync(src)) {
40
- let content = fs.readFileSync(src, 'utf8');
41
- content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
42
- content = content.replace(/\{\{project_name\}\}/g, path.basename(projectDir));
43
- content = content.replace(/\{\{project_path\}\}/g, projectDir);
44
- fs.writeFileSync(dest, content, 'utf8');
45
- }
46
- }
47
- }
48
-
49
- // Detect existing brain directories
50
- const brainCandidates = [
51
- path.join(os.homedir(), '.bmad-plus', 'brain'),
52
- path.join(projectDir, '_brain'),
53
- path.join(os.homedir(), '.claude', 'memory'),
54
- ];
55
- const existingBrain = brainCandidates.find(p => fs.existsSync(p));
56
-
57
- if (existingBrain) {
58
- clack.log.info(`🧠 Existing brain detected: ${existingBrain}`);
59
- // Write brain link pointer
60
- fs.writeFileSync(
61
- path.join(memoryDir, '.brain-link'),
62
- JSON.stringify({ linked_brain: existingBrain, linked_at: new Date().toISOString() }, null, 2),
63
- 'utf8'
64
- );
65
- } else {
66
- // Create fresh global brain
67
- fsExtra.ensureDirSync(globalBrainDir);
68
- const identitySrc = path.join(templateDir, 'identity.yaml');
69
- const identityDest = path.join(os.homedir(), '.bmad-plus', 'brain', 'identity.yaml');
70
- if (fs.existsSync(identitySrc) && !fs.existsSync(identityDest)) {
71
- let content = fs.readFileSync(identitySrc, 'utf8');
72
- content = content.replace(/\{\{user_name\}\}/g, userName);
73
- content = content.replace(/\{\{language\}\}/g, commLang);
74
- content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
75
- fs.writeFileSync(identityDest, content, 'utf8');
76
- }
77
- // Copy global memory templates
78
- for (const gf of ['decisions.md', 'lessons.md', 'patterns.md']) {
79
- const dest = path.join(os.homedir(), '.bmad-plus', 'brain', gf);
80
- if (!fs.existsSync(dest)) {
81
- const src = path.join(templateDir, gf);
82
- if (fs.existsSync(src)) {
83
- let content = fs.readFileSync(src, 'utf8');
84
- content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
85
- content = content.replace(/\{\{project_name\}\}/g, 'Global Brain');
86
- fs.writeFileSync(dest, content, 'utf8');
87
- }
88
- }
89
- }
90
- clack.log.info(`🧠 Global brain created: ${path.join(os.homedir(), '.bmad-plus', 'brain')}`);
91
- }
92
-
93
- // Index this project in global brain
94
- const crypto = require('node:crypto');
95
- const projHash = crypto.createHash('sha256').update(projectDir).digest('hex').slice(0, 8);
96
- const projMeta = {
97
- path: projectDir,
98
- name: path.basename(projectDir),
99
- hash: projHash,
100
- status: 'active',
101
- bmad_installed: true,
102
- packs_installed: selectedPacks,
103
- last_scanned: new Date().toISOString().slice(0, 10),
104
- };
105
- fsExtra.ensureDirSync(globalBrainDir);
106
- fs.writeFileSync(
107
- path.join(globalBrainDir, `${projHash}.yaml`),
108
- Object.entries(projMeta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
109
- 'utf8'
110
- );
111
- }
112
-
113
- module.exports = { initMemory };
1
+ /**
2
+ * BMAD+ Memory Initialization Module
3
+ * Extracted from install.js — initializes brain with existing brain detection.
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 fsExtra = require('fs-extra');
12
+ const clack = require('@clack/prompts');
13
+ const { projectHash } = require('./path-hash');
14
+
15
+ /**
16
+ * True when `p` exists AND is a directory. A stray *file* named `_brain` (or a
17
+ * file-valued BMAD_PLUS_BRAIN) must never be linked as a brain.
18
+ * @param {string} p
19
+ * @returns {boolean}
20
+ */
21
+ function isBrainCandidateDir(p) {
22
+ try {
23
+ return fs.statSync(p).isDirectory();
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Resolve the brain directory a project should link to.
31
+ *
32
+ * Priority order:
33
+ * 1. `BMAD_PLUS_BRAIN` env var — explicit override, wins everywhere
34
+ * 2. A `_brain/` directory in the project dir or any ancestor (portfolio brain
35
+ * shared by sibling projects under a common workspace root)
36
+ * 3. `<home>/.bmad-plus/brain` BMAD+ global brain
37
+ * 4. `<home>/.claude/memory` — Claude Code persistent memory, only when it is
38
+ * a non-empty directory (an empty shell is not evidence of a real memory)
39
+ *
40
+ * @param {string} projectDir - Absolute path of the target project
41
+ * @param {string} [homeDir] - Home directory (injectable for tests; defaults to os.homedir())
42
+ * @returns {{dir: string, type: 'portfolio'|'bmad-global'|'claude-memory'}|null}
43
+ */
44
+ function resolveBrain(projectDir, homeDir = os.homedir()) {
45
+ const envBrain = process.env.BMAD_PLUS_BRAIN;
46
+ if (envBrain) {
47
+ if (isBrainCandidateDir(envBrain)) return { dir: envBrain, type: 'portfolio' };
48
+ clack.log.warn(`BMAD_PLUS_BRAIN is set but is not a directory: ${envBrain} — falling back to auto-detection`);
49
+ }
50
+
51
+ let dir = path.resolve(projectDir);
52
+ for (;;) {
53
+ const candidate = path.join(dir, '_brain');
54
+ if (isBrainCandidateDir(candidate)) return { dir: candidate, type: 'portfolio' };
55
+ const parent = path.dirname(dir);
56
+ if (parent === dir) break;
57
+ dir = parent;
58
+ }
59
+
60
+ const globalBrain = path.join(homeDir, '.bmad-plus', 'brain');
61
+ if (isBrainCandidateDir(globalBrain)) return { dir: globalBrain, type: 'bmad-global' };
62
+
63
+ const claudeMemory = path.join(homeDir, '.claude', 'memory');
64
+ if (isBrainCandidateDir(claudeMemory)) {
65
+ let entries;
66
+ try {
67
+ entries = fs.readdirSync(claudeMemory);
68
+ } catch {
69
+ entries = [];
70
+ }
71
+ if (entries.length > 0) return { dir: claudeMemory, type: 'claude-memory' };
72
+ }
73
+
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Idempotently ensure the global brain is COMPLETE: identity.yaml, the global
79
+ * memory files, and projects/. Creates only what is missing, never overwrites.
80
+ *
81
+ * This must run before ANY write into the global brain: older versions wrote
82
+ * the projects/ index unconditionally while identity creation lived in a
83
+ * branch that was skipped whenever any brain was detected — leaving a
84
+ * permanent "half brain" (projects/ but no identity.yaml) that resolveBrain
85
+ * then detected as a valid global brain forever.
86
+ *
87
+ * @param {string} brainRoot - Path to <home>/.bmad-plus/brain
88
+ * @param {string} templateDir - Path to pack-memory templates
89
+ * @param {string} userName - User display name (identity.yaml)
90
+ * @param {string} commLang - Communication language (identity.yaml)
91
+ * @returns {void}
92
+ */
93
+ function ensureGlobalBrain(brainRoot, templateDir, userName, commLang) {
94
+ fsExtra.ensureDirSync(path.join(brainRoot, 'projects'));
95
+
96
+ const identitySrc = path.join(templateDir, 'identity.yaml');
97
+ const identityDest = path.join(brainRoot, 'identity.yaml');
98
+ if (fs.existsSync(identitySrc) && !fs.existsSync(identityDest)) {
99
+ let content = fs.readFileSync(identitySrc, 'utf8');
100
+ content = content.replace(/\{\{user_name\}\}/g, userName);
101
+ content = content.replace(/\{\{language\}\}/g, commLang);
102
+ content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
103
+ fs.writeFileSync(identityDest, content, 'utf8');
104
+ }
105
+
106
+ for (const gf of ['decisions.md', 'lessons.md', 'patterns.md']) {
107
+ const dest = path.join(brainRoot, gf);
108
+ if (!fs.existsSync(dest)) {
109
+ const src = path.join(templateDir, gf);
110
+ if (fs.existsSync(src)) {
111
+ let content = fs.readFileSync(src, 'utf8');
112
+ content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
113
+ content = content.replace(/\{\{project_name\}\}/g, 'Global Brain');
114
+ fs.writeFileSync(dest, content, 'utf8');
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Initialize the memory pack: create project memory files, detect or create global brain.
122
+ *
123
+ * @param {object} opts
124
+ * @param {string} opts.projectDir - Target project directory
125
+ * @param {string} opts.bmadSrc - Path to src/bmad-plus/
126
+ * @param {string} opts.userName - User display name
127
+ * @param {string} opts.commLang - Communication language
128
+ * @param {string[]} opts.selectedPacks - Array of pack IDs being installed
129
+ * @param {string} [opts.homeDir] - Home directory (injectable for tests; defaults to os.homedir())
130
+ * @returns {void}
131
+ */
132
+ function initMemory({ projectDir, bmadSrc, userName, commLang, selectedPacks, homeDir = os.homedir() }) {
133
+ const memoryDir = path.join(projectDir, '.agents', 'memory');
134
+ const sessionsDir = path.join(memoryDir, 'sessions');
135
+ const globalBrainRoot = path.join(homeDir, '.bmad-plus', 'brain');
136
+ const templateDir = path.join(bmadSrc, 'packs', 'pack-memory', 'templates');
137
+
138
+ // Create project memory (never overwrite existing)
139
+ fsExtra.ensureDirSync(sessionsDir);
140
+ const memoryFiles = ['decisions.md', 'lessons.md', 'patterns.md', 'context.md'];
141
+ for (const mf of memoryFiles) {
142
+ const dest = path.join(memoryDir, mf);
143
+ if (!fs.existsSync(dest)) {
144
+ const src = path.join(templateDir, mf);
145
+ if (fs.existsSync(src)) {
146
+ let content = fs.readFileSync(src, 'utf8');
147
+ content = content.replace(/\{\{date\}\}/g, new Date().toISOString().slice(0, 10));
148
+ content = content.replace(/\{\{project_name\}\}/g, path.basename(projectDir));
149
+ content = content.replace(/\{\{project_path\}\}/g, projectDir);
150
+ fs.writeFileSync(dest, content, 'utf8');
151
+ }
152
+ }
153
+ }
154
+
155
+ // Detect existing brain (env var > portfolio _brain walk-up > global > claude)
156
+ const detected = resolveBrain(projectDir, homeDir);
157
+
158
+ let linkedBrain;
159
+ if (detected) {
160
+ clack.log.info(`🧠 Existing brain detected (${detected.type}): ${detected.dir}`);
161
+ linkedBrain = detected;
162
+ } else {
163
+ ensureGlobalBrain(globalBrainRoot, templateDir, userName, commLang);
164
+ clack.log.info(`🧠 Global brain created: ${globalBrainRoot}`);
165
+ linkedBrain = { dir: globalBrainRoot, type: 'bmad-global' };
166
+ }
167
+
168
+ // Write the brain link pointer in EVERY case — including first-ever creation,
169
+ // so a fresh machine's first install is linked exactly like the second one.
170
+ fs.writeFileSync(
171
+ path.join(memoryDir, '.brain-link'),
172
+ JSON.stringify(
173
+ { linked_brain: linkedBrain.dir, brain_type: linkedBrain.type, linked_at: new Date().toISOString() },
174
+ null,
175
+ 2
176
+ ),
177
+ 'utf8'
178
+ );
179
+
180
+ // Index this project in the global brain — only when the global brain IS the
181
+ // linked brain. Portfolio and claude-memory brains keep their own registries
182
+ // (see pack-memory/shared/memory-protocol.md); writing into ~/.bmad-plus from
183
+ // those installs is what used to create orphaned "half brains".
184
+ if (linkedBrain.type === 'bmad-global') {
185
+ ensureGlobalBrain(globalBrainRoot, templateDir, userName, commLang);
186
+ const projHash = projectHash(projectDir);
187
+ const projMeta = {
188
+ path: projectDir,
189
+ name: path.basename(projectDir),
190
+ hash: projHash,
191
+ status: 'active',
192
+ bmad_installed: true,
193
+ packs_installed: selectedPacks,
194
+ last_scanned: new Date().toISOString().slice(0, 10),
195
+ };
196
+ fs.writeFileSync(
197
+ path.join(globalBrainRoot, 'projects', `${projHash}.yaml`),
198
+ Object.entries(projMeta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n'),
199
+ 'utf8'
200
+ );
201
+ }
202
+ }
203
+
204
+ module.exports = { initMemory, resolveBrain, ensureGlobalBrain };