bmad-plus 0.13.0 → 0.16.0

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +113 -487
  3. package/SECURITY.md +71 -0
  4. package/THIRD-PARTY-LICENSES.md +349 -0
  5. package/osint-agent-package/README.md +1 -1
  6. package/package.json +14 -3
  7. package/readme-international/README.de.md +18 -8
  8. package/readme-international/README.es.md +19 -9
  9. package/readme-international/README.fr.md +18 -8
  10. package/src/bmad-plus/agents/agent-architect-dev/SKILL.md +11 -13
  11. package/src/bmad-plus/agents/agent-orchestrator/SKILL.md +148 -9
  12. package/src/bmad-plus/agents/agent-quality/SKILL.md +41 -11
  13. package/src/bmad-plus/data/role-triggers.yaml +19 -0
  14. package/src/bmad-plus/module-help.csv +1 -0
  15. package/src/bmad-plus/module.yaml +1 -0
  16. package/src/bmad-plus/packs/pack-dev-studio/README.md +133 -141
  17. package/src/bmad-plus/packs/pack-dev-studio/SKILL.md +49 -0
  18. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/analyst-agent.md +35 -60
  19. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/document-project.md +59 -59
  20. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/domain-research.md +55 -93
  21. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/market-research.md +58 -93
  22. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/prfaq.md +55 -132
  23. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/product-brief.md +63 -78
  24. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/tech-writer-agent.md +54 -69
  25. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/technical-research.md +54 -93
  26. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/architect-agent.md +32 -60
  27. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-architecture.md +67 -71
  28. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-epics-stories.md +61 -90
  29. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/generate-project-context.md +56 -78
  30. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/implementation-readiness.md +55 -88
  31. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01-init.md +20 -153
  32. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01b-continue.md +20 -173
  33. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-02-context.md +14 -220
  34. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-03-starter.md +20 -329
  35. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-04-decisions.md +15 -314
  36. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-05-patterns.md +15 -355
  37. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-06-structure.md +15 -375
  38. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-07-validation.md +14 -357
  39. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-08-complete.md +13 -78
  40. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/checkpoint-preview.md +52 -65
  41. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-01-gather-context.md +14 -81
  42. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-02-review.md +14 -31
  43. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-03-triage.md +14 -45
  44. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-04-present.md +13 -128
  45. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review.md +61 -87
  46. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/correct-course.md +55 -298
  47. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/create-story.md +54 -426
  48. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-agent.md +48 -69
  49. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story-checklist.md +24 -80
  50. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story.md +64 -482
  51. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/investigate.md +50 -184
  52. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/qa-e2e-tests.md +57 -173
  53. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/quick-dev.md +56 -108
  54. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/retrospective.md +54 -1509
  55. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-planning.md +54 -296
  56. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-status.md +41 -283
  57. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-prd.md +58 -18
  58. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-ux-design.md +103 -72
  59. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/edit-prd.md +55 -27
  60. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/pm-agent.md +34 -60
  61. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/prd.md +46 -87
  62. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/steps/step-01-init.md +10 -0
  63. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/ux-designer-agent.md +30 -60
  64. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/validate-prd.md +57 -27
  65. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/advanced-elicitation.md +47 -138
  66. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/adversarial-review.md +48 -34
  67. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/bmad-help.md +51 -68
  68. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/brainstorming.md +46 -3
  69. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/customize.md +68 -109
  70. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/distillator.md +53 -174
  71. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/edge-case-hunter.md +39 -53
  72. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-prose.md +45 -83
  73. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-structure.md +45 -176
  74. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/index-docs.md +45 -63
  75. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/party-mode.md +53 -124
  76. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/shard-doc.md +44 -100
  77. package/src/bmad-plus/packs/pack-dev-studio/dev-studio-orchestrator.md +56 -116
  78. package/src/bmad-plus/packs/pack-dev-studio/shared/architecture-decision-template.md +49 -12
  79. package/src/bmad-plus/packs/pack-dev-studio/shared/bwml-spec.md +51 -328
  80. package/src/bmad-plus/packs/pack-dev-studio/shared/catalog.json +489 -0
  81. package/src/bmad-plus/packs/pack-dev-studio/shared/execution.md +69 -0
  82. package/src/bmad-plus/packs/pack-dev-studio/shared/module-help.csv +39 -32
  83. package/src/bmad-plus/packs/pack-dev-studio/upstream-sync.yaml +85 -14
  84. package/src/bmad-plus/packs/pack-memory/README.md +35 -4
  85. package/src/bmad-plus/packs/pack-memory/memory-orchestrator.md +33 -6
  86. package/src/bmad-plus/packs/pack-memory/shared/karpathy-guardrails.md +3 -3
  87. package/src/bmad-plus/packs/pack-memory/shared/memory-protocol.md +27 -3
  88. package/src/bmad-plus/packs/pack-memory/zecher-agent.md +18 -2
  89. package/src/bmad-plus/packs/pack-seo/SKILL.md +27 -1
  90. package/src/bmad-plus/packs/pack-seo/seo-chief.md +16 -1
  91. package/src/bmad-plus/packs/pack-seo/seo-judge.md +12 -0
  92. package/src/bmad-plus/packs/pack-seo/seo-scout.md +12 -0
  93. package/src/bmad-plus/skills/bmad-plus-autopilot/SKILL.md +49 -12
  94. package/src/bmad-plus/skills/bmad-plus-parallel/SKILL.md +17 -3
  95. package/src/bmad-plus/skills/bmad-plus-sync/SKILL.md +76 -65
  96. package/src/bmad-plus/skills/bmad-plus-uat/SKILL.md +144 -0
  97. package/src/bmad-plus/skills/bmad-plus-uat/ref/uat-results.schema.json +60 -0
  98. package/src/bmad-plus/skills/bmad-plus-uat/ref/uat-spec.schema.json +121 -0
  99. package/src/bmad-plus/skills/bmad-plus-uat/ref/uat-triage.schema.json +60 -0
  100. package/src/bmad-plus/skills/bmad-plus-uat/template/page.html +552 -0
  101. package/src/bmad-plus/skills/bmad-plus-uat/template/strings.json +362 -0
  102. package/src/bmad-plus/skills/dev-studio/SKILL.md +19 -0
  103. package/tools/build/check-install-contract.js +367 -17
  104. package/tools/build/generate.js +229 -40
  105. package/tools/build/generated-adapters/.codex/AGENTS.md +1 -1
  106. package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +1 -1
  107. package/tools/build/generated-adapters/.opencode/AGENTS.md +1 -1
  108. package/tools/build/generated-adapters/AGENTS.md +1 -1
  109. package/tools/build/generated-adapters/CLAUDE.md +1 -1
  110. package/tools/build/generated-adapters/CONVENTIONS.md +1 -1
  111. package/tools/build/generated-adapters/GEMINI.md +1 -1
  112. package/tools/build/pack-delivery.js +78 -0
  113. package/tools/cli/bmad-plus-cli.js +15 -12
  114. package/tools/cli/commands/doctor.js +50 -189
  115. package/tools/cli/commands/install.js +22 -3
  116. package/tools/cli/commands/memory-journal-cmd.js +119 -19
  117. package/tools/cli/commands/nexus.js +111 -0
  118. package/tools/cli/commands/studio.js +68 -0
  119. package/tools/cli/commands/uat.js +389 -0
  120. package/tools/cli/lib/README-memory-journal.md +19 -8
  121. package/tools/cli/lib/installation-health.js +366 -0
  122. package/tools/cli/lib/memory-journal.js +0 -0
  123. package/tools/cli/lib/memory-outcomes.js +293 -0
  124. package/tools/cli/lib/memory-store.js +139 -0
  125. package/tools/cli/lib/nexus-process.js +377 -0
  126. package/tools/cli/lib/nexus.js +1532 -0
  127. package/tools/cli/lib/pack-copy.js +39 -11
  128. package/tools/cli/lib/packs.js +134 -11
  129. package/tools/cli/lib/python-health.js +233 -0
  130. package/tools/cli/lib/python-provision.js +2 -2
  131. package/tools/cli/lib/studio.js +310 -0
  132. package/tools/cli/lib/uat.js +869 -0
  133. package/tools/maintain/upstream-candidate.js +456 -0
  134. package/tools/release/publication-content.js +903 -0
  135. package/tools/release/supply-chain.js +282 -0
@@ -1,205 +1,66 @@
1
- /**
2
- * BMAD+ Doctor Command
3
- * Checks installation integrity and reports issues
4
- *
5
- * Author: Laurent Rochetta
6
- */
7
-
8
- const path = require('node:path');
9
- const fs = require('node:fs');
1
+ /** Human and JSON output share one report; runtime execution is explicitly opt-in. */
10
2
  const clack = require('@clack/prompts');
11
3
  const pc = require('picocolors');
12
- const { t } = require('../i18n');
13
- const { EXPECTED_AGENTS } = require('../lib/packs');
14
- const { readInstallManifest } = require('../lib/install-manifest');
4
+ const { collectInstallationHealth } = require('../lib/installation-health');
15
5
 
16
6
  module.exports = {
17
7
  command: 'doctor',
18
- description: 'Check BMAD+ installation integrity',
8
+ description: 'Check BMAD+ installation integrity and declared capabilities',
19
9
  options: [
20
10
  ['-d, --directory <path>', 'Project directory (default: current directory)'],
21
11
  ['-l, --lang <code>', 'Language code: en, fr, es, de, pt-br, ru, zh, he, ja, it'],
12
+ ['--json', 'Print the diagnostic report as JSON (read-only unless a probe is requested)'],
13
+ ['--verify-python', 'Execute installed Python interpreters to verify versions and imports'],
22
14
  ],
23
- action: async (options) => {
24
- const projectDir = path.resolve(options.directory || process.cwd());
25
- const packageJson = require('../../../package.json');
26
-
27
- clack.intro(pc.bgBlue(pc.white(` BMAD+ Doctor v${packageJson.version} `)));
28
-
29
- let checks = 0;
30
- let passed = 0;
31
- let warnings = 0;
32
- let errors = 0;
33
-
34
- // ── Check 1: Installation manifest ──
35
- checks++;
36
- const manifestPath = path.join(projectDir, '_bmad', '.bmad-plus-install.json');
37
- if (!fs.existsSync(manifestPath)) {
38
- clack.log.error('❌ BMAD+ is not installed in this directory');
39
- clack.outro(pc.red('Run `npx bmad-plus install` first.'));
40
- return;
41
- }
42
-
43
- let manifest;
44
- try {
45
- manifest = readInstallManifest(manifestPath);
46
- } catch (err) {
47
- const i = t(options.lang || 'en');
48
- clack.log.error(i.manifest_invalid(err.message));
49
- clack.outro(pc.red(i.manifest_repair));
50
- process.exitCode = 1;
15
+ action: async (options = {}) => {
16
+ const report = collectInstallationHealth({
17
+ projectDir: options.directory,
18
+ verifyPython: Boolean(options.verifyPython),
19
+ });
20
+ process.exitCode = report.summary.errors ? 1 : 0;
21
+ if (options.json) {
22
+ console.log(JSON.stringify(report, null, 2));
51
23
  return;
52
24
  }
53
- clack.log.success(`✅ Manifest found — v${manifest.version}`);
54
- passed++;
55
-
56
- // ── Check 2: Version comparison ──
57
- checks++;
58
- if (manifest.version === packageJson.version) {
59
- clack.log.success(`✅ Version up to date (v${manifest.version})`);
60
- passed++;
61
- } else {
62
- clack.log.warn(`⚠️ Version mismatch: installed v${manifest.version} → latest v${packageJson.version}`);
63
- clack.log.info(' Run `npx bmad-plus update` to upgrade');
64
- warnings++;
25
+ clack.intro(pc.bgBlue(pc.white(' BMAD+ Doctor v' + report.versions.cli + ' ')));
26
+ for (const entry of report.checks.filter(
27
+ (item) => item.status !== 'pass' || item.code === 'manifest.valid'
28
+ )) {
29
+ const log =
30
+ entry.status === 'error'
31
+ ? clack.log.error
32
+ : entry.status === 'warning'
33
+ ? clack.log.warn
34
+ : clack.log.success;
35
+ log(
36
+ entry.message +
37
+ (entry.path && !entry.message.includes(entry.path) ? ' (' + entry.path + ')' : '')
38
+ );
65
39
  }
66
-
67
- // ── Check 3: Agent files ──
68
- checks++;
69
- const agentsDir = path.join(projectDir, '.agents', 'skills');
70
- if (fs.existsSync(agentsDir)) {
71
- const agentDirs = fs.readdirSync(agentsDir, { withFileTypes: true })
72
- .filter(d => d.isDirectory())
73
- .map(d => d.name);
74
- clack.log.success(`✅ ${agentDirs.length} agent/skill directories found`);
75
- passed++;
76
-
77
- // Check each expected agent from manifest packs using shared EXPECTED_AGENTS module
78
- for (const pack of (manifest.packs || ['core'])) {
79
- const entry = EXPECTED_AGENTS[pack];
80
- if (!entry) continue;
81
-
82
- // Check individual agent directories
83
- for (const agent of (entry.agents || [])) {
84
- checks++;
85
- const agentPath = path.join(agentsDir, agent);
86
- if (fs.existsSync(agentPath)) {
87
- passed++;
88
- } else {
89
- clack.log.warn(`⚠️ Missing agent: ${agent} (pack: ${pack})`);
90
- warnings++;
91
- }
92
- }
93
-
94
- // Check pack directory if applicable
95
- if (entry.packDir) {
96
- checks++;
97
- const packPath = path.join(agentsDir, entry.packDir);
98
- if (fs.existsSync(packPath)) {
99
- passed++;
100
-
101
- // These packs bundle their agents as files inside packDir — verify
102
- // them there (not as loose directories, which would falsely warn).
103
- for (const agentFile of (entry.packAgents || [])) {
104
- checks++;
105
- if (fs.existsSync(path.join(packPath, agentFile))) {
106
- passed++;
107
- } else {
108
- clack.log.warn(`⚠️ Missing agent: ${agentFile} (pack: ${pack})`);
109
- warnings++;
110
- }
111
- }
112
- } else {
113
- clack.log.warn(`⚠️ Missing pack directory: ${entry.packDir} (pack: ${pack})`);
114
- warnings++;
115
- }
116
- }
117
- }
118
- } else {
119
- clack.log.error('❌ No .agents/skills/ directory found');
120
- errors++;
40
+ if (report.versions.installed) {
41
+ clack.note(
42
+ [
43
+ report.summary.passed + '/' + report.checks.length + ' checks passed',
44
+ report.summary.errors + ' error(s), ' + report.summary.warnings + ' warning(s)',
45
+ 'Installed: v' + report.versions.installed + ' | Executing CLI: v' + report.versions.cli,
46
+ 'Ownership inventory: ' + report.inventory.status,
47
+ 'Integrations use host-managed execution; host capabilities were not probed.',
48
+ 'Optional Nexus process execution requires an explicit plan; executable availability was not probed.',
49
+ ].join('\n'),
50
+ 'BMAD+ Health Report'
51
+ );
121
52
  }
122
-
123
- // ── Check 4: Config files ──
124
- checks++;
125
- const configPath = path.join(projectDir, '_bmad', 'config.yaml');
126
- if (fs.existsSync(configPath)) {
127
- clack.log.success('✅ config.yaml present');
128
- passed++;
129
- } else {
130
- clack.log.error('❌ config.yaml missing');
131
- errors++;
132
- }
133
-
134
- // ── Check 5: Module config ──
135
- checks++;
136
- const moduleYaml = path.join(projectDir, '_bmad', 'module.yaml');
137
- if (fs.existsSync(moduleYaml)) {
138
- clack.log.success('✅ module.yaml present');
139
- passed++;
140
- } else {
141
- clack.log.warn('⚠️ module.yaml missing');
142
- warnings++;
143
- }
144
-
145
- // ── Check 6: IDE configs ──
146
- checks++;
147
- const ideFiles = ['CLAUDE.md', 'GEMINI.md', 'AGENTS.md'].filter(f =>
148
- fs.existsSync(path.join(projectDir, f))
53
+ clack.log.info(
54
+ 'Published version is not checked here. Use `bmad-plus update-check --json` to check npm.'
55
+ );
56
+ clack.outro(
57
+ report.status === 'ok'
58
+ ? pc.green('Installation checks passed.')
59
+ : pc.yellow(
60
+ options.verifyPython
61
+ ? 'Review the reported evidence. Python import probes ran; no provisioning was requested.'
62
+ : 'Review the reported evidence before changing files. Doctor did not modify the project.'
63
+ )
149
64
  );
150
- if (ideFiles.length > 0) {
151
- clack.log.success(`✅ ${ideFiles.length} IDE config(s): ${ideFiles.join(', ')}`);
152
- passed++;
153
- } else {
154
- clack.log.warn('⚠️ No IDE config files found');
155
- warnings++;
156
- }
157
-
158
- // ── Check 7: Output directories ──
159
- checks++;
160
- const outputDir = path.join(projectDir, '_bmad-output');
161
- if (fs.existsSync(outputDir)) {
162
- clack.log.success('✅ _bmad-output/ directory exists');
163
- passed++;
164
- } else {
165
- clack.log.warn('⚠️ _bmad-output/ not found');
166
- warnings++;
167
- }
168
-
169
- // ── Check 8: Skills integrity (SKILL.md exists in pack dirs) ──
170
- if (manifest.packs && manifest.packs.includes('seo')) {
171
- checks++;
172
- const seoSkill = path.join(agentsDir, 'pack-seo', 'SKILL.md');
173
- if (fs.existsSync(seoSkill)) {
174
- clack.log.success('✅ SEO Engine SKILL.md present');
175
- passed++;
176
- } else {
177
- clack.log.error('❌ SEO Engine SKILL.md missing — /seo audit will not work');
178
- errors++;
179
- }
180
- }
181
-
182
- // ── Summary ──
183
- const summaryColor = errors > 0 ? pc.red : warnings > 0 ? pc.yellow : pc.green;
184
- const summaryIcon = errors > 0 ? '❌' : warnings > 0 ? '⚠️' : '✅';
185
-
186
- clack.note([
187
- `${summaryIcon} ${passed}/${checks} checks passed`,
188
- errors > 0 ? `❌ ${errors} error(s)` : null,
189
- warnings > 0 ? `⚠️ ${warnings} warning(s)` : null,
190
- '',
191
- `📦 Version: v${manifest.version}`,
192
- `📅 Installed: ${manifest.installed.split('T')[0]}`,
193
- `🗣️ Language: ${manifest.language || 'N/A'}`,
194
- `📦 Packs: ${(manifest.packs || ['core']).join(', ')}`,
195
- ].filter(Boolean).join('\n'), '🩺 BMAD+ Health Report');
196
-
197
- clack.outro(summaryColor(
198
- errors > 0
199
- ? 'Issues found — run `npx bmad-plus install` to fix'
200
- : warnings > 0
201
- ? 'Minor issues found — consider running `npx bmad-plus update`'
202
- : 'Everything looks great! 🎉'
203
- ));
204
65
  },
205
66
  };
@@ -35,7 +35,7 @@ module.exports = {
35
35
  ['-l, --lang <code>', 'Language code: en, fr, es, de, pt-br, ru, zh, he, ja, it (overrides auto-detection)'],
36
36
  ['--tools <tools>', 'Comma-separated IDE IDs, all, or none (default: auto-detect)'],
37
37
  ['--mode <mode>', 'Execution mode: manual, autopilot, hybrid (default: manual)'],
38
- ['--provision-python', 'Provision an isolated Python env for packs that need one (seo, memory)'],
38
+ ['--provision-python', 'Provision isolated environments for selected Python packs'],
39
39
  ],
40
40
  action: async (options) => {
41
41
  // A closed/piped stdin cannot answer prompts. Require explicit defaults
@@ -180,6 +180,7 @@ module.exports = {
180
180
  let userName = process.env.USER || process.env.USERNAME || 'Developer';
181
181
  let commLang = 'French';
182
182
  let execMode = options.mode || 'manual';
183
+ let uatEnvironment = '';
183
184
 
184
185
  if (!options.yes) {
185
186
  const userConfig = await clack.group({
@@ -192,6 +193,13 @@ module.exports = {
192
193
  message: i.comm_language,
193
194
  options: getCommLanguageOptions(),
194
195
  }),
196
+ uatEnvironment: () => clack.text({
197
+ message:
198
+ i.uat_environment ||
199
+ 'Test environment for human acceptance recipes (URL, blank if none)',
200
+ placeholder: 'https://demo.example.com',
201
+ defaultValue: '',
202
+ }),
195
203
  execMode: () => clack.select({
196
204
  message: i.exec_mode,
197
205
  options: [
@@ -215,6 +223,7 @@ module.exports = {
215
223
  userName = validatedName;
216
224
  commLang = userConfig.commLang;
217
225
  execMode = options.mode || userConfig.execMode;
226
+ uatEnvironment = String(userConfig.uatEnvironment || '').trim();
218
227
  }
219
228
 
220
229
  const releaseLock = acquireProjectLock(projectDir);
@@ -366,7 +375,7 @@ module.exports = {
366
375
  }
367
376
 
368
377
  // ── Step 6: Create config.yaml ──
369
- const configYaml = generateConfigYaml(userName, commLang, projectDir, execMode);
378
+ const configYaml = generateConfigYaml(userName, commLang, projectDir, execMode, uatEnvironment);
370
379
  const configPath = path.join(targetBmadDir, 'config.yaml');
371
380
  fs.writeFileSync(configPath, configYaml, 'utf8');
372
381
 
@@ -522,7 +531,7 @@ module.exports = {
522
531
  _internal: { writeIDEConfigs, generateConfigYaml },
523
532
  };
524
533
 
525
- function generateConfigYaml(userName, language, projectDir, execMode = 'manual') {
534
+ function generateConfigYaml(userName, language, projectDir, execMode = 'manual', uatEnvironment = '') {
526
535
  if (!['manual', 'autopilot', 'hybrid'].includes(execMode)) throw new Error('Unknown execution mode: ' + execMode);
527
536
  return '# BMAD+ Project Configuration\n# Generated by bmad-plus install\n\n' + yaml.dump({
528
537
  user_name: userName,
@@ -539,5 +548,15 @@ function generateConfigYaml(userName, language, projectDir, execMode = 'manual')
539
548
  story: execMode === 'autopilot' ? 'notify_only' : 'require_approval',
540
549
  delivery: 'require_approval',
541
550
  },
551
+ // Human acceptance recipes. advisory: every delivery produces and offers its page and
552
+ // nothing is ever blocked. gate: the delivery waits for a passing "bmad-plus uat gate".
553
+ // off: no recipe, and the delivery report says so.
554
+ uat: {
555
+ mode: 'advisory',
556
+ granularity: 'delivery',
557
+ environment: { name: uatEnvironment ? 'TEST' : '', url: uatEnvironment || '' },
558
+ page_budget: { max_steps: 15, max_minutes: 30 },
559
+ dir: '_bmad-output/uat',
560
+ },
542
561
  }, { lineWidth: -1, forceQuotes: true, quotingType: '"' });
543
562
  }
@@ -32,6 +32,8 @@
32
32
  const path = require('node:path');
33
33
  const fs = require('node:fs');
34
34
  const mj = require('../lib/memory-journal');
35
+ const store = require('../lib/memory-store');
36
+ const outcomes = require('../lib/memory-outcomes');
35
37
 
36
38
  // ── Pattern score store ──────────────────────────────────────────────────────
37
39
  // Lives next to the journal in the north-star scope (.bmad/memory/). Keyed by
@@ -63,9 +65,7 @@ function readScores(baseDir) {
63
65
  }
64
66
 
65
67
  function writeScores(baseDir, scores) {
66
- const file = scoresPath(baseDir);
67
- fs.mkdirSync(path.dirname(file), { recursive: true });
68
- fs.writeFileSync(file, JSON.stringify(scores, null, 2) + '\n', 'utf8');
68
+ store.writeJson(baseDir, SCORES_RELPATH, scores);
69
69
  }
70
70
 
71
71
  // ── Flag parsing helpers ─────────────────────────────────────────────────────
@@ -104,12 +104,30 @@ function truncate(text, max = 120) {
104
104
  * @returns {object[]} ranked entries (same shape as memory-journal.recall)
105
105
  */
106
106
  function runRecall(query, opts = {}) {
107
- const { baseDir = process.cwd(), scope = 'project', portfolioDir = null, limit = 8, now = null, json = false, log = console.log } = opts;
107
+ const {
108
+ baseDir = process.cwd(),
109
+ scope = 'project',
110
+ portfolioDir = null,
111
+ limit = 8,
112
+ now = null,
113
+ json = false,
114
+ log = console.log,
115
+ ranking = 'lexical',
116
+ contextScope,
117
+ } = opts;
108
118
 
109
- const results = mj.recall(query, { baseDir, scope, portfolioDir, limit, now });
119
+ const results = mj.recall(query, {
120
+ baseDir,
121
+ scope,
122
+ portfolioDir,
123
+ limit,
124
+ now,
125
+ ranking,
126
+ contextScope,
127
+ });
110
128
 
111
129
  if (json) {
112
- log(JSON.stringify({ query, scope, count: results.length, results }, null, 2));
130
+ log(JSON.stringify({ query, scope, ranking, count: results.length, results }, null, 2));
113
131
  return results;
114
132
  }
115
133
 
@@ -119,7 +137,8 @@ function runRecall(query, opts = {}) {
119
137
  }
120
138
  log(`mem recall: ${results.length} match(es) for "${query}" (scope: ${scope})`);
121
139
  results.forEach((r, i) => {
122
- const origin = r.kind === 'event' ? `event ${r.source}#${r.ref}` : `note ${r.source} § ${r.ref}`;
140
+ const origin =
141
+ r.kind === 'event' ? `event ${r.source}#${r.ref}` : `note ${r.source} § ${r.ref}`;
123
142
  log(` ${i + 1}. [${r.score.toFixed(3)}] ${origin}`);
124
143
  log(` ${truncate(r.text)}`);
125
144
  });
@@ -134,7 +153,18 @@ function runRecall(query, opts = {}) {
134
153
  * @returns {object} the persisted event
135
154
  */
136
155
  function runWrite(opts = {}) {
137
- const { baseDir = process.cwd(), agent, cli = null, model = null, task = '', outcome = undefined, signals = {}, now, json = false, log = console.log } = opts;
156
+ const {
157
+ baseDir = process.cwd(),
158
+ agent,
159
+ cli = null,
160
+ model = null,
161
+ task = '',
162
+ outcome = undefined,
163
+ signals = {},
164
+ now,
165
+ json = false,
166
+ log = console.log,
167
+ } = opts;
138
168
 
139
169
  const event = mj.appendEvent(baseDir, {
140
170
  ts: now,
@@ -150,7 +180,9 @@ function runWrite(opts = {}) {
150
180
  log(JSON.stringify({ written: event }, null, 2));
151
181
  } else {
152
182
  log(`mem write: event ${event.id} appended (${event.ts})`);
153
- log(` agent=${event.agent} cli=${event.cli || '-'} model=${event.model || '-'} outcome=${event.outcome || '-'}`);
183
+ log(
184
+ ` agent=${event.agent} cli=${event.cli || '-'} model=${event.model || '-'} outcome=${event.outcome || '-'}`
185
+ );
154
186
  if (event.task) log(` task: ${truncate(event.task)}`);
155
187
  }
156
188
  return event;
@@ -168,10 +200,24 @@ function runWrite(opts = {}) {
168
200
  * @returns {{patternId:string, reward:number, previous:object, next:object, promotion:object|null}}
169
201
  */
170
202
  function runReinforce(opts = {}) {
171
- const { baseDir = process.cwd(), patternId, signals = {}, evidence = [], now, json = false, log = console.log } = opts;
203
+ return store.withMemoryLock(opts.baseDir || process.cwd(), () => reinforceUnderLock(opts));
204
+ }
205
+
206
+ function reinforceUnderLock(opts = {}) {
207
+ const {
208
+ baseDir = process.cwd(),
209
+ patternId,
210
+ signals = {},
211
+ evidence = [],
212
+ now,
213
+ json = false,
214
+ log = console.log,
215
+ } = opts;
172
216
 
173
217
  if (typeof patternId !== 'string' || patternId.trim().length === 0) {
174
- throw new TypeError('mem reinforce: --pattern <id> is required (the pattern heading in patterns.md)');
218
+ throw new TypeError(
219
+ 'mem reinforce: --pattern <id> is required (the pattern heading in patterns.md)'
220
+ );
175
221
  }
176
222
 
177
223
  const reward = mj.computeReward(signals);
@@ -198,7 +244,15 @@ function runReinforce(opts = {}) {
198
244
  );
199
245
  }
200
246
 
201
- const result = { patternId, reward, previous, next, promotion };
247
+ const result = {
248
+ patternId,
249
+ reward,
250
+ previous,
251
+ next,
252
+ promotion,
253
+ evidenceStatus: 'self-reported',
254
+ rankingEligible: false,
255
+ };
202
256
 
203
257
  if (json) {
204
258
  log(JSON.stringify(result, null, 2));
@@ -209,12 +263,16 @@ function runReinforce(opts = {}) {
209
263
  log(`mem reinforce: pattern "${patternId}"`);
210
264
  log(` reward ${reward.toFixed(3)} (from ${JSON.stringify(signals)})`);
211
265
  log(` elo ${prevElo.toFixed(1)} -> ${next.elo.toFixed(1)}`);
212
- log(` mean ${next.mean.toFixed(3)} (alpha=${next.alpha.toFixed(2)}, beta=${next.beta.toFixed(2)}, updates=${next.updates})`);
266
+ log(
267
+ ` mean ${next.mean.toFixed(3)} (alpha=${next.alpha.toFixed(2)}, beta=${next.beta.toFixed(2)}, updates=${next.updates})`
268
+ );
213
269
  if (promotion) {
214
270
  log(` promotion PROPOSED (${promotion.id}): ${promotion.fromStatus} -> ${promotion.toStatus}`);
215
271
  log(` awaiting human/Shield approval — never auto-applied (governance guard)`);
216
272
  } else {
217
- log(` promotion: none proposed (needs mean >= ${PROMOTION_MEAN_THRESHOLD} and >= ${PROMOTION_MIN_UPDATES} updates)`);
273
+ log(
274
+ ` promotion: none proposed (needs mean >= ${PROMOTION_MEAN_THRESHOLD} and >= ${PROMOTION_MIN_UPDATES} updates)`
275
+ );
218
276
  }
219
277
  return result;
220
278
  }
@@ -223,14 +281,20 @@ function runReinforce(opts = {}) {
223
281
 
224
282
  module.exports = {
225
283
  command: 'mem <action> [query...]',
226
- description: 'Karpathy memory loop — recall | write | reinforce (portable across agent CLIs)',
284
+ description: 'Project memory — recall | write | reinforce | observe | outcomes',
227
285
  options: [
228
286
  ['-d, --directory <path>', 'Project directory (default: current directory)'],
229
287
  ['--scope <scope>', 'Recall scope: project | portfolio', 'project'],
230
288
  ['--portfolio <path>', 'Portfolio brain directory (used with --scope portfolio)'],
231
289
  ['--limit <n>', 'Max recall results', '8'],
290
+ ['--ranking <mode>', 'Recall ranking: lexical | evidence (project only)', 'lexical'],
291
+ ['--context-scope <paths>', 'Comma-separated project paths for evidence recall'],
292
+ ['--input <file>', 'Project-relative JSON outcome observation input (observe)'],
232
293
  ['--agent <name>', 'Agent that produced the event (write)'],
233
- ['--cli <name>', 'Driving CLI: claude-code, gemini-cli, codex-cli, cursor, opencode, aider, antigravity'],
294
+ [
295
+ '--cli <name>',
296
+ 'Driving CLI: claude-code, gemini-cli, codex-cli, cursor, opencode, aider, antigravity',
297
+ ],
234
298
  ['--model <id>', 'Model id used (model-agnostic: claude/gpt/gemini/local)'],
235
299
  ['--task <text>', 'What was attempted (write)'],
236
300
  ['--outcome <outcome>', 'success | failure | partial | abandoned (write)'],
@@ -245,6 +309,8 @@ module.exports = {
245
309
  recall: 'Ranked retrieval over the journal + memory files',
246
310
  write: 'Append a structured event to .bmad/memory/journal.ndjson',
247
311
  reinforce: 'Apply a reward to a pattern score (promotions PROPOSED only)',
312
+ observe: 'Record accepted current Nexus evidence for an exact memory section',
313
+ outcomes: 'Inspect stored outcomes and their current eligibility',
248
314
  },
249
315
 
250
316
  action: async (action, query, options = {}) => {
@@ -264,6 +330,12 @@ module.exports = {
264
330
  limit: Number(options.limit || 8),
265
331
  now,
266
332
  json,
333
+ ranking: options.ranking || 'lexical',
334
+ contextScope: options.contextScope
335
+ ? String(options.contextScope)
336
+ .split(',')
337
+ .map((s) => s.trim())
338
+ : undefined,
267
339
  });
268
340
  } else if (action === 'write') {
269
341
  runWrite({
@@ -282,16 +354,44 @@ module.exports = {
282
354
  baseDir,
283
355
  patternId: options.pattern,
284
356
  signals: parseSignals(options),
285
- evidence: options.evidence ? String(options.evidence).split(',').map(s => s.trim()).filter(Boolean) : [],
357
+ evidence: options.evidence
358
+ ? String(options.evidence)
359
+ .split(',')
360
+ .map((s) => s.trim())
361
+ .filter(Boolean)
362
+ : [],
286
363
  now,
287
364
  json,
288
365
  });
366
+ } else if (action === 'observe') {
367
+ const input = JSON.parse(store.readText(baseDir, options.input));
368
+ const written = outcomes.observeOutcome(baseDir, input, { now });
369
+ console.log(
370
+ json
371
+ ? JSON.stringify({ written }, null, 2)
372
+ : `mem observe: ${written.id} recorded; lesson causality remains unmeasured.`
373
+ );
374
+ } else if (action === 'outcomes') {
375
+ const results = outcomes.inspectOutcomes(baseDir);
376
+ console.log(
377
+ json
378
+ ? JSON.stringify({ count: results.length, results }, null, 2)
379
+ : results
380
+ .map(
381
+ (record) =>
382
+ `${record.id}: ${record.eligible ? 'accepted-current' : record.reason}`
383
+ )
384
+ .join('\n') || 'mem outcomes: no observations.'
385
+ );
289
386
  } else {
290
- console.error(`mem: unknown action '${action}' — expected recall | write | reinforce`);
387
+ console.error(
388
+ `mem: unknown action '${action}' — expected recall | write | reinforce | observe | outcomes`
389
+ );
291
390
  process.exitCode = 1;
292
391
  }
293
392
  } catch (err) {
294
- console.error(`mem ${action}: ${err.message}`);
393
+ if (json) console.log(JSON.stringify({ error: err.message }));
394
+ else console.error(`mem ${action}: ${err.message}`);
295
395
  process.exitCode = 1;
296
396
  }
297
397
  },