ma-agents 3.16.0 → 3.17.0-beta.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 (141) hide show
  1. package/README.md +85 -4
  2. package/bin/cli.js +323 -6
  3. package/lib/agents.js +22 -15
  4. package/lib/bmad-cache/cache-manifest.json +2 -2
  5. package/lib/bmad-extension/.claude-plugin/marketplace.json.template +2 -15
  6. package/lib/bmad-extension/skills/module-help.csv +0 -13
  7. package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +3 -16
  8. package/lib/bmad-extension-plugin/skills/module-help.csv +0 -13
  9. package/lib/bmad.js +116 -1
  10. package/lib/custom-marketplace.js +507 -0
  11. package/lib/installer.js +19 -7
  12. package/lib/knowledge-atlas/dist/atlas.cjs +14651 -14651
  13. package/lib/knowledge-atlas/src/render/vendor.ts +43 -43
  14. package/lib/knowledge-atlas/vendor/drawio-viewer.min.js +7563 -7563
  15. package/lib/knowledge-atlas/vendor/mermaid.min.js +2314 -2314
  16. package/lib/profile.js +201 -0
  17. package/lib/skill-authoring.js +3 -1
  18. package/package.json +3 -3
  19. package/scripts/build-bmad-cache.js +127 -43
  20. package/lib/bmad-extension/skills/ma-agent-ml/.gitkeep +0 -0
  21. package/lib/bmad-extension/skills/ma-agent-ml/SKILL.md +0 -59
  22. package/lib/bmad-extension/skills/ma-agent-ml/bmad-skill-manifest.yaml +0 -11
  23. package/lib/bmad-extension/skills/ml-advise/.gitkeep +0 -0
  24. package/lib/bmad-extension/skills/ml-advise/SKILL.md +0 -76
  25. package/lib/bmad-extension/skills/ml-advise/bmad-skill-manifest.yaml +0 -3
  26. package/lib/bmad-extension/skills/ml-advise/skill.json +0 -7
  27. package/lib/bmad-extension/skills/ml-analysis/.gitkeep +0 -0
  28. package/lib/bmad-extension/skills/ml-analysis/SKILL.md +0 -60
  29. package/lib/bmad-extension/skills/ml-analysis/bmad-skill-manifest.yaml +0 -3
  30. package/lib/bmad-extension/skills/ml-analysis/skill.json +0 -7
  31. package/lib/bmad-extension/skills/ml-architecture/.gitkeep +0 -0
  32. package/lib/bmad-extension/skills/ml-architecture/SKILL.md +0 -55
  33. package/lib/bmad-extension/skills/ml-architecture/bmad-skill-manifest.yaml +0 -3
  34. package/lib/bmad-extension/skills/ml-architecture/skill.json +0 -7
  35. package/lib/bmad-extension/skills/ml-detailed-design/.gitkeep +0 -0
  36. package/lib/bmad-extension/skills/ml-detailed-design/SKILL.md +0 -67
  37. package/lib/bmad-extension/skills/ml-detailed-design/bmad-skill-manifest.yaml +0 -3
  38. package/lib/bmad-extension/skills/ml-detailed-design/skill.json +0 -7
  39. package/lib/bmad-extension/skills/ml-eda/.gitkeep +0 -0
  40. package/lib/bmad-extension/skills/ml-eda/SKILL.md +0 -56
  41. package/lib/bmad-extension/skills/ml-eda/bmad-skill-manifest.yaml +0 -3
  42. package/lib/bmad-extension/skills/ml-eda/scripts/baseline_classifier.py +0 -522
  43. package/lib/bmad-extension/skills/ml-eda/scripts/class_weights_calculator.py +0 -295
  44. package/lib/bmad-extension/skills/ml-eda/scripts/clustering_explorer.py +0 -383
  45. package/lib/bmad-extension/skills/ml-eda/scripts/eda_analyzer.py +0 -654
  46. package/lib/bmad-extension/skills/ml-eda/skill.json +0 -7
  47. package/lib/bmad-extension/skills/ml-experiment/.gitkeep +0 -0
  48. package/lib/bmad-extension/skills/ml-experiment/SKILL.md +0 -74
  49. package/lib/bmad-extension/skills/ml-experiment/assets/advanced_trainer_configs.py +0 -430
  50. package/lib/bmad-extension/skills/ml-experiment/assets/quick_trainer_setup.py +0 -233
  51. package/lib/bmad-extension/skills/ml-experiment/assets/template_datamodule.py +0 -219
  52. package/lib/bmad-extension/skills/ml-experiment/assets/template_gnn_module.py +0 -341
  53. package/lib/bmad-extension/skills/ml-experiment/assets/template_lightning_module.py +0 -158
  54. package/lib/bmad-extension/skills/ml-experiment/bmad-skill-manifest.yaml +0 -3
  55. package/lib/bmad-extension/skills/ml-experiment/skill.json +0 -7
  56. package/lib/bmad-extension/skills/ml-hparam/.gitkeep +0 -0
  57. package/lib/bmad-extension/skills/ml-hparam/SKILL.md +0 -81
  58. package/lib/bmad-extension/skills/ml-hparam/bmad-skill-manifest.yaml +0 -3
  59. package/lib/bmad-extension/skills/ml-hparam/skill.json +0 -7
  60. package/lib/bmad-extension/skills/ml-ideation/.gitkeep +0 -0
  61. package/lib/bmad-extension/skills/ml-ideation/SKILL.md +0 -50
  62. package/lib/bmad-extension/skills/ml-ideation/bmad-skill-manifest.yaml +0 -3
  63. package/lib/bmad-extension/skills/ml-ideation/scripts/validate_ml_prd.py +0 -287
  64. package/lib/bmad-extension/skills/ml-ideation/skill.json +0 -7
  65. package/lib/bmad-extension/skills/ml-infra/.gitkeep +0 -0
  66. package/lib/bmad-extension/skills/ml-infra/SKILL.md +0 -58
  67. package/lib/bmad-extension/skills/ml-infra/bmad-skill-manifest.yaml +0 -3
  68. package/lib/bmad-extension/skills/ml-infra/skill.json +0 -7
  69. package/lib/bmad-extension/skills/ml-retrospective/.gitkeep +0 -0
  70. package/lib/bmad-extension/skills/ml-retrospective/SKILL.md +0 -63
  71. package/lib/bmad-extension/skills/ml-retrospective/bmad-skill-manifest.yaml +0 -3
  72. package/lib/bmad-extension/skills/ml-retrospective/skill.json +0 -7
  73. package/lib/bmad-extension/skills/ml-revision/.gitkeep +0 -0
  74. package/lib/bmad-extension/skills/ml-revision/SKILL.md +0 -82
  75. package/lib/bmad-extension/skills/ml-revision/bmad-skill-manifest.yaml +0 -3
  76. package/lib/bmad-extension/skills/ml-revision/skill.json +0 -7
  77. package/lib/bmad-extension/skills/ml-techspec/.gitkeep +0 -0
  78. package/lib/bmad-extension/skills/ml-techspec/SKILL.md +0 -80
  79. package/lib/bmad-extension/skills/ml-techspec/bmad-skill-manifest.yaml +0 -3
  80. package/lib/bmad-extension/skills/ml-techspec/skill.json +0 -7
  81. package/lib/bmad-extension-plugin/skills/ma-agent-ml/.gitkeep +0 -0
  82. package/lib/bmad-extension-plugin/skills/ma-agent-ml/SKILL.md +0 -59
  83. package/lib/bmad-extension-plugin/skills/ma-agent-ml/bmad-skill-manifest.yaml +0 -11
  84. package/lib/bmad-extension-plugin/skills/ml-advise/.gitkeep +0 -0
  85. package/lib/bmad-extension-plugin/skills/ml-advise/SKILL.md +0 -76
  86. package/lib/bmad-extension-plugin/skills/ml-advise/bmad-skill-manifest.yaml +0 -3
  87. package/lib/bmad-extension-plugin/skills/ml-advise/skill.json +0 -7
  88. package/lib/bmad-extension-plugin/skills/ml-analysis/.gitkeep +0 -0
  89. package/lib/bmad-extension-plugin/skills/ml-analysis/SKILL.md +0 -60
  90. package/lib/bmad-extension-plugin/skills/ml-analysis/bmad-skill-manifest.yaml +0 -3
  91. package/lib/bmad-extension-plugin/skills/ml-analysis/skill.json +0 -7
  92. package/lib/bmad-extension-plugin/skills/ml-architecture/.gitkeep +0 -0
  93. package/lib/bmad-extension-plugin/skills/ml-architecture/SKILL.md +0 -55
  94. package/lib/bmad-extension-plugin/skills/ml-architecture/bmad-skill-manifest.yaml +0 -3
  95. package/lib/bmad-extension-plugin/skills/ml-architecture/skill.json +0 -7
  96. package/lib/bmad-extension-plugin/skills/ml-detailed-design/.gitkeep +0 -0
  97. package/lib/bmad-extension-plugin/skills/ml-detailed-design/SKILL.md +0 -67
  98. package/lib/bmad-extension-plugin/skills/ml-detailed-design/bmad-skill-manifest.yaml +0 -3
  99. package/lib/bmad-extension-plugin/skills/ml-detailed-design/skill.json +0 -7
  100. package/lib/bmad-extension-plugin/skills/ml-eda/.gitkeep +0 -0
  101. package/lib/bmad-extension-plugin/skills/ml-eda/SKILL.md +0 -56
  102. package/lib/bmad-extension-plugin/skills/ml-eda/bmad-skill-manifest.yaml +0 -3
  103. package/lib/bmad-extension-plugin/skills/ml-eda/scripts/baseline_classifier.py +0 -522
  104. package/lib/bmad-extension-plugin/skills/ml-eda/scripts/class_weights_calculator.py +0 -295
  105. package/lib/bmad-extension-plugin/skills/ml-eda/scripts/clustering_explorer.py +0 -383
  106. package/lib/bmad-extension-plugin/skills/ml-eda/scripts/eda_analyzer.py +0 -654
  107. package/lib/bmad-extension-plugin/skills/ml-eda/skill.json +0 -7
  108. package/lib/bmad-extension-plugin/skills/ml-experiment/.gitkeep +0 -0
  109. package/lib/bmad-extension-plugin/skills/ml-experiment/SKILL.md +0 -74
  110. package/lib/bmad-extension-plugin/skills/ml-experiment/assets/advanced_trainer_configs.py +0 -430
  111. package/lib/bmad-extension-plugin/skills/ml-experiment/assets/quick_trainer_setup.py +0 -233
  112. package/lib/bmad-extension-plugin/skills/ml-experiment/assets/template_datamodule.py +0 -219
  113. package/lib/bmad-extension-plugin/skills/ml-experiment/assets/template_gnn_module.py +0 -341
  114. package/lib/bmad-extension-plugin/skills/ml-experiment/assets/template_lightning_module.py +0 -158
  115. package/lib/bmad-extension-plugin/skills/ml-experiment/bmad-skill-manifest.yaml +0 -3
  116. package/lib/bmad-extension-plugin/skills/ml-experiment/skill.json +0 -7
  117. package/lib/bmad-extension-plugin/skills/ml-hparam/.gitkeep +0 -0
  118. package/lib/bmad-extension-plugin/skills/ml-hparam/SKILL.md +0 -81
  119. package/lib/bmad-extension-plugin/skills/ml-hparam/bmad-skill-manifest.yaml +0 -3
  120. package/lib/bmad-extension-plugin/skills/ml-hparam/skill.json +0 -7
  121. package/lib/bmad-extension-plugin/skills/ml-ideation/.gitkeep +0 -0
  122. package/lib/bmad-extension-plugin/skills/ml-ideation/SKILL.md +0 -50
  123. package/lib/bmad-extension-plugin/skills/ml-ideation/bmad-skill-manifest.yaml +0 -3
  124. package/lib/bmad-extension-plugin/skills/ml-ideation/scripts/validate_ml_prd.py +0 -287
  125. package/lib/bmad-extension-plugin/skills/ml-ideation/skill.json +0 -7
  126. package/lib/bmad-extension-plugin/skills/ml-infra/.gitkeep +0 -0
  127. package/lib/bmad-extension-plugin/skills/ml-infra/SKILL.md +0 -58
  128. package/lib/bmad-extension-plugin/skills/ml-infra/bmad-skill-manifest.yaml +0 -3
  129. package/lib/bmad-extension-plugin/skills/ml-infra/skill.json +0 -7
  130. package/lib/bmad-extension-plugin/skills/ml-retrospective/.gitkeep +0 -0
  131. package/lib/bmad-extension-plugin/skills/ml-retrospective/SKILL.md +0 -63
  132. package/lib/bmad-extension-plugin/skills/ml-retrospective/bmad-skill-manifest.yaml +0 -3
  133. package/lib/bmad-extension-plugin/skills/ml-retrospective/skill.json +0 -7
  134. package/lib/bmad-extension-plugin/skills/ml-revision/.gitkeep +0 -0
  135. package/lib/bmad-extension-plugin/skills/ml-revision/SKILL.md +0 -82
  136. package/lib/bmad-extension-plugin/skills/ml-revision/bmad-skill-manifest.yaml +0 -3
  137. package/lib/bmad-extension-plugin/skills/ml-revision/skill.json +0 -7
  138. package/lib/bmad-extension-plugin/skills/ml-techspec/.gitkeep +0 -0
  139. package/lib/bmad-extension-plugin/skills/ml-techspec/SKILL.md +0 -80
  140. package/lib/bmad-extension-plugin/skills/ml-techspec/bmad-skill-manifest.yaml +0 -3
  141. package/lib/bmad-extension-plugin/skills/ml-techspec/skill.json +0 -7
package/README.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  A universal NPX tool to install AI coding agent skills. Write skills once, install them across Claude Code, Gemini, Copilot, Cline, Cursor, Kilocode, and Roo Code.
4
4
 
5
+ ## What's New in v3.17.0
6
+
7
+ - **BMAD-METHOD v6.10.0** — bundled bmad-method updated to v6.10.0. The
8
+ offline bundle stays exactly `{tea, bmb, cis, gds, wds}`; the new upstream
9
+ `bmad-loop` module is deliberately excluded (never bundled, never installed
10
+ by default) by a closed-allowlist filter with a two-sided fail-loud guard.
11
+ - **Custom marketplace source (opt-in)** — `--bmad-custom-source
12
+ <path|url>` (or the install-time Y/N prompt) lets you enumerate and install
13
+ a hand-picked subset of extensions from your own BMAD marketplace repo —
14
+ the sanctioned way to add `bmad-loop` or any other excluded module. The
15
+ source and your selection persist in `.ma-agents.json` and re-enumerate on
16
+ every `update`. See "Custom Marketplace Source" below.
17
+ - **Demerzel (ML Scientist) deprecated** — `ma-agent-ml` and its 12 `ml-*`
18
+ skills are no longer installed by default for new projects. Existing
19
+ installs are completely untouched (non-destructive); a one-time notice
20
+ explains how to opt back in via a custom marketplace source.
21
+
5
22
  ## What's New in v3.12.0
6
23
 
7
24
  - **Cline `.clinerules` directory fix** — `.clinerules` is now correctly created as a directory (matching Cline's native structure), not as a file. This prevents ENOTDIR errors during workflow generation and ensures `.clinerules/workflows/` is always generated correctly. Legacy installs that had `.clinerules` as a file are automatically migrated on the next install or update.
@@ -316,7 +333,7 @@ Running `ma-agents` on a project where skills are already installed triggers the
316
333
  - **Cleans up stale tool directories** — if a tool's skill path changed between releases (e.g., Copilot migrated from `.github/copilot/skills/` to `.github/skills/`), the old directory is automatically removed when the new one has content.
317
334
 
318
335
  ### BMAD-METHOD Integration
319
- `ma-agents` bundles [BMAD-METHOD](https://docs.bmad-method.org/) **v6.5.0** to provide advanced orchestration and specialized personas.
336
+ `ma-agents` bundles [BMAD-METHOD](https://docs.bmad-method.org/) **v6.10.0** to provide advanced orchestration and specialized personas.
320
337
 
321
338
  #### Core Features
322
339
  - **Auto-Detection**: Recognizes existing BMAD installations and prompts for silent updates.
@@ -330,16 +347,70 @@ Running `ma-agents` on a project where skills are already installed triggers the
330
347
  `ma-agents` ships with everything needed to install BMAD without any network access at install time:
331
348
 
332
349
  - **`bmad-method` is a direct npm dependency** — no runtime `npx bmad-method` fetch required. The correct version is pinned and bundled inside `node_modules/` when you `npm install ma-agents`.
333
- - **External modules pre-bundled** — `lib/bmad-cache/` ships the 4 optional BMAD modules (`bmb`, `cis`, `gds`, `tea`) inside the package itself. They are pre-populated to `~/.bmad/cache/external-modules/` before any install runs, so BMAD never needs to clone them from GitHub.
350
+ - **External modules pre-bundled** — `lib/bmad-cache/` ships exactly 5 optional BMAD modules (`bmb`, `cis`, `gds`, `tea`, `wds`) inside the package itself. They are pre-populated to `~/.bmad/cache/external-modules/` before any install runs, so BMAD never needs to clone them from GitHub. The bundled set is a **closed allowlist** — a build-time guard fails loud if the resolved set is ever smaller (a thin cache) *or* larger (a silent upstream addition) than these five, so a new upstream module can never be force-bundled or installed by default. `bmad-loop` (BMAD's own unattended-dev orchestrator, added upstream at v6.10.0) is deliberately **excluded** from this bundle — see "BMAD-loop and other opt-in modules" below.
334
351
  - **Plugin bundle pre-built** — `lib/bmad-extension-plugin/` ships as a deterministic, byte-stable bundle (built from `lib/bmad-extension/` via `scripts/build-plugin.js`). At install time it is staged into `<project>/.ma-agents-plugin-stage/` and consumed by `bmad-method install --custom-source`, so the install never touches the network for plugin assembly.
335
352
  - **Deterministic installs** — same `ma-agents` version always produces the same `_bmad/` output, regardless of network conditions (NFR21). Bundle reproducibility is gated by a CI drift check.
336
353
  - **Air-gapped friendly** — install from a tarball (`npm install ./ma-agents-x.y.z.tgz`) and run `npx ma-agents install` completely offline. Every PR runs an offline-install gate (`--network none` Docker) to enforce this guarantee (NFR54).
337
354
 
338
355
  ```
339
- npm install ma-agents # bundles bmad-method + 4 external modules
356
+ npm install ma-agents # bundles bmad-method + 5 external modules
340
357
  npx ma-agents install --yes # fully offline BMAD install — no network needed
341
358
  ```
342
359
 
360
+ #### Custom Marketplace Source (opt-in)
361
+
362
+ After module selection, the installer offers to pull in extensions from **one**
363
+ additional BMAD marketplace repository — a local path or an intra-perimeter
364
+ Git URL (e.g. on-prem GitLab) pointing at a repo with a
365
+ `.claude-plugin/marketplace.json`. This is how a team plugs in extensions
366
+ ma-agents deliberately does not ship by default — including **`bmad-loop`**
367
+ (see below) or an internally-published replacement for a deprecated agent.
368
+
369
+ - **Interactive flow**: `Install additional BMAD extensions from a custom
370
+ source? (y/N)` → if yes, enter the source → ma-agents **enumerates** the
371
+ extensions the source declares (reusing bmad-method's own module
372
+ discovery) → presents them in **ma-agents' own multiselect** (the
373
+ single-wizard principle is preserved; `bmad-method` itself still runs
374
+ non-interactively) → installs only the codes you picked.
375
+ - **Non-interactive equivalent**: `--bmad-custom-source <local-path-or-url>`.
376
+ Under `--yes`, every declared extension is selected automatically (matching
377
+ how `--bmad-modules`/skill selection behave under `--yes`).
378
+ - **Persistence + reconcile on update**: the marketplace source and your
379
+ selected extension codes are saved in `.ma-agents.json`. On the next
380
+ `update`, ma-agents automatically re-resolves the source and re-enumerates
381
+ it, reporting anything newly published (not auto-installed — re-run the
382
+ custom-source flow to add it) or no longer available (dropped from the
383
+ installed selection) — it never silently changes what you already have.
384
+ - **Authentication**: ambient git only — your existing SSH keys or credential
385
+ helper. ma-agents performs zero token handling and never stores or logs a
386
+ credential.
387
+ - **On-prem posture**: no public-internet egress is required (an on-prem
388
+ GitLab over SSH works with no extra configuration), but custom-source
389
+ content is explicitly **outside** the sealed offline-bundle guarantee above
390
+ — you are responsible for that source's own offline self-containment. The
391
+ installer prints a one-line warning before you can select anything from it.
392
+
393
+ ```bash
394
+ npx ma-agents install --yes --bmad-custom-source ./org-marketplace
395
+ npx ma-agents install --bmad-custom-source git@git.intra.example:team/bmad-marketplace.git
396
+ ```
397
+
398
+ #### `bmad-loop` and other opt-in modules
399
+
400
+ BMAD-METHOD v6.10.0 added `bmad-loop`, an unattended Python/tmux/hooks
401
+ development-orchestrator module that requires a separate `bmad-loop-setup`
402
+ step and vendor-API sessions. It is **air-gap-hostile as a default** and
403
+ functionally overlaps ma-agents' own `bmad-dev-epic` orchestrator, so
404
+ `bmad-loop` is **never bundled and never installed by default** — it is
405
+ excluded by the same closed-allowlist guard described above. `bmad-dev-epic`
406
+ remains ma-agents' default air-gapped epic orchestrator.
407
+
408
+ `bmad-loop` (or any other module ma-agents excludes) can still be installed
409
+ as a deliberate, explicit opt-in through a **custom marketplace source** (see
410
+ above) — for example a locally-published copy of `bmad-loop` in your org's
411
+ marketplace repo. This is the sanctioned path for teams that want it; nothing
412
+ in the default install pulls it in silently.
413
+
343
414
  #### Specialized BMAD Agents
344
415
  1. **SRE Agent (Alex)**:
345
416
  - **Focus**: High availability, Kubernetes, Docker, and Podman.
@@ -353,10 +424,20 @@ npx ma-agents install --yes # fully offline BMAD install — no network neede
353
424
  - **Focus**: Cyber immunity, security auditing, and vulnerability management.
354
425
  - **Capabilities**: Immunity estimation (scoring), Vault secret management, and PKI automation.
355
426
  - **Integration**: Orchestrates ma-agents security skills for deep scans.
356
- 4. **Demerzel (ML Scientist)**:
427
+ 4. **Demerzel (ML Scientist)** — **DEPRECATED, no longer installed by default (v3.17.0+)**:
357
428
  - **Focus**: Hypothesis-driven machine learning lifecycle with scientific rigor.
358
429
  - **Capabilities**: EDA, architecture design, locked TechSpec contracts, experiment execution, failure-cost analysis.
359
430
  - **Workflows**: 12-stage ML lifecycle from ideation through retrospective, with mandatory review gates.
431
+ - **Status**: `ma-agent-ml` and its 12 `ml-*` workflow skills are no longer
432
+ part of the default bundle for **new** installs. This is **non-destructive**
433
+ for everyone else — an **existing** install's `ma-agent-ml`/`ml-*` content
434
+ (and anything you produced with it) is left completely untouched on update;
435
+ there is no auto-removal and no prompt. You'll see a one-time notice the
436
+ first time you install or update after upgrading ma-agents, and that's it.
437
+ There is no in-tool migration path — if your team still needs ML
438
+ workflows, install a replacement from your organization's own BMAD
439
+ marketplace via the **custom marketplace source** described above
440
+ (`--bmad-custom-source`).
360
441
  5. **Gadi (SQA & Standards Expert)**:
361
442
  - **Focus**: Software quality assurance and defense documentation standards — a unified persona covering SQA auditing and MIL-STD-498 document generation.
362
443
  - **SQA Capabilities**: Multi-dimensional project quality audits, IEEE/ISO/IEC 12207:2017 compliance assessments (23 process areas, 4 groups), requirements evaluation against 14 established quality criteria (scope-selectable: PRD FRs, NFRs, epics, stories).
package/bin/cli.js CHANGED
@@ -34,11 +34,12 @@ const path = require('path');
34
34
  const fs = require('fs');
35
35
  const { execFileSync } = require('child_process');
36
36
  const { installSkill, uninstallSkill, getStatus, listSkills, listAgents, updateProjectContextRepoLayout, migrateRetiredSkills, reconcileAgentsSharedSkills } = require('../lib/installer');
37
- const { getProfile, setProfile, resolveProfile, getBmadModules, setBmadModules } = require('../lib/profile');
37
+ const { getProfile, setProfile, resolveProfile, getBmadModules, setBmadModules, showDemerzelDeprecationNoticeOnce, getCustomMarketplaceSelection } = require('../lib/profile');
38
38
  const { reconfigure: runReconfigure, ReconfigureYesRejectedError, ManifestNotFoundError, RoomodesSlugDivergenceError } = require('../lib/reconfigure');
39
39
  const { uninstallProfileArtifacts } = require('../lib/uninstall');
40
40
  const bmad = require('../lib/bmad');
41
41
  const { getBmadPlatformCode } = require('../lib/agents');
42
+ const { enumerateCustomSource, NFR71_WARNING, CustomMarketplaceError, reconcileCustomMarketplaceSelection } = require('../lib/custom-marketplace');
42
43
  const { handleCreateSkill, handleValidateSkill, handleSetMandatory, handleCustomizeAgent, handleCreateAgent } = require('../lib/skill-authoring');
43
44
 
44
45
  const PKG = require('../package.json');
@@ -119,6 +120,13 @@ ${chalk.bold('Install options:')}
119
120
  With CSV: install exactly those (e.g. tea,bmb)
120
121
  Omitted: install every non-retired module (default)
121
122
  'bmm' is always force-included.
123
+ ${chalk.cyan('--bmad-custom-source <ref>')} Enumerate extensions from ONE custom
124
+ marketplace repo (local path or Git URL containing
125
+ .claude-plugin/marketplace.json) and offer them in
126
+ ma-agents' own multiselect. Non-interactive equivalent
127
+ of the "Install additional BMAD extensions from a
128
+ custom source?" prompt. Ambient git credentials only —
129
+ no token handling.
122
130
  ${chalk.cyan('--log')} Log all console output to install_<datetime>.log
123
131
 
124
132
  ${chalk.bold('Reconfigure options:')}
@@ -142,6 +150,8 @@ ${chalk.bold('Examples:')}
142
150
  npx ${NAME} install --yes --global
143
151
  npx ${NAME} install --yes --bmad-modules bmm,tea,bmb
144
152
  npx ${NAME} install --bmad-modules # prompt for modules
153
+ npx ${NAME} install --yes --bmad-custom-source ./org-marketplace
154
+ npx ${NAME} install --bmad-custom-source git@git.intra.example:team/bmad-marketplace.git
145
155
  npx ${NAME} uninstall code-review claude-code
146
156
  npx ${NAME} status
147
157
  npx ${NAME} status --global
@@ -270,6 +280,23 @@ function parseFlags(args) {
270
280
  }
271
281
  }
272
282
 
283
+ // --bmad-custom-source <ref> — Story 30.6a. Requires an explicit value (a
284
+ // path or Git URL); unlike --bmad-modules there is no "bare = prompt" form
285
+ // because the underlying Y/N wizard prompt already exists for interactive
286
+ // runs. A missing value is an error, not a silent no-op, to avoid a typo'd
287
+ // invocation silently skipping the intended custom source.
288
+ let bmadCustomSourceFlag = '';
289
+ const bmadCustomSourceIdx = positional.indexOf('--bmad-custom-source');
290
+ if (bmadCustomSourceIdx !== -1) {
291
+ const value = positional[bmadCustomSourceIdx + 1];
292
+ if (!value || value.startsWith('--')) {
293
+ console.error(chalk.red('Error: --bmad-custom-source requires a path or Git URL'));
294
+ process.exit(1);
295
+ }
296
+ bmadCustomSourceFlag = value;
297
+ positional.splice(bmadCustomSourceIdx, 2);
298
+ }
299
+
273
300
  return {
274
301
  globalFlag,
275
302
  forceFlag,
@@ -278,6 +305,7 @@ function parseFlags(args) {
278
305
  customPath,
279
306
  bmadModulesFlag,
280
307
  bmadModulesPrompt,
308
+ bmadCustomSourceFlag,
281
309
  scope: globalFlag ? 'global' : 'project',
282
310
  positional
283
311
  };
@@ -918,9 +946,212 @@ async function selectBmadModules({ bmadModulesFlag, bmadModulesPrompt, projectRo
918
946
  return persist(installable.map(m => m.id));
919
947
  }
920
948
 
949
+ // --- Custom marketplace source (Story 30.6a) ---
950
+
951
+ /**
952
+ * Story 30.6b AC 6 — single source of truth for whether the base BMAD
953
+ * install/update step (Step 3.5) left a usable installation for the
954
+ * custom-marketplace subset install (Step 3.5b) to layer `--custom-source`
955
+ * onto. Pure function, exported for direct unit testing (no need to drive
956
+ * the full wizard to exercise this policy):
957
+ *
958
+ * - not attempted (user declined, or --yes short-circuited before any
959
+ * call): usable only if BMAD was ALREADY installed from a prior run —
960
+ * there is no `_bmad/` tree to layer onto for a declined FRESH install.
961
+ * - attempted: usable iff the install/update call itself reported success.
962
+ *
963
+ * @param {Object} ctx
964
+ * @param {boolean} ctx.bmadInstalled - Was BMAD already installed before this step?
965
+ * @param {boolean} ctx.attempted - Was `installBmad`/`updateBmad` actually invoked?
966
+ * @param {boolean} [ctx.success] - That call's return value (ignored if `attempted` is false).
967
+ * @returns {boolean}
968
+ */
969
+ function resolveBaseBmadOk({ bmadInstalled, attempted, success }) {
970
+ if (!attempted) return Boolean(bmadInstalled);
971
+ return Boolean(success);
972
+ }
973
+
974
+ /**
975
+ * FR239-FR241 — after BMAD module selection, offer to enumerate ONE custom
976
+ * marketplace source and let the user pick which of its declared extensions
977
+ * to install. This function does the Y/N prompt (or its `--bmad-custom-source`
978
+ * non-interactive equivalent), the enumerate call, the NFR71 warning, and
979
+ * ma-agents' OWN multiselect (FR240) — and stops there.
980
+ *
981
+ * Scope boundary (Story 30.6a vs 30.6b): this returns the user's selection —
982
+ * `{ source, mode, selectedCodes, plugins, rootDir, sourceUrl }` (the last two
983
+ * added by 30.6b — additive, existing 30.6a consumers only read the first
984
+ * four fields) — for Story 30.6b to consume for prune-and-stage. NO pruning,
985
+ * staging, or `bmad-method` invocation happens here or anywhere in this
986
+ * function. Returns `null` when the feature is not engaged (no flag, and
987
+ * either --yes with no flag, or the user answered "N") — this is the
988
+ * byte-identical-to-baseline "off" path required by NFR70.
989
+ *
990
+ * @param {Object} ctx
991
+ * @param {string} [ctx.bmadCustomSourceFlag] - `--bmad-custom-source <ref>` value
992
+ * @param {boolean} [ctx.yesFlag] - non-interactive run
993
+ * @returns {Promise<null|{ source: string, mode: 'discovery'|'direct', selectedCodes: string[],
994
+ * plugins: Array<Object>, rootDir: string, sourceUrl: string|null }>}
995
+ */
996
+ async function promptCustomMarketplace({ bmadCustomSourceFlag = '', yesFlag = false } = {}) {
997
+ let source = bmadCustomSourceFlag;
998
+
999
+ if (!source) {
1000
+ if (yesFlag) {
1001
+ // Non-interactive run with no flag — feature stays off (NFR70: a
1002
+ // no-source install is byte-identical to baseline). There is no
1003
+ // "bare = prompt" fallback for --yes, matching how other install-time
1004
+ // Y/N prompts behave under --yes.
1005
+ return null;
1006
+ }
1007
+ const { wantsCustomSource } = await prompts({
1008
+ type: 'confirm',
1009
+ name: 'wantsCustomSource',
1010
+ message: 'Install additional BMAD extensions from a custom source? (y/N)',
1011
+ initial: false,
1012
+ });
1013
+ if (!wantsCustomSource) return null;
1014
+
1015
+ const { sourceInput } = await prompts({
1016
+ type: 'text',
1017
+ name: 'sourceInput',
1018
+ message: 'Custom marketplace source (local path or Git URL):',
1019
+ });
1020
+ if (!sourceInput || !sourceInput.trim()) {
1021
+ console.log(chalk.yellow(' No source entered — skipping custom marketplace source.'));
1022
+ return null;
1023
+ }
1024
+ source = sourceInput.trim();
1025
+ }
1026
+
1027
+ let enumerated;
1028
+ try {
1029
+ enumerated = await enumerateCustomSource(source);
1030
+ } catch (err) {
1031
+ // Fail clean (spike §3 contract) — a short, human-readable message only.
1032
+ // Never print err.stack here: an unreachable/unauthorized custom source
1033
+ // must not dump a raw upstream stack, and must never touch the base
1034
+ // install (the rest of installWizard proceeds unaffected).
1035
+ const message = err instanceof CustomMarketplaceError ? err.message : `Custom marketplace source error: ${err.message}`;
1036
+ console.log(chalk.red(`\n ${message}`));
1037
+ console.log(chalk.gray(' Skipping custom marketplace source; the rest of the install is unaffected.'));
1038
+ return null;
1039
+ }
1040
+
1041
+ const { plugins, mode, rootDir, sourceUrl } = enumerated;
1042
+ if (!plugins || plugins.length === 0) {
1043
+ console.log(chalk.yellow(` Custom marketplace source "${source}" declared no plugins — nothing to select.`));
1044
+ return { source, mode, selectedCodes: [], plugins: [], rootDir, sourceUrl };
1045
+ }
1046
+
1047
+ // NFR71 — shown before the user can select/install anything from the source.
1048
+ console.log(chalk.yellow(`\n Warning: ${NFR71_WARNING}`));
1049
+
1050
+ if (yesFlag) {
1051
+ // --yes + --bmad-custom-source: select every declared plugin, matching
1052
+ // the "select all" default used elsewhere in --yes mode (bmad modules,
1053
+ // skills). The user asked for this source explicitly via the flag.
1054
+ const selectedCodes = plugins.map((p) => p.code);
1055
+ console.log(chalk.cyan(` Custom marketplace: selected all ${selectedCodes.length} extension(s) from "${source}".`));
1056
+ return { source, mode, selectedCodes, plugins, rootDir, sourceUrl };
1057
+ }
1058
+
1059
+ const { chosenCodes } = await prompts({
1060
+ type: 'multiselect',
1061
+ name: 'chosenCodes',
1062
+ message: `Select extensions to install from "${source}":`,
1063
+ choices: plugins.map((p) => ({
1064
+ title: chalk.white(p.code) + chalk.gray(p.description ? ` — ${p.description}` : ''),
1065
+ value: p.code,
1066
+ selected: true,
1067
+ })),
1068
+ instructions: chalk.gray(' Use space to toggle, enter to confirm.'),
1069
+ });
1070
+
1071
+ if (chosenCodes === undefined) {
1072
+ console.log(chalk.gray(' Custom marketplace selection cancelled.'));
1073
+ return null;
1074
+ }
1075
+
1076
+ return { source, mode, selectedCodes: chosenCodes, plugins, rootDir, sourceUrl };
1077
+ }
1078
+
1079
+ /**
1080
+ * Story 30.7 (FR242) — on `update`, re-enumerate a PERSISTED custom
1081
+ * marketplace source instead of asking the plain Y/N prompt again, and
1082
+ * reconcile the fresh plugin list against the persisted `selectedCodes`.
1083
+ * Newly-appeared plugins are reported ONLY — never added to the returned
1084
+ * selection. A code that is no longer declared is dropped and reported.
1085
+ *
1086
+ * Fail-loud contract: if the persisted source cannot be re-enumerated
1087
+ * (unreachable, removed, drifted adapter surface), this prints a clear,
1088
+ * visible error and returns `null` WITHOUT writing anything to
1089
+ * `.ma-agents.json` — the persisted selection is left exactly as it was so a
1090
+ * later successful `update` picks up where the user left off. This function
1091
+ * never calls `setCustomMarketplaceSelection` itself; persistence only ever
1092
+ * happens after a REAL install, in `lib/bmad.js#installCustomMarketplaceSubset`.
1093
+ *
1094
+ * @param {Object} ctx
1095
+ * @param {string} ctx.projectRoot
1096
+ * @returns {Promise<undefined|null|{ source: string, mode: 'discovery'|'direct',
1097
+ * selectedCodes: string[], plugins: Array<Object>, rootDir: string, sourceUrl: string|null }>}
1098
+ * `undefined` — nothing persisted; caller should fall back to `promptCustomMarketplace()`.
1099
+ * `null` — persisted source exists but could not be re-enumerated (fail-loud; skip this run).
1100
+ * selection object — reconciled; `selectedCodes` is the persisted set minus any `removed`.
1101
+ */
1102
+ async function reconcileCustomMarketplaceOnUpdate({ projectRoot }) {
1103
+ const persisted = getCustomMarketplaceSelection(projectRoot);
1104
+ if (!persisted) return undefined;
1105
+
1106
+ let enumerated;
1107
+ try {
1108
+ enumerated = await enumerateCustomSource(persisted.source);
1109
+ } catch (err) {
1110
+ const message = err instanceof CustomMarketplaceError ? err.message : `Custom marketplace source error: ${err.message}`;
1111
+ console.log(chalk.red(`\n Custom marketplace update FAILED: ${message}`));
1112
+ console.log(
1113
+ chalk.red(
1114
+ ` The persisted selection (${persisted.selectedCodes.join(', ') || '(none)'}) from ` +
1115
+ `"${persisted.source}" is UNCHANGED — it will be retried on the next update.`
1116
+ )
1117
+ );
1118
+ return null;
1119
+ }
1120
+
1121
+ const { plugins, mode, rootDir, sourceUrl } = enumerated;
1122
+ const { added, removed, stillPresent } = reconcileCustomMarketplaceSelection({
1123
+ persistedSelectedCodes: persisted.selectedCodes,
1124
+ enumeratedPlugins: plugins,
1125
+ });
1126
+
1127
+ console.log(chalk.cyan(`\n Custom marketplace source "${persisted.source}" re-enumerated:`));
1128
+ if (persisted.sha) {
1129
+ // The persisted SHA is the last-installed commit, INFORMATIONAL only.
1130
+ // Updates deliberately float to remote HEAD (FR242/NFR72) to surface
1131
+ // newly-published extensions — we report the last-installed SHA rather
1132
+ // than pin to it, so the float-to-HEAD behavior is explicit, not silent.
1133
+ console.log(chalk.gray(` (last installed at ${persisted.sha.slice(0, 12)}; update re-enumerates at the source's current HEAD, not this pin)`));
1134
+ }
1135
+ if (added.length > 0) {
1136
+ console.log(chalk.gray(` + newly available (not auto-installed; re-run the custom-source flow to add): ${added.join(', ')}`));
1137
+ }
1138
+ if (removed.length > 0) {
1139
+ console.log(chalk.yellow(` - no longer available (dropped from the installed selection): ${removed.join(', ')}`));
1140
+ }
1141
+ if (added.length === 0 && removed.length === 0) {
1142
+ console.log(chalk.gray(' No changes vs. the persisted selection.'));
1143
+ }
1144
+
1145
+ if (stillPresent.length === 0) {
1146
+ console.log(chalk.yellow(' None of the previously-selected extensions are declared by this source anymore — nothing to reinstall from it.'));
1147
+ }
1148
+
1149
+ return { source: persisted.source, mode, selectedCodes: stillPresent, plugins, rootDir, sourceUrl };
1150
+ }
1151
+
921
1152
  // --- Install wizard ---
922
1153
 
923
- async function installWizard(preselectedSkill, preselectedAgents, customPath, forceFlag, yesFlag = false, cliScope, bmadModulesFlag = '', bmadModulesPrompt = false) {
1154
+ async function installWizard(preselectedSkill, preselectedAgents, customPath, forceFlag, yesFlag = false, cliScope, bmadModulesFlag = '', bmadModulesPrompt = false, bmadCustomSourceFlag = '') {
924
1155
  const skills = listSkills();
925
1156
  const agents = listAgents();
926
1157
 
@@ -1209,10 +1440,16 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
1209
1440
  projectRoot: process.cwd(),
1210
1441
  });
1211
1442
 
1443
+ // Story 30.6b AC 6 — tracks whether the base BMAD install/update step
1444
+ // left a usable installation for the custom-marketplace subset install
1445
+ // (Step 3.5b) to layer onto. See resolveBaseBmadOk() for the policy.
1446
+ let baseBmadOk = resolveBaseBmadOk({ bmadInstalled, attempted: false });
1447
+
1212
1448
  if (!bmadInstalled) {
1213
1449
  if (yesFlag) {
1214
1450
  console.log(chalk.cyan(`\n Installing BMAD-METHOD (modules: ${bmadModules.join(', ')})...`));
1215
1451
  const success = await bmad.installBmad(bmadModules, bmadTools);
1452
+ baseBmadOk = resolveBaseBmadOk({ bmadInstalled, attempted: true, success });
1216
1453
  if (success) {
1217
1454
  console.log(chalk.green(' BMAD-METHOD installed successfully!'));
1218
1455
  }
@@ -1227,15 +1464,19 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
1227
1464
  if (installBmad) {
1228
1465
  console.log(chalk.cyan(`\n Installing BMAD-METHOD (modules: ${bmadModules.join(', ')})...`));
1229
1466
  const success = await bmad.installBmad(bmadModules, bmadTools);
1467
+ baseBmadOk = resolveBaseBmadOk({ bmadInstalled, attempted: true, success });
1230
1468
  if (success) {
1231
1469
  console.log(chalk.green(' BMAD-METHOD installed successfully!'));
1232
1470
  }
1471
+ } else {
1472
+ baseBmadOk = resolveBaseBmadOk({ bmadInstalled, attempted: false });
1233
1473
  }
1234
1474
  }
1235
1475
  } else {
1236
1476
  if (yesFlag) {
1237
1477
  console.log(chalk.cyan(`\n Updating BMAD-METHOD (modules: ${bmadModules.join(', ')})...`));
1238
1478
  const success = await bmad.updateBmad(bmadModules, bmadTools);
1479
+ baseBmadOk = resolveBaseBmadOk({ bmadInstalled, attempted: true, success });
1239
1480
  if (success) {
1240
1481
  console.log(chalk.green(' BMAD-METHOD updated successfully!'));
1241
1482
  }
@@ -1250,10 +1491,70 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
1250
1491
  if (updateBmad) {
1251
1492
  console.log(chalk.cyan(`\n Updating BMAD-METHOD (modules: ${bmadModules.join(', ')})...`));
1252
1493
  const success = await bmad.updateBmad(bmadModules, bmadTools);
1494
+ baseBmadOk = resolveBaseBmadOk({ bmadInstalled, attempted: true, success });
1253
1495
  if (success) {
1254
1496
  console.log(chalk.green(' BMAD-METHOD updated successfully!'));
1255
1497
  }
1256
1498
  }
1499
+ // else: update declined, but BMAD was already installed from a
1500
+ // prior run — resolveBaseBmadOk({attempted:false}) is `bmadInstalled`,
1501
+ // i.e. still true (there IS a usable base).
1502
+ }
1503
+ }
1504
+
1505
+ // Step 3.5b: Custom marketplace source (Story 30.6a enumerate + wizard;
1506
+ // Story 30.6b prune + stage + install-the-subset; Story 30.7 persist +
1507
+ // re-enumerate/reconcile on update, FR240/FR242).
1508
+ let customMarketplaceSelection = null;
1509
+ // Story 30.7 — on a BMAD-METHOD *update* (i.e. `bmadInstalled` was
1510
+ // already true before this run, so bmad.updateBmad() ran above — this
1511
+ // is the FR242 "on update" case, and is DELIBERATELY independent of
1512
+ // `isUpdate`, which only tracks ma-agents' own "Update (add/remove
1513
+ // skills)" wizard choice and stays false under `--yes` reruns) with NO
1514
+ // explicit `--bmad-custom-source` override, prefer re-enumerating a
1515
+ // PERSISTED source over asking the plain Y/N prompt again. An explicit
1516
+ // flag always wins (fresh engagement via the normal prompt path below).
1517
+ // `undefined` means nothing was persisted — fall through unchanged.
1518
+ let usedPersistedReconcile = false;
1519
+ if (bmadInstalled && !bmadCustomSourceFlag) {
1520
+ const reconciled = await reconcileCustomMarketplaceOnUpdate({ projectRoot: process.cwd() });
1521
+ if (reconciled !== undefined) {
1522
+ usedPersistedReconcile = true;
1523
+ customMarketplaceSelection = reconciled; // null (fail-loud skip) or a reconciled selection
1524
+ }
1525
+ }
1526
+ if (!usedPersistedReconcile) {
1527
+ try {
1528
+ customMarketplaceSelection = await promptCustomMarketplace({ bmadCustomSourceFlag, yesFlag });
1529
+ } catch (err) {
1530
+ console.log(chalk.yellow(`\n Custom marketplace source skipped: ${err.message}`));
1531
+ }
1532
+ }
1533
+ if (customMarketplaceSelection && customMarketplaceSelection.selectedCodes.length > 0) {
1534
+ if (!baseBmadOk) {
1535
+ // AC 6 (30.6b) — never layer --custom-source onto a project with no
1536
+ // (or a broken) base BMAD install. The rest of the install is
1537
+ // otherwise unaffected.
1538
+ console.log(
1539
+ chalk.yellow(
1540
+ '\n Skipping custom marketplace install — the base BMAD install/update did not ' +
1541
+ 'complete successfully, so there is no BMAD installation to add it to.'
1542
+ )
1543
+ );
1544
+ } else {
1545
+ try {
1546
+ const success = await bmad.installCustomMarketplaceSubset({
1547
+ projectRoot: process.cwd(),
1548
+ modules: bmadModules,
1549
+ tools: bmadTools,
1550
+ selection: customMarketplaceSelection,
1551
+ });
1552
+ if (success) {
1553
+ console.log(chalk.green(' Custom marketplace subset installed successfully!'));
1554
+ }
1555
+ } catch (err) {
1556
+ console.log(chalk.red(`\n Custom marketplace install failed: ${err.message}`));
1557
+ }
1257
1558
  }
1258
1559
  }
1259
1560
  }
@@ -1341,6 +1642,14 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
1341
1642
  console.log(chalk.yellow(`\n Retired-skill migration skipped: ${err.message}`));
1342
1643
  }
1343
1644
 
1645
+ // Story 30.4 (FR245/FR246) — idempotent one-time Demerzel deprecation
1646
+ // notice. No-op after the first time it has ever printed for this project.
1647
+ try {
1648
+ showDemerzelDeprecationNoticeOnce(process.cwd());
1649
+ } catch (err) {
1650
+ console.log(chalk.yellow(`\n Deprecation notice skipped: ${err.message}`));
1651
+ }
1652
+
1344
1653
  // Install requested skills
1345
1654
  const batchState = {};
1346
1655
  if (yesFlag) {
@@ -1424,7 +1733,7 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
1424
1733
  // --- Command handlers ---
1425
1734
 
1426
1735
  async function handleInstall(args) {
1427
- const { globalFlag, forceFlag, yesFlag, agentFlag, customPath, bmadModulesFlag, bmadModulesPrompt, scope, positional } = parseFlags(args);
1736
+ const { globalFlag, forceFlag, yesFlag, agentFlag, customPath, bmadModulesFlag, bmadModulesPrompt, bmadCustomSourceFlag, scope, positional } = parseFlags(args);
1428
1737
 
1429
1738
  // Task 3.2: Validate agentFlag against known agents
1430
1739
  if (agentFlag) {
@@ -1443,13 +1752,13 @@ async function handleInstall(args) {
1443
1752
 
1444
1753
  // No skill → launch wizard (Task 3.3, 3.4)
1445
1754
  if (!skillId) {
1446
- await installWizard(null, preselectedAgents, customPath, forceFlag, yesFlag, scope, bmadModulesFlag, bmadModulesPrompt);
1755
+ await installWizard(null, preselectedAgents, customPath, forceFlag, yesFlag, scope, bmadModulesFlag, bmadModulesPrompt, bmadCustomSourceFlag);
1447
1756
  return;
1448
1757
  }
1449
1758
 
1450
1759
  // Skill but no agents (and no agentFlag) → wizard with skill preselected
1451
1760
  if (!agentFlag && agentIds.length === 0) {
1452
- await installWizard(skillId, null, customPath, forceFlag, yesFlag, scope, bmadModulesFlag, bmadModulesPrompt);
1761
+ await installWizard(skillId, null, customPath, forceFlag, yesFlag, scope, bmadModulesFlag, bmadModulesPrompt, bmadCustomSourceFlag);
1453
1762
  return;
1454
1763
  }
1455
1764
 
@@ -1506,6 +1815,14 @@ async function handleInstall(args) {
1506
1815
  console.log(chalk.yellow(`\n Retired-skill migration skipped: ${err.message}`));
1507
1816
  }
1508
1817
 
1818
+ // Story 30.4 (FR245/FR246) — idempotent one-time Demerzel deprecation
1819
+ // notice. No-op after the first time it has ever printed for this project.
1820
+ try {
1821
+ showDemerzelDeprecationNoticeOnce(process.cwd());
1822
+ } catch (err) {
1823
+ console.log(chalk.yellow(`\n Deprecation notice skipped: ${err.message}`));
1824
+ }
1825
+
1509
1826
  try {
1510
1827
  await installSkill(skillId, targetAgents, customPath, scope, { force: forceFlag, yes: yesFlag });
1511
1828
  console.log(chalk.bold.green('\n Installation complete!\n'));
@@ -1772,4 +2089,4 @@ if (require.main === module) {
1772
2089
  });
1773
2090
  }
1774
2091
 
1775
- module.exports = { parseFlags, selectBmadModules, collectRepoLayout, readExistingLayout, writeRepoLayoutConfig, writeProjectLayoutYaml, writeConfigField, normalizePath, toPortablePath, resolveStoredPath, ciCloneIfNeeded, showCurrentLayout, handleConfigLayout, yamlEscapeValue };
2092
+ module.exports = { parseFlags, selectBmadModules, promptCustomMarketplace, reconcileCustomMarketplaceOnUpdate, resolveBaseBmadOk, collectRepoLayout, readExistingLayout, writeRepoLayoutConfig, writeProjectLayoutYaml, writeConfigField, normalizePath, toPortablePath, resolveStoredPath, ciCloneIfNeeded, showCurrentLayout, handleConfigLayout, yamlEscapeValue };
package/lib/agents.js CHANGED
@@ -151,7 +151,20 @@ const agents = [
151
151
  'assets': 'templates'
152
152
  },
153
153
  instructionFiles: ['.cline/clinerules.md'],
154
- injectionStrategy: { position: 'top', skipPatterns: ['---'] }
154
+ // Story 30.9 registry-driven capability flags replace the previous
155
+ // `agent.id === 'cline'` literal checks in lib/installer.js.
156
+ // `dualFileDriftCheck` opts this agent into the legacy
157
+ // `.cline/clinerules.md` vs `.clinerules`-file drift guard (Story 21.5
158
+ // AC #6). `framingTemplate` names the fresh-file framing template
159
+ // (relative to lib/templates/) used when creating the instruction file
160
+ // from scratch (Story 21.5 AC #1/#2). Both are absent (falsy) on every
161
+ // other agent — installer.js treats that as "skip this behavior".
162
+ injectionStrategy: {
163
+ position: 'top',
164
+ skipPatterns: ['---'],
165
+ dualFileDriftCheck: true,
166
+ framingTemplate: 'clinerules.template.md'
167
+ }
155
168
  },
156
169
  {
157
170
  id: 'roo-code',
@@ -326,20 +339,14 @@ const agents = [
326
339
  instructionFiles: ['_bmad/bmm/agents/qa.md'],
327
340
  injectionStrategy: { position: 'top', skipPatterns: ['---'] }
328
341
  },
329
- {
330
- id: 'bmm-demerzel',
331
- name: 'ML Scientist (Demerzel)',
332
- version: '1.0.0',
333
- category: 'bmad',
334
- description: 'Machine Learning Lifecycle Agent (Demerzel)',
335
- skillsDir: '_bmad/skills/demerzel',
336
- getProjectPath: () => path.join(process.cwd(), '_bmad', 'skills', 'demerzel'),
337
- getGlobalPath: () => null,
338
- fileExtension: '.md',
339
- template: 'generic',
340
- instructionFiles: ['_bmad/bmm/agents/demerzel.md'],
341
- injectionStrategy: { position: 'top', skipPatterns: ['---'] }
342
- }
342
+ // Story 30.4 / Decision P5-8, FR244-FR247: the 'bmm-demerzel' (ML Scientist)
343
+ // agent registration is REMOVED for new installs — Demerzel is deprecated
344
+ // and its ma-agent-ml + 12 ml-* skills no longer ship in the BMAD-extension
345
+ // plugin (lib/bmad-extension/skills/). Existing installs that already have
346
+ // this content deployed are left untouched (NFR49) — see
347
+ // lib/profile.js#showDemerzelDeprecationNoticeOnce for the one-time notice.
348
+ // Supersedes FR193 (the deferred Demerzel -> Memory-agent conversion is
349
+ // cancelled).
343
350
  // DEFERRED (Story 28.3 / Decision P5-6(d), FR214): BMAD v6.9.0 adds two new
344
351
  // installer platform targets — `hermes` (Hermes Agent) and `codewhale`
345
352
  // (CodeWhale). They are intentionally NOT added to this registry. Passthrough
@@ -1,6 +1,6 @@
1
1
  {
2
- "generated": "2026-06-27T14:28:18.113Z",
3
- "bmadMethodVersion": "6.9.0",
2
+ "generated": "2026-07-12T07:02:48.848Z",
3
+ "bmadMethodVersion": "6.10.0",
4
4
  "registrySchema": "package-root-v68",
5
5
  "modules": {
6
6
  "tea": {