bmad-plus 0.12.1 → 0.13.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 (176) hide show
  1. package/CHANGELOG.md +637 -580
  2. package/README.md +123 -120
  3. package/osint-agent-package/agents/osint-investigator.md +12 -0
  4. package/osint-agent-package/skills/bmad-osint-investigate/osint/SKILL.md +491 -482
  5. package/osint-agent-package/skills/bmad-osint-investigate/osint/assets/dossier-template.md +126 -126
  6. package/osint-agent-package/skills/bmad-osint-investigate/osint/assets/lawful-basis-record.md +48 -48
  7. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/content-extraction.md +100 -100
  8. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/gdpr-osint.md +48 -48
  9. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/platforms.md +130 -130
  10. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/psychoprofile.md +69 -69
  11. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/tools.md +281 -281
  12. package/osint-agent-package/skills/bmad-osint-investigate/osint/scripts/mcp-client.py +136 -136
  13. package/package.json +106 -91
  14. package/readme-international/README.de.md +625 -594
  15. package/readme-international/README.es.md +642 -611
  16. package/readme-international/README.fr.md +640 -609
  17. package/src/bmad-plus/agents/agent-orchestrator/SKILL.md +2 -0
  18. package/src/bmad-plus/agents/agent-shadow/SKILL.md +18 -0
  19. package/src/bmad-plus/data/role-triggers.yaml +52 -0
  20. package/src/bmad-plus/module.yaml +333 -283
  21. package/src/bmad-plus/packs/pack-animated/animated-website-agent.md +325 -325
  22. package/src/bmad-plus/packs/pack-animated/templates/animated-website-workflow.md +55 -55
  23. package/src/bmad-plus/packs/pack-backup/backup-agent.md +71 -71
  24. package/src/bmad-plus/packs/pack-backup/templates/backup-workflow.md +51 -51
  25. package/src/bmad-plus/packs/pack-dev-studio/README.md +162 -162
  26. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/analyst-agent.md +73 -73
  27. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/document-project.md +61 -61
  28. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/domain-research.md +95 -95
  29. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/market-research.md +95 -95
  30. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/prfaq.md +134 -134
  31. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/product-brief.md +80 -80
  32. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/tech-writer-agent.md +73 -73
  33. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/technical-research.md +95 -95
  34. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/architect-agent.md +73 -73
  35. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-architecture.md +73 -73
  36. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-epics-stories.md +92 -92
  37. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/generate-project-context.md +80 -80
  38. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/implementation-readiness.md +90 -90
  39. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01-init.md +153 -153
  40. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01b-continue.md +173 -173
  41. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-02-context.md +224 -224
  42. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-03-starter.md +329 -329
  43. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-04-decisions.md +318 -318
  44. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-05-patterns.md +359 -359
  45. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-06-structure.md +379 -379
  46. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-07-validation.md +361 -361
  47. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-08-complete.md +81 -81
  48. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/checkpoint-preview.md +67 -67
  49. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-01-gather-context.md +85 -85
  50. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-02-review.md +35 -35
  51. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-03-triage.md +49 -49
  52. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-04-present.md +131 -131
  53. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review.md +89 -89
  54. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/correct-course.md +300 -300
  55. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/create-story.md +428 -428
  56. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-agent.md +73 -73
  57. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story-checklist.md +80 -80
  58. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story.md +484 -484
  59. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/investigate.md +193 -193
  60. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/qa-e2e-tests.md +175 -175
  61. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/quick-dev.md +110 -110
  62. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/retrospective.md +1511 -1511
  63. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-planning.md +298 -298
  64. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-status.md +296 -296
  65. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-prd.md +29 -29
  66. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-ux-design.md +74 -74
  67. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/edit-prd.md +29 -29
  68. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/pm-agent.md +73 -73
  69. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/prd.md +89 -89
  70. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/ux-designer-agent.md +73 -73
  71. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/validate-prd.md +29 -29
  72. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/advanced-elicitation.md +141 -141
  73. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/adversarial-review.md +37 -37
  74. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/bmad-help.md +75 -75
  75. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/brainstorming.md +6 -6
  76. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/customize.md +110 -110
  77. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/distillator.md +176 -176
  78. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/edge-case-hunter.md +67 -67
  79. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-prose.md +86 -86
  80. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-structure.md +179 -179
  81. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/index-docs.md +66 -66
  82. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/party-mode.md +127 -127
  83. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/shard-doc.md +105 -105
  84. package/src/bmad-plus/packs/pack-dev-studio/dev-studio-orchestrator.md +120 -120
  85. package/src/bmad-plus/packs/pack-dev-studio/shared/architecture-decision-template.md +12 -12
  86. package/src/bmad-plus/packs/pack-dev-studio/shared/bwml-spec.md +328 -328
  87. package/src/bmad-plus/packs/pack-dev-studio/shared/module-help.csv +32 -32
  88. package/src/bmad-plus/packs/pack-dev-studio/upstream-sync.yaml +81 -81
  89. package/src/bmad-plus/packs/pack-seo/SKILL.md +171 -171
  90. package/src/bmad-plus/packs/pack-seo/checklist.md +140 -140
  91. package/src/bmad-plus/packs/pack-seo/pagespeed-playbook.md +320 -320
  92. package/src/bmad-plus/packs/pack-seo/ref/audit-schema.json +187 -187
  93. package/src/bmad-plus/packs/pack-seo/ref/cwv-thresholds.md +87 -87
  94. package/src/bmad-plus/packs/pack-seo/ref/eeat-criteria.md +123 -123
  95. package/src/bmad-plus/packs/pack-seo/ref/geo-signals.md +167 -167
  96. package/src/bmad-plus/packs/pack-seo/ref/hreflang-rules.md +153 -153
  97. package/src/bmad-plus/packs/pack-seo/ref/quality-gates.md +133 -133
  98. package/src/bmad-plus/packs/pack-seo/ref/schema-catalog.md +91 -91
  99. package/src/bmad-plus/packs/pack-seo/ref/schema-templates.json +356 -356
  100. package/src/bmad-plus/packs/pack-seo/requirements.txt +17 -0
  101. package/src/bmad-plus/packs/pack-seo/scripts/seo_apis.py +456 -0
  102. package/src/bmad-plus/packs/pack-seo/scripts/seo_crawl.py +359 -0
  103. package/src/bmad-plus/packs/pack-seo/scripts/seo_fetch.py +303 -0
  104. package/src/bmad-plus/packs/pack-seo/scripts/seo_parse.py +255 -0
  105. package/src/bmad-plus/packs/pack-seo/scripts/seo_report.py +409 -0
  106. package/src/bmad-plus/packs/pack-seo/scripts/seo_screenshot.py +202 -0
  107. package/src/bmad-plus/packs/pack-seo/seo-chief.md +294 -294
  108. package/src/bmad-plus/packs/pack-seo/seo-judge.md +241 -241
  109. package/src/bmad-plus/packs/pack-seo/seo-scout.md +171 -171
  110. package/src/bmad-plus/packs/pack-seo/templates/seo-audit-workflow.md +241 -241
  111. package/src/bmad-plus/packs/pack-shield/README.md +6 -6
  112. package/src/bmad-plus/packs/pack-shield/SKILL.md +2 -2
  113. package/src/bmad-plus/packs/pack-shield/categories/accessibility-esg/csrd-agent.md +11 -11
  114. package/src/bmad-plus/packs/pack-shield/categories/accessibility-esg/section508-agent.md +11 -11
  115. package/src/bmad-plus/packs/pack-shield/categories/accessibility-esg/wcag-agent.md +11 -11
  116. package/src/bmad-plus/packs/pack-shield/categories/ai-governance/eu-ai-act-agent.md +11 -11
  117. package/src/bmad-plus/packs/pack-shield/categories/ai-governance/iso42001-agent.md +11 -11
  118. package/src/bmad-plus/packs/pack-shield/categories/ai-governance/nist-ai-rmf-agent.md +11 -11
  119. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/cis-controls-agent.md +11 -11
  120. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/ism-agent.md +11 -11
  121. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/iso27001-agent.md +11 -11
  122. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/nis2-agent.md +11 -11
  123. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/nist-800-53-agent.md +11 -11
  124. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/nist-csf-agent.md +11 -11
  125. package/src/bmad-plus/packs/pack-shield/categories/defense-export/cmmc-agent.md +11 -11
  126. package/src/bmad-plus/packs/pack-shield/categories/defense-export/ear-agent.md +11 -11
  127. package/src/bmad-plus/packs/pack-shield/categories/defense-export/itar-agent.md +11 -11
  128. package/src/bmad-plus/packs/pack-shield/categories/defense-export/tsa-agent.md +11 -11
  129. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/dora-agent.md +11 -11
  130. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/fedramp-agent.md +11 -11
  131. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/hipaa-agent.md +11 -11
  132. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/pci-dss-agent.md +11 -11
  133. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/soc2-agent.md +11 -11
  134. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/swift-csp-agent.md +11 -11
  135. package/src/bmad-plus/packs/pack-shield/shield-orchestrator.md +1 -1
  136. package/src/bmad-plus/skills/bmad-plus-autopilot/SKILL.md +1 -1
  137. package/tools/bmad-plus-npx.js +4 -2
  138. package/tools/build/adapters.config.js +60 -51
  139. package/tools/build/check-counts.js +626 -0
  140. package/tools/build/check-install-contract.js +298 -0
  141. package/tools/build/generate-adapters.js +252 -56
  142. package/tools/build/generate.js +187 -10
  143. package/tools/build/generated-adapters/.codex/AGENTS.md +20 -7
  144. package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +20 -7
  145. package/tools/build/generated-adapters/.opencode/AGENTS.md +20 -7
  146. package/tools/build/generated-adapters/AGENTS.md +20 -7
  147. package/tools/build/generated-adapters/CLAUDE.md +20 -7
  148. package/tools/build/generated-adapters/CONVENTIONS.md +20 -7
  149. package/tools/build/generated-adapters/GEMINI.md +20 -7
  150. package/tools/build/module.template.yaml +82 -0
  151. package/tools/cli/bmad-plus-cli.js +16 -1
  152. package/tools/cli/commands/autoconfig.js +470 -470
  153. package/tools/cli/commands/doctor.js +205 -233
  154. package/tools/cli/commands/install.js +543 -501
  155. package/tools/cli/commands/memory-journal-cmd.js +311 -311
  156. package/tools/cli/commands/memory.js +195 -195
  157. package/tools/cli/commands/scan.js +348 -348
  158. package/tools/cli/commands/uninstall.js +209 -101
  159. package/tools/cli/commands/update-check.js +31 -0
  160. package/tools/cli/commands/update-policy.js +39 -0
  161. package/tools/cli/commands/update.js +123 -134
  162. package/tools/cli/i18n.js +905 -845
  163. package/tools/cli/lib/README-memory-journal.md +125 -125
  164. package/tools/cli/lib/ide-config.js +10 -259
  165. package/tools/cli/lib/install-manifest.js +17 -0
  166. package/tools/cli/lib/installed-adapters.js +89 -0
  167. package/tools/cli/lib/npm-runner.js +177 -0
  168. package/tools/cli/lib/pack-copy.js +62 -66
  169. package/tools/cli/lib/packs.js +437 -3
  170. package/tools/cli/lib/python-provision.js +508 -508
  171. package/tools/cli/lib/stack-detect.js +102 -102
  172. package/tools/cli/lib/update-check.js +153 -0
  173. package/tools/cli/lib/update-dispatch.js +182 -0
  174. package/tools/cli/lib/update-policy.js +90 -0
  175. package/tools/cli/lib/update-transaction.js +334 -0
  176. package/tools/cli/lib/validate.js +50 -50
@@ -27,6 +27,9 @@
27
27
  * node tools/build/generate-adapters.js --adopt --force # overwrite root files that
28
28
  * # lack the AUTO-GENERATED marker
29
29
  * node tools/build/generate-adapters.js --out-dir <dir> # write to <dir>
30
+ * node tools/build/generate-adapters.js --target <dir> # user spine + adapters
31
+ * [--packs core,memory] [--tools claude-code,cursor] [--user-name Alice]
32
+ * [--language English] [--force] # explicit foreign overwrite
30
33
  * node tools/build/generate-adapters.js --print # print all adapters to stdout
31
34
  * node tools/build/generate-adapters.js --check # verify preview adapters match
32
35
  * node tools/build/generate-adapters.js --check --adopt # verify ROOT adapters + spine
@@ -39,12 +42,13 @@
39
42
 
40
43
  const fs = require('node:fs');
41
44
  const path = require('node:path');
42
- const { loadRegistry, DEFAULT_REGISTRY_PATH } = require('./generate');
43
- const { PROJECT_INSTRUCTIONS } = require('./adapters.config');
45
+ const { loadRegistry, buildDerived, DEFAULT_REGISTRY_PATH } = require('./generate');
46
+ const { PROJECT_INSTRUCTIONS, MEMORY_INSTRUCTIONS, UPDATE_INSTRUCTIONS, TOOL_METADATA } = require('./adapters.config');
44
47
 
45
48
  const DEFAULT_OUT_DIR = path.join(__dirname, 'generated-adapters');
46
49
  const REPO_ROOT = path.join(__dirname, '..', '..');
47
50
  const GENERATED_MARKER = 'AUTO-GENERATED by tools/build/generate-adapters.js';
51
+ const USER_CONFIG_MARKER = 'BMAD+ installed project configuration';
48
52
 
49
53
  /** Per-tool invocation notes. Unknown tools fall back to buildDefaultNotes(). */
50
54
  const TOOL_NOTES = {
@@ -99,6 +103,7 @@ function validateTargets(registry) {
99
103
  if (typeof t.spine !== 'string' || t.spine === '') {
100
104
  throw new Error('registry.yaml: targets.spine must be a non-empty string');
101
105
  }
106
+ validateRelativeFile(t.spine);
102
107
  if (!Array.isArray(t.adapters) || t.adapters.length === 0) {
103
108
  throw new Error('registry.yaml: targets.adapters must be a non-empty list');
104
109
  }
@@ -106,58 +111,55 @@ function validateTargets(registry) {
106
111
  if (!a || typeof a.tool !== 'string' || a.tool === '' || typeof a.file !== 'string' || a.file === '') {
107
112
  throw new Error(`registry.yaml: targets.adapters[${i}] must have string "tool" and "file"`);
108
113
  }
114
+ validateRelativeFile(a.file);
109
115
  });
110
116
  return registry;
111
117
  }
112
118
 
113
- /** Sum of list lengths for a given key across a pack's categories. */
114
- function sumCategories(pack, key) {
115
- if (!Array.isArray(pack.categories)) return 0;
116
- return pack.categories.reduce((n, c) => n + (Array.isArray(c[key]) ? c[key].length : 0), 0);
117
- }
118
-
119
- /**
120
- * Facts DERIVED from the registry — every number here is computed from the
121
- * declared lists, so adapters can never contradict registry.yaml.
122
- */
123
- function buildRegistryFacts(registry) {
124
- const packEntries = Object.entries(registry.packs).sort((a, b) => a[1].order - b[1].order);
125
- const packs = packEntries.map(([id, p]) => {
126
- const fact = {
127
- id,
128
- name: p.cli.name,
129
- displayName: p.display_name || p.cli.name,
130
- desc: p.cli.desc,
131
- agentCount: p.agents.length,
132
- required: p.required === true,
119
+ /** Convert the canonical derivation into the adapter presentation shape. */
120
+ function factsFromDerived(derived, packs = derived.packOrder) {
121
+ const selected = packs.map(id => {
122
+ const pack = derived.packs[id];
123
+ return {
124
+ id, name: pack.name, displayName: pack.displayName, desc: pack.desc,
125
+ required: pack.required, agentCount: pack.installerAgentCount,
126
+ categoryAgentCount: pack.categoryAgentCount, subAgentCount: pack.subAgentCount,
127
+ workflowCount: pack.workflowCount, frameworkCount: pack.frameworkCount,
128
+ personas: pack.personas,
133
129
  };
134
- const categoryAgents = sumCategories(p, 'agents');
135
- const categoryWorkflows = sumCategories(p, 'workflows');
136
- if (categoryAgents > 0) fact.categoryAgentCount = categoryAgents;
137
- if (Array.isArray(p.sub_agents)) fact.subAgentCount = p.sub_agents.length;
138
- if (Array.isArray(p.workflows)) fact.workflowCount = p.workflows.length;
139
- if (categoryWorkflows > 0) fact.workflowCount = (fact.workflowCount || 0) + categoryWorkflows;
140
- if (Array.isArray(p.compliance_tags) && p.compliance_tags.length > 0) {
141
- fact.frameworkCount = p.compliance_tags.length;
142
- }
143
- return fact;
144
130
  });
145
-
146
131
  return {
147
- product: {
148
- code: registry.product.code,
149
- displayName: registry.product.display_name,
150
- version: registry.product.version,
151
- derivedFrom: registry.product.derived_from,
152
- },
153
- spine: registry.targets.spine,
154
- modelsSupported: [...(registry.targets.models_supported || [])],
155
- packCount: packs.length,
156
- installerAgentCount: packs.reduce((n, p) => n + p.agentCount, 0),
157
- packs,
132
+ product: derived.product,
133
+ spine: derived.targets.spine,
134
+ modelsSupported: derived.targets.models_supported,
135
+ packCount: selected.length,
136
+ installerAgentCount: packs.length === derived.packOrder.length
137
+ ? derived.installerAgents
138
+ : selected.reduce((total, pack) => total + pack.agentCount, 0),
139
+ packs: selected,
158
140
  };
159
141
  }
160
142
 
143
+ /** Counts are calculated only by buildDerived, never from raw lists here. */
144
+ function buildRegistryFacts(registry) {
145
+ return factsFromDerived(buildDerived(registry));
146
+ }
147
+
148
+ /** Render registry-owned persona metadata for the selected packs. */
149
+ function renderRoster(facts) {
150
+ const lines = ['## Agents', '', 'To activate an agent, say its name or persona:', ''];
151
+ for (const pack of facts.packs) {
152
+ for (const persona of pack.personas || []) {
153
+ const identity = persona.alias
154
+ ? '(' + persona.alias + ') — ' + persona.role
155
+ : '(' + persona.role + ')';
156
+ lines.push('- **' + persona.name + '** ' + identity + ' — ' + persona.description +
157
+ (pack.required ? '' : ' (' + pack.name + ')'));
158
+ }
159
+ }
160
+ return lines;
161
+ }
162
+
161
163
  /** Group targets.adapters[] by output file → [{ file, tools: [tool, ...] }]. */
162
164
  function groupAdaptersByFile(registry) {
163
165
  const byFile = new Map();
@@ -209,6 +211,8 @@ function renderProjectInstructions(facts) {
209
211
  '<!-- Hand-authored project instructions — single source: tools/build/adapters.config.js. -->',
210
212
  '<!-- Edit the config, then regenerate; never edit this file directly. -->',
211
213
  '',
214
+ ...renderRoster(facts),
215
+ '',
212
216
  ...PROJECT_INSTRUCTIONS,
213
217
  ];
214
218
  }
@@ -339,18 +343,167 @@ function generateAllFiles(registry) {
339
343
  return [spine, ...generateAllAdapters(registry)];
340
344
  }
341
345
 
346
+ /** Installable tools and paths come from the registry; only detection is local. */
347
+ function buildIDEConfigs(derived) {
348
+ validateTargets({ targets: derived.targets });
349
+ return Object.fromEntries(derived.targets.adapters.map(({ tool, file }) => [
350
+ tool, { name: TOOL_METADATA[tool]?.name || tool, configFile: file,
351
+ detect: [...(TOOL_METADATA[tool]?.detect || [])] },
352
+ ]));
353
+ }
354
+
355
+ function selectedPacks(derived, packs) {
356
+ if (packs !== undefined && !Array.isArray(packs)) throw new Error('packs must be an array of pack IDs');
357
+ const selected = new Set(packs === undefined ? derived.packOrder : packs);
358
+ for (const id of selected) {
359
+ if (!Object.hasOwn(derived.packs, id)) throw new Error('Unknown pack: ' + id);
360
+ }
361
+ for (const id of derived.packOrder) if (derived.packs[id].required) selected.add(id);
362
+ return derived.packOrder.filter(id => selected.has(id));
363
+ }
364
+
365
+ function renderUserSections(derived, { packs, userName = 'Developer', language = 'English' } = {}) {
366
+ const ids = selectedPacks(derived, packs);
367
+ const facts = factsFromDerived(derived, ids);
368
+ const lines = [
369
+ '## Project Context', '',
370
+ 'This project uses ' + facts.product.displayName + ', an augmented AI-driven development framework.',
371
+ 'Based on ' + facts.product.derivedFrom + ' with multi-role agents, autopilot mode, and parallel execution.',
372
+ '', ...renderRoster(facts), '',
373
+ '## Skills', '',
374
+ '- Load skills from `.agents/skills/`.',
375
+ '- Each agent has a SKILL.md with capabilities, activation protocol, and role-switching rules.',
376
+ '- Auto-activation triggers: `.agents/data/role-triggers.yaml`.',
377
+ '', '## Communication', '',
378
+ '- User name: ' + String(userName).replace(/[\r\n]/g, ' '),
379
+ '- Default language: ' + String(language).replace(/[\r\n]/g, ' ') + ' for user-facing content, English for code and technical docs.',
380
+ ];
381
+ if (ids.includes('memory')) lines.push('', ...MEMORY_INSTRUCTIONS);
382
+ lines.push('', ...UPDATE_INSTRUCTIONS, '', ...renderFactsSection(facts), '', ...renderKeyCommands());
383
+ return lines;
384
+ }
385
+
386
+ function userMarkerLines() {
387
+ return [
388
+ '<!-- ' + GENERATED_MARKER + ' — DO NOT EDIT. -->',
389
+ '<!-- ' + USER_CONFIG_MARKER + ' -->',
390
+ '<!-- Refresh installed adapters with: npx bmad-plus update -->',
391
+ ];
392
+ }
393
+
394
+ /** Pure user spine renderer; DERIVED also ships in the npm package. */
395
+ function renderUserConfig(derived, options = {}) {
396
+ validateTargets({ targets: derived.targets });
397
+ return [
398
+ ...userMarkerLines(), '',
399
+ '# ' + derived.product.displayName + ' — Agent Spine', '',
400
+ 'This file is the common project instruction spine for the installed ' + derived.product.displayName + ' tools.', '',
401
+ ...renderUserSections(derived, options), '',
402
+ ].join('\n');
403
+ }
404
+
405
+ /** Pure render of the common spine and every selected registry adapter. */
406
+ function generateUserFiles(derived, options = {}) {
407
+ validateTargets({ targets: derived.targets });
408
+ const available = derived.targets.adapters.map(adapter => adapter.tool);
409
+ const requested = options.tools === undefined ? available : options.tools;
410
+ if (!Array.isArray(requested)) throw new Error('tools must be an array of tool IDs');
411
+ for (const tool of requested) {
412
+ if (!available.includes(tool)) throw new Error('Unknown tool: ' + tool);
413
+ }
414
+ const targets = { ...derived.targets,
415
+ adapters: derived.targets.adapters.filter(adapter => requested.includes(adapter.tool)) };
416
+ const files = [{ file: targets.spine, tools: ['spine'], spine: true,
417
+ content: renderUserConfig(derived, options) }];
418
+ for (const { file, tools } of groupAdaptersByFile({ targets })) {
419
+ if (file === targets.spine) continue;
420
+ const reference = path.posix.relative(path.posix.dirname(file), targets.spine);
421
+ const lines = [];
422
+ if (file.endsWith('.mdc')) lines.push('---',
423
+ 'description: "' + derived.product.displayName + ' agents adapter"', 'alwaysApply: true', '---', '');
424
+ lines.push(...userMarkerLines(), '',
425
+ '# ' + derived.product.displayName + ' — Adapter for ' + tools.join(' + '), '',
426
+ 'Read the project spine first: [' + targets.spine + '](' + reference + ').',
427
+ 'Paths in the shared project instructions below are relative to the project root.', '');
428
+ for (const tool of tools) {
429
+ lines.push('## Tool notes — ' + tool, '');
430
+ for (const note of TOOL_NOTES[tool] || buildDefaultNotes(tool, file)) lines.push('- ' + note);
431
+ lines.push('');
432
+ }
433
+ lines.push(...renderUserSections(derived, options), '');
434
+ files.push({ file, tools, content: lines.join('\n') });
435
+ }
436
+ return files;
437
+ }
438
+
439
+ /** Refuse portable-path escapes even when generated input has been modified. */
440
+ function validateRelativeFile(file) {
441
+ if (typeof file !== 'string' || !file || /[\\:]/.test(file) || file.includes('\0') ||
442
+ path.posix.isAbsolute(file) || file.split('/').some(part => !part || part === '.' || part === '..')) {
443
+ throw new Error('Adapter file must be a relative project path: ' + file);
444
+ }
445
+ }
446
+
447
+ /** Check existing ancestors before any write; Windows junctions are symlinks here. */
448
+ function safeTarget(outDir, file) {
449
+ validateRelativeFile(file);
450
+ const root = path.resolve(outDir);
451
+ const target = path.resolve(root, file);
452
+ const relative = path.relative(root, target);
453
+ if (!relative || relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
454
+ throw new Error('Adapter path escapes the target project: ' + file);
455
+ }
456
+ let current = path.parse(target).root;
457
+ for (const part of target.slice(current.length).split(path.sep)) {
458
+ current = path.join(current, part);
459
+ let stat;
460
+ try { stat = fs.lstatSync(current); } catch (error) {
461
+ if (error.code === 'ENOENT') break;
462
+ throw error;
463
+ }
464
+ if (stat.isSymbolicLink()) throw new Error('Adapter path contains a symlink or junction: ' + current);
465
+ if (current === target && !stat.isFile()) throw new Error('Adapter target is not a regular file: ' + current);
466
+ if (current !== target && !stat.isDirectory()) throw new Error('Adapter parent is not a directory: ' + current);
467
+ }
468
+ return target;
469
+ }
470
+
471
+ /** Preflight the whole batch to avoid partial writes on a later unsafe path. */
472
+ function writeFiles(outDir, files) {
473
+ for (const { file } of files) safeTarget(outDir, file);
474
+ return files.map(({ file, content }) => {
475
+ const target = safeTarget(outDir, file);
476
+ fs.mkdirSync(path.dirname(target), { recursive: true });
477
+ safeTarget(outDir, file);
478
+ fs.writeFileSync(target, content, 'utf8');
479
+ return target;
480
+ });
481
+ }
482
+
483
+ function writeUserAdapters({ outDir, derived = require('../cli/lib/packs').DERIVED,
484
+ packs, userName, language, tools, force = false } = {}) {
485
+ if (!outDir) throw new Error('A target project directory is required');
486
+ const files = generateUserFiles(derived, { packs, userName, language, tools });
487
+ const blockers = listAdoptionBlockers(outDir, files, USER_CONFIG_MARKER);
488
+ if (blockers.length && !force) {
489
+ throw new Error('REFUSING to overwrite files not owned by this user configuration: ' +
490
+ blockers.join(', ') + '. Review them and use --force to replace them explicitly.');
491
+ }
492
+ return writeFiles(outDir, files);
493
+ }
494
+
342
495
  /**
343
496
  * Adoption guard: list on-disk targets under outDir that exist but do NOT
344
497
  * carry the AUTO-GENERATED marker (i.e. hand-authored files that would be
345
498
  * clobbered). Overwriting them requires an explicit --force.
346
499
  */
347
- function listAdoptionBlockers(outDir, files) {
500
+ function listAdoptionBlockers(outDir, files, marker = GENERATED_MARKER) {
348
501
  const blockers = [];
349
502
  for (const { file } of files) {
350
- const target = path.join(outDir, file);
503
+ const target = safeTarget(outDir, file);
351
504
  if (!fs.existsSync(target)) continue;
352
505
  const current = fs.readFileSync(target, 'utf8');
353
- if (!current.includes(GENERATED_MARKER)) blockers.push(file);
506
+ if (!current.includes(GENERATED_MARKER) || !current.includes(marker)) blockers.push(file);
354
507
  }
355
508
  return blockers;
356
509
  }
@@ -358,14 +511,7 @@ function listAdoptionBlockers(outDir, files) {
358
511
  /** Write generated spine + adapters under outDir, preserving relative paths. */
359
512
  function writeAdapters({ registryPath = DEFAULT_REGISTRY_PATH, outDir = DEFAULT_OUT_DIR } = {}) {
360
513
  const files = generateAllFiles(loadRegistry(registryPath));
361
- const written = [];
362
- for (const { file, content } of files) {
363
- const target = path.join(outDir, file);
364
- fs.mkdirSync(path.dirname(target), { recursive: true });
365
- fs.writeFileSync(target, content, 'utf8');
366
- written.push(target);
367
- }
368
- return written;
514
+ return writeFiles(outDir, files);
369
515
  }
370
516
 
371
517
  /**
@@ -398,13 +544,20 @@ function check({ registryPath = DEFAULT_REGISTRY_PATH, outDir = DEFAULT_OUT_DIR
398
544
 
399
545
  function parseOutDir(args) {
400
546
  const idx = args.indexOf('--out-dir');
547
+ const targetIdx = args.indexOf('--target');
548
+ if (targetIdx !== -1) {
549
+ if (args.includes('--adopt') || idx !== -1) throw new Error('--target, --adopt and --out-dir are mutually exclusive');
550
+ const value = args[targetIdx + 1];
551
+ if (!value || value.startsWith('--')) throw new Error('--target requires a directory path');
552
+ return path.resolve(value);
553
+ }
401
554
  if (args.includes('--adopt')) {
402
555
  if (idx !== -1) throw new Error('--adopt and --out-dir are mutually exclusive (--adopt targets the repo root)');
403
556
  return REPO_ROOT;
404
557
  }
405
558
  if (idx === -1) return DEFAULT_OUT_DIR;
406
559
  const value = args[idx + 1];
407
- if (!value) throw new Error('--out-dir requires a directory path');
560
+ if (!value || value.startsWith('--')) throw new Error('--out-dir requires a directory path');
408
561
  return path.resolve(value);
409
562
  }
410
563
 
@@ -417,6 +570,42 @@ function main(argv) {
417
570
  console.error(err.message);
418
571
  return 1;
419
572
  }
573
+ if (args.includes('--target')) {
574
+ try {
575
+ const option = (name) => {
576
+ const index = args.indexOf(name);
577
+ if (index === -1) return undefined;
578
+ const value = args[index + 1];
579
+ if (!value || value.startsWith('--')) throw new Error(name + ' requires a value');
580
+ return value;
581
+ };
582
+ const packs = option('--packs');
583
+ const tools = option('--tools');
584
+ const options = { packs: !packs || packs === 'all' ? undefined : packs.split(',').map(value => value.trim()),
585
+ tools: !tools || tools === 'all' ? undefined : tools === 'none' ? [] : tools.split(',').map(value => value.trim()),
586
+ userName: option('--user-name'), language: option('--language') };
587
+ const derived = require('../cli/lib/packs').DERIVED;
588
+ const files = generateUserFiles(derived, options);
589
+ if (args.includes('--print')) {
590
+ for (const { file, content } of files) console.log(file + '\n' + content);
591
+ return 0;
592
+ }
593
+ if (args.includes('--check')) {
594
+ const mismatches = files.filter(({ file, content }) => {
595
+ const target = safeTarget(outDir, file);
596
+ return !fs.existsSync(target) || normalizeEol(fs.readFileSync(target, 'utf8')) !== normalizeEol(content);
597
+ });
598
+ if (mismatches.length) console.error('User adapter drift: ' + mismatches.map(entry => entry.file).join(', '));
599
+ return mismatches.length ? 1 : 0;
600
+ }
601
+ const written = writeUserAdapters({ outDir, derived, ...options, force: args.includes('--force') });
602
+ for (const target of written) console.log('Generated ' + target);
603
+ return 0;
604
+ } catch (error) {
605
+ console.error(error.message);
606
+ return 1;
607
+ }
608
+ }
420
609
  const adopting = args.includes('--adopt');
421
610
  const label = adopting ? 'root adapters + spine' : 'adapters';
422
611
 
@@ -467,6 +656,13 @@ module.exports = {
467
656
  DEFAULT_OUT_DIR,
468
657
  REPO_ROOT,
469
658
  GENERATED_MARKER,
659
+ USER_CONFIG_MARKER,
660
+ buildIDEConfigs,
661
+ get IDE_CONFIGS() { return buildIDEConfigs(require('../cli/lib/packs').DERIVED); },
662
+ renderUserConfig,
663
+ generateUserFiles,
664
+ writeUserAdapters,
665
+ safeTarget,
470
666
  TOOL_NOTES,
471
667
  validateTargets,
472
668
  buildRegistryFacts,