bmad-method 6.6.1-next.8 → 6.7.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 (27) hide show
  1. package/README.md +3 -3
  2. package/{tools/installer/modules/registry-fallback.yaml → bmad-modules.yaml} +29 -15
  3. package/package.json +4 -4
  4. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +18 -11
  5. package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +13 -8
  6. package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +54 -57
  7. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md +2 -2
  8. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-template.md +40 -30
  9. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-validation-checklist.md +126 -21
  10. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/validation-report-template.html +193 -58
  11. package/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml +47 -13
  12. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +27 -12
  13. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md +97 -0
  14. package/src/bmm-skills/module.yaml +2 -2
  15. package/src/core-skills/module.yaml +1 -1
  16. package/tools/installer/core/installer.js +1 -22
  17. package/tools/installer/core/manifest.js +0 -22
  18. package/tools/installer/modules/channel-plan.js +1 -1
  19. package/tools/installer/modules/external-manager.js +9 -27
  20. package/tools/installer/modules/official-modules.js +9 -48
  21. package/tools/installer/prompts.js +149 -0
  22. package/tools/installer/ui.js +13 -197
  23. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/facilitation-guide.md +0 -79
  24. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validation-render.md +0 -58
  25. package/src/bmm-skills/2-plan-workflows/bmad-prd/scripts/render-validation-html.py +0 -290
  26. package/tools/installer/modules/community-manager.js +0 -704
  27. package/tools/installer/modules/registry-client.js +0 -187
@@ -818,32 +818,18 @@ class UI {
818
818
  // Phase 1: Official modules
819
819
  const officialSelected = await this._selectOfficialModules(installedModuleIds, installedModuleVersions, channelOptions);
820
820
 
821
- // Determine which installed modules are NOT official (community or custom).
822
- // These must be preserved even if the user declines to browse community/custom.
823
- const officialCodes = new Set(officialSelected);
821
+ // Identify installed modules that aren't official (previously installed
822
+ // community modules or custom-source modules). Preserve them on update;
823
+ // they can be managed via --custom-source, uninstall, or a dedicated installer.
824
824
  const externalManager = new ExternalModuleManager();
825
825
  const registryModules = await externalManager.listAvailable();
826
826
  const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]);
827
827
  const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
828
828
 
829
- // Phase 2: Community modules (category drill-down)
830
- // Returns { codes, didBrowse } so we know if the user entered the flow
831
- const communityResult = await this._browseCommunityModules(installedModuleIds);
832
-
833
- // Phase 3: Custom URL modules
829
+ // Phase 2: Custom URL modules
834
830
  const customSelected = await this._addCustomUrlModules(installedModuleIds);
835
831
 
836
- // Merge all selections
837
- const allSelected = new Set([...officialSelected, ...communityResult.codes, ...customSelected]);
838
-
839
- // Auto-include installed non-official modules that the user didn't get
840
- // a chance to manage (they declined to browse). If they did browse,
841
- // trust their selections - they could have deselected intentionally.
842
- if (!communityResult.didBrowse) {
843
- for (const code of installedNonOfficial) {
844
- allSelected.add(code);
845
- }
846
- }
832
+ const allSelected = new Set([...officialSelected, ...customSelected, ...installedNonOfficial]);
847
833
 
848
834
  return [...allSelected];
849
835
  }
@@ -954,166 +940,6 @@ class UI {
954
940
  return result;
955
941
  }
956
942
 
957
- /**
958
- * Browse and select community modules using category drill-down.
959
- * Featured/promoted modules appear at the top.
960
- * @param {Set} installedModuleIds - Currently installed module IDs
961
- * @returns {Object} { codes: string[], didBrowse: boolean }
962
- */
963
- async _browseCommunityModules(installedModuleIds = new Set()) {
964
- const browseCommunity = await prompts.confirm({
965
- message: 'Would you like to browse community modules?',
966
- default: false,
967
- });
968
- if (!browseCommunity) return { codes: [], didBrowse: false };
969
-
970
- const { CommunityModuleManager } = require('./modules/community-manager');
971
- const communityMgr = new CommunityModuleManager();
972
-
973
- const s = await prompts.spinner();
974
- s.start('Loading community module catalog...');
975
-
976
- let categories, featured, allCommunity;
977
- try {
978
- [categories, featured, allCommunity] = await Promise.all([
979
- communityMgr.getCategoryList(),
980
- communityMgr.listFeatured(),
981
- communityMgr.listAll(),
982
- ]);
983
- s.stop(`Community catalog loaded (${allCommunity.length} modules)`);
984
- } catch (error) {
985
- s.error('Failed to load community catalog');
986
- await prompts.log.warn(` ${error.message}`);
987
- return { codes: [], didBrowse: false };
988
- }
989
-
990
- if (allCommunity.length === 0) {
991
- await prompts.log.info('No community modules are currently available.');
992
- return { codes: [], didBrowse: false };
993
- }
994
-
995
- const selectedCodes = new Set();
996
- let browsing = true;
997
-
998
- while (browsing) {
999
- const categoryChoices = [];
1000
-
1001
- // Featured section at top
1002
- if (featured.length > 0) {
1003
- categoryChoices.push({
1004
- value: '__featured__',
1005
- label: `\u2605 Featured (${featured.length} module${featured.length === 1 ? '' : 's'})`,
1006
- });
1007
- }
1008
-
1009
- // Categories with module counts
1010
- for (const cat of categories) {
1011
- categoryChoices.push({
1012
- value: cat.slug,
1013
- label: `${cat.name} (${cat.moduleCount} module${cat.moduleCount === 1 ? '' : 's'})`,
1014
- });
1015
- }
1016
-
1017
- // Special actions at bottom
1018
- categoryChoices.push(
1019
- { value: '__all__', label: '\u25CE View all community modules' },
1020
- { value: '__search__', label: '\u25CE Search by keyword' },
1021
- { value: '__done__', label: '\u2713 Done browsing' },
1022
- );
1023
-
1024
- const selectedCount = selectedCodes.size;
1025
- const categoryChoice = await prompts.select({
1026
- message: `Browse community modules${selectedCount > 0 ? ` (${selectedCount} selected)` : ''}:`,
1027
- choices: categoryChoices,
1028
- });
1029
-
1030
- if (categoryChoice === '__done__') {
1031
- browsing = false;
1032
- continue;
1033
- }
1034
-
1035
- let modulesToShow;
1036
- switch (categoryChoice) {
1037
- case '__featured__': {
1038
- modulesToShow = featured;
1039
-
1040
- break;
1041
- }
1042
- case '__all__': {
1043
- modulesToShow = allCommunity;
1044
-
1045
- break;
1046
- }
1047
- case '__search__': {
1048
- const query = await prompts.text({
1049
- message: 'Search community modules:',
1050
- placeholder: 'e.g., design, testing, game',
1051
- });
1052
- if (!query || query.trim() === '') continue;
1053
- modulesToShow = await communityMgr.searchByKeyword(query.trim());
1054
- if (modulesToShow.length === 0) {
1055
- await prompts.log.warn('No matching modules found.');
1056
- continue;
1057
- }
1058
-
1059
- break;
1060
- }
1061
- default: {
1062
- modulesToShow = await communityMgr.listByCategory(categoryChoice);
1063
- }
1064
- }
1065
-
1066
- // Build options for autocompleteMultiselect
1067
- const trustBadge = (tier) => {
1068
- if (tier === 'bmad-certified') return '\u2713';
1069
- if (tier === 'community-reviewed') return '\u25CB';
1070
- return '\u26A0';
1071
- };
1072
-
1073
- const options = modulesToShow.map((mod) => {
1074
- const versionStr = mod.version ? ` (v${mod.version})` : '';
1075
- const badge = trustBadge(mod.trustTier);
1076
- return {
1077
- label: `${mod.displayName}${versionStr} [${badge}]`,
1078
- value: mod.code,
1079
- hint: mod.description,
1080
- };
1081
- });
1082
-
1083
- // Pre-check modules that are already selected or installed
1084
- const initialValues = modulesToShow.filter((m) => selectedCodes.has(m.code) || installedModuleIds.has(m.code)).map((m) => m.code);
1085
-
1086
- const selected = await prompts.autocompleteMultiselect({
1087
- message: 'Select community modules:',
1088
- options,
1089
- initialValues: initialValues.length > 0 ? initialValues : undefined,
1090
- required: false,
1091
- maxItems: Math.min(options.length, 10),
1092
- });
1093
-
1094
- // Update accumulated selections: sync with what user selected in this view
1095
- const shownCodes = new Set(modulesToShow.map((m) => m.code));
1096
- for (const code of shownCodes) {
1097
- if (selected && selected.includes(code)) {
1098
- selectedCodes.add(code);
1099
- } else {
1100
- selectedCodes.delete(code);
1101
- }
1102
- }
1103
- }
1104
-
1105
- if (selectedCodes.size > 0) {
1106
- const moduleLines = [];
1107
- for (const code of selectedCodes) {
1108
- const mod = await communityMgr.getModuleByCode(code);
1109
- moduleLines.push(` \u2022 ${mod?.displayName || code}`);
1110
- }
1111
- await prompts.log.message('Selected community modules:\n' + moduleLines.join('\n'));
1112
- }
1113
-
1114
- return { codes: [...selectedCodes], didBrowse: true };
1115
- }
1116
-
1117
943
  /**
1118
944
  * Prompt user to install modules from custom sources (Git URLs or local paths).
1119
945
  * @param {Set} installedModuleIds - Currently installed module IDs
@@ -1121,7 +947,7 @@ class UI {
1121
947
  */
1122
948
  async _addCustomUrlModules(installedModuleIds = new Set()) {
1123
949
  const addCustom = await prompts.confirm({
1124
- message: 'Would you like to install from a custom source (Git URL or local path)?',
950
+ message: 'Do you want to install custom or community modules (Git URL or local path)?',
1125
951
  default: false,
1126
952
  });
1127
953
  if (!addCustom) return [];
@@ -1436,7 +1262,7 @@ class UI {
1436
1262
  */
1437
1263
  async promptForDirectory() {
1438
1264
  // Use sync validation because @clack/prompts doesn't support async validate
1439
- const directory = await prompts.text({
1265
+ const directory = await prompts.directory({
1440
1266
  message: 'Installation directory:',
1441
1267
  default: process.cwd(),
1442
1268
  placeholder: process.cwd(),
@@ -1885,19 +1711,14 @@ class UI {
1885
1711
  const haveFlagIntent = channelOptions.global || channelOptions.nextSet.size > 0 || channelOptions.pins.size > 0;
1886
1712
  if (haveFlagIntent) return;
1887
1713
 
1888
- // Figure out which selected modules actually get a channel (externals +
1889
- // community modules). Bundled core/bmm and custom modules skip the picker.
1714
+ // Figure out which selected modules actually get a channel (externals only).
1715
+ // Bundled core/bmm and custom modules skip the picker.
1890
1716
  const externalManager = new ExternalModuleManager();
1891
1717
  const externals = await externalManager.listAvailable();
1892
1718
  const externalByCode = new Map(externals.map((m) => [m.code, m]));
1893
1719
 
1894
- const { CommunityModuleManager } = require('./modules/community-manager');
1895
- const communityMgr = new CommunityModuleManager();
1896
- const community = await communityMgr.listAll();
1897
- const communityByCode = new Map(community.map((m) => [m.code, m]));
1898
-
1899
1720
  const channelSelectable = selectedModules.filter((code) => {
1900
- const info = externalByCode.get(code) || communityByCode.get(code);
1721
+ const info = externalByCode.get(code);
1901
1722
  return info && !info.builtIn;
1902
1723
  });
1903
1724
  if (channelSelectable.length === 0) return;
@@ -1912,7 +1733,7 @@ class UI {
1912
1733
  const { fetchStableTags, parseGitHubRepo } = require('./modules/channel-resolver');
1913
1734
 
1914
1735
  for (const code of channelSelectable) {
1915
- const info = externalByCode.get(code) || communityByCode.get(code);
1736
+ const info = externalByCode.get(code);
1916
1737
  const repoUrl = info.url;
1917
1738
 
1918
1739
  // Try to pre-resolve the top stable tag so we can surface it in the picker.
@@ -1987,11 +1808,6 @@ class UI {
1987
1808
  const externals = await externalManager.listAvailable();
1988
1809
  const externalByCode = new Map(externals.map((m) => [m.code, m]));
1989
1810
 
1990
- const { CommunityModuleManager } = require('./modules/community-manager');
1991
- const communityMgr = new CommunityModuleManager();
1992
- const community = await communityMgr.listAll();
1993
- const communityByCode = new Map(community.map((m) => [m.code, m]));
1994
-
1995
1811
  const { fetchStableTags, classifyUpgrade, releaseNotesUrl } = require('./modules/channel-resolver');
1996
1812
  const { parseGitHubRepo } = require('./modules/channel-resolver');
1997
1813
 
@@ -2003,7 +1819,7 @@ class UI {
2003
1819
  const existingWithChannel = selectedModules.filter((code) => {
2004
1820
  const prev = existingByName.get(code);
2005
1821
  if (!prev) return false;
2006
- const info = externalByCode.get(code) || communityByCode.get(code);
1822
+ const info = externalByCode.get(code);
2007
1823
  return info && !info.builtIn;
2008
1824
  });
2009
1825
  if (existingWithChannel.length > 0) {
@@ -2018,7 +1834,7 @@ class UI {
2018
1834
  const prev = existingByName.get(code);
2019
1835
  if (!prev) continue;
2020
1836
 
2021
- const info = externalByCode.get(code) || communityByCode.get(code);
1837
+ const info = externalByCode.get(code);
2022
1838
  if (!info) continue;
2023
1839
  // Bundled modules (core/bmm) ship with the installer binary itself —
2024
1840
  // their version is stapled to the CLI version, not a git tag. Skip
@@ -1,79 +0,0 @@
1
- # PRD Facilitation Guide
2
-
3
- Per-section conversation techniques for facilitative mode. Each entry names the coaching move that makes the section's conversation productive — not a checklist, a posture. Skip sections the PM has already resolved; spend more time where thinking is thin.
4
-
5
- ---
6
-
7
- ## Users and Personas
8
-
9
- **The move:** Ground personas in real people, not archetypes.
10
-
11
- Ask the PM to describe a specific person they have observed or talked to — not a type, an actual human. "Who is the clerk at your store? Tell me about them." Invented detail (name, age, backstory from nowhere) is persona theater — the team builds for a fiction. If the PM says "someone like..." push gently: "Is there a real person you're thinking of?"
12
-
13
- Once grounded: what does that person want to accomplish in the time they interact with this product? What would make them say this is easier than what they do today? What would make them abandon it?
14
-
15
- For the remote user or secondary persona: same grounding, different question — what question do they need answered in under ten seconds, and what do they do if they can't get it?
16
-
17
- Mark anything the PM could not ground in observation as `[ILLUSTRATIVE]` — and note it's a hypothesis to validate, not a spec to build for.
18
-
19
- ---
20
-
21
- ## Core User Journeys
22
-
23
- **The move:** Story structure, not use-case list.
24
-
25
- For each primary journey, walk through four beats:
26
-
27
- - **Opening scene** — where do we meet this person, what is their situation right now, what pain or need is present?
28
- - **Rising action** — what steps do they take, what do they discover or decide along the way?
29
- - **Climax** — the moment the product delivers real value; the thing they could not do before
30
- - **Resolution** — what is their new reality; how is their situation different?
31
-
32
- After each journey: what could go wrong at the climax? What is the recovery path? This is where edge cases that matter surface — not invented error states, but real failure modes for this person.
33
-
34
- Explicitly name what capability each journey reveals. "This journey requires the operator to log an entry with no internet — which means we need a decision on whether that's in or out of MVP." Journeys produce capability requirements; make the link visible.
35
-
36
- ---
37
-
38
- ## Key Feature Decisions
39
-
40
- **The move:** Surface the assumptions that would otherwise be silent.
41
-
42
- Before the draft exists, there are decisions the agent would silently make and the PM would never know were made. These are the ones worth a thirty-second conversation:
43
-
44
- - Decisions that drive the core UX model (e.g., one record per day vs. many; who can edit vs. view; what happens when the expected input doesn't exist)
45
- - Decisions where the "obvious" choice has real consequences the PM may not have considered
46
- - Decisions that, if wrong, require structural changes to fix later
47
-
48
- For each: state what you inferred, name the alternative, ask which is right. Do not present options as a quiz — present your inference and invite correction. "I'm assuming one sales tally per day replaces rather than adds. Is that right, or should the operator be able to log multiple?" Resolve and move on. Only tag as `[ASSUMPTION]` when the answer requires external input or research the PM cannot provide now.
49
-
50
- ---
51
-
52
- ## Scope Boundary
53
-
54
- **The move:** Establish MVP philosophy before listing features.
55
-
56
- Before asking what is in or out, ask what kind of MVP this is:
57
-
58
- - **Problem-solving MVP** — the minimum that proves the core problem is solved; rough edges acceptable
59
- - **Experience MVP** — the minimum that proves the interaction model works; quality matters
60
- - **Platform MVP** — the minimum infrastructure other things can build on; completeness of the base matters
61
- - **Revenue MVP** — the minimum someone will pay for; business viability is the test
62
-
63
- The answer changes what "minimum" means. A problem-solving MVP for a personal-use tool has different scope logic than an experience MVP aimed at non-tech-savvy users who will bounce at the first confusion.
64
-
65
- Once the philosophy is named, non-goals do as much work as in-scope items. Probe for the things the PM is tempted to add. "What keeps almost making it onto the list?" For each: is it truly out of MVP, or does it need to be in because the MVP fails without it?
66
-
67
- ---
68
-
69
- ## Success Metrics
70
-
71
- **The move:** Push every adjective to a measurement.
72
-
73
- "Users will love it" — what does that mean in behavior? "It'll be fast" — fast at what, for whom, measured how? "Good adoption" — what percentage, by when, doing what? Every quality claim needs a measurement or it is not a success criterion, it is a wish.
74
-
75
- For each metric surfaced: connect it back to the product's differentiator. If the differentiator is simplicity for non-tech users, the primary metric should measure whether non-tech users successfully complete the core action without help — not session count or feature usage breadth.
76
-
77
- Name counter-metrics explicitly — what this product should *not* optimize for. These prevent the wrong thing being built: more entries per day is not better if the goal is accurate daily records; longer dashboard sessions may indicate a broken IA, not high engagement. Counter-metrics are as load-bearing as primary metrics for downstream readers.
78
-
79
- For low-stakes or personal-use products: one sentence is enough. "Success: I use this daily and it replaces the notebook within a month." Do not impose metric rigor where the stakes do not warrant it.
@@ -1,58 +0,0 @@
1
- # Validation Rendering
2
-
3
- How the validator subagent's findings become a validation report. Loaded only when the user has explicitly asked for analysis — either Validate intent or a mid-session report request. The Finalize discipline pass during Create/Update does NOT render a report; its findings stay in-conversation.
4
-
5
- ## Validator subagent output contract
6
-
7
- The subagent walks `{workflow.validation_checklist}` against `prd.md` (and `addendum.md` if present) and writes `{doc_workspace}/validation-findings.json`:
8
-
9
- ```json
10
- {
11
- "prd_name": "Plantsona",
12
- "prd_path": "{doc_workspace}/prd.md",
13
- "checklist_path": "{workflow.validation_checklist}",
14
- "timestamp": "2026-05-11T09:14:00",
15
- "overall_synthesis": "2-3 sentences of judgment about the PRD's overall state — what holds up, what's at risk. Written by the subagent, not the parent.",
16
- "findings": [
17
- {
18
- "id": "Q-2",
19
- "category": "Quality",
20
- "title": "Measurability",
21
- "status": "warn",
22
- "severity": "medium",
23
- "location": "§16 Success Metrics, lines 408-422",
24
- "note": "Success Metrics list is measurable but counter-metrics are named only for premium conversion. Other metrics lack paired counter-metrics.",
25
- "suggested_fix": "Add counter-metrics for engagement (e.g., DAU/MAU) and seasonal cadence."
26
- }
27
- ]
28
- }
29
- ```
30
-
31
- Per-finding fields:
32
-
33
- - `id` (required) — checklist item ID (e.g., `Q-1`, `D-2`, `STK-1`, or org-custom prefixes).
34
- - `category` (optional) — explicit category name; if omitted, the renderer maps from the ID prefix.
35
- - `title` (optional but recommended) — the checklist item's short name.
36
- - `status` — `pass` | `warn` | `fail` | `n/a`.
37
- - `severity` — `low` | `medium` | `high` | `critical`.
38
- - `location` (optional) — section/line/range in the PRD where the finding lives. Cite specifics, never abstract criticism.
39
- - `note` (optional) — the finding itself, in one or two sentences.
40
- - `suggested_fix` (optional) — concrete next action.
41
-
42
- ## Rendering invocation
43
-
44
- After the subagent writes findings:
45
-
46
- ```bash
47
- python3 {skill-root}/scripts/render-validation-html.py \
48
- --findings {doc_workspace}/validation-findings.json \
49
- --template {workflow.validation_report_template} \
50
- --output {doc_workspace}/validation-report.html \
51
- --open
52
- ```
53
-
54
- Include `--open` for interactive runs (auto-opens in default browser). Omit `--open` in headless runs.
55
-
56
- The script writes two artifacts side-by-side: the HTML report at `--output`, and a markdown companion at the same path with `.md` extension (e.g. `validation-report.md`). Both are always produced when the script runs — trigger gating happens upstream (the script is only invoked when the user has asked for analysis). It computes pass/warn/fail/na counts, derives a grade (Excellent / Good / Fair / Poor) from critical-fail and total-fail counts, renders an inline SVG score bar in the HTML, groups findings by category, and returns a one-line JSON summary on stdout: `{"output": "...", "markdown": "...", "grade": "...", "stats": {...}}`.
57
-
58
- Re-running validation overwrites the existing report files in place. Markdown form is what Update mode reads when rolling findings into a revision.