bmad-method 6.12.2 → 6.12.3-next.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.
@@ -14,7 +14,7 @@
14
14
  "displayName": "BMad Method Analyze Plan Build",
15
15
  "source": "./",
16
16
  "description": "Full-lifecycle AI development framework — agents and workflows for product analysis, planning, architecture, and implementation.",
17
- "version": "6.12.2",
17
+ "version": "6.12.3",
18
18
  "author": {
19
19
  "name": "Brian (BMad) Madison"
20
20
  },
package/bmad-modules.yaml CHANGED
@@ -9,7 +9,9 @@
9
9
  # Omit to inherit the installer's hardcoded default (stable).
10
10
  #
11
11
  # pin (optional) — a git tag to install instead of the channel. Use it when
12
- # newer releases no longer work with this installer. Only --pin overrides it.
12
+ # newer releases no longer work with this installer. The pin is the default for
13
+ # every module that gets no explicit choice. --pin, --next=CODE and a stable or
14
+ # next pick in the interactive picker override it; --channel and --all-* do not.
13
15
  #
14
16
  # deprecated (optional, default false) — when true, the module is hidden from
15
17
  # the installer picker UNLESS it is already installed (so existing users can
package/body.md ADDED
@@ -0,0 +1,17 @@
1
+ Picking a channel in the picker is ignored when the module has a registry `pin`.
2
+ The `tea` pin (v1.27.2) wins, and the manifest records `channel: pinned` with no message.
3
+
4
+ `decideChannelForModule` checked `registryPin` before `--next=CODE`, and the picker never recorded a `stable` pick.
5
+
6
+ New order: `--pin` > `--next=CODE` or picker stable > registry pin > `--channel`/`--all-*` > registry default > `stable`.
7
+
8
+ - `channel-plan.js`: explicit per-module picks resolve before the registry pin. New `stableSet` holds a `stable` pick.
9
+ - `ui.js`: `next` is gone from the install picker and from the update flow's channel review. A stable pick fills `stableSet`. A legacy install that tracks `main` moves to stable without a prompt.
10
+ - Tests: "registry pin beats --next" splits into three cases. Picker-level tests drive the install and update flows, including the all-stable fast path that keeps the pin.
11
+
12
+ `--yes`, the all-stable fast path and `--channel` still get the pin.
13
+
14
+ `--next=CODE` stays for headless use.
15
+ On 6.12 it now installs `main` for a pinned module, which fails for TEA v2+ because that layout needs the v7 installer.
16
+
17
+ `npm run quality` passes.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-method",
4
- "version": "6.12.2",
4
+ "version": "6.12.3-next.0",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -1474,7 +1474,7 @@ class Installer {
1474
1474
  // 'pinned' would trigger a stable-channel tag lookup at config-collection
1475
1475
  // time, burning GitHub API quota and potentially failing.
1476
1476
  const manifestData = await this.manifest.read(bmadDir);
1477
- const channelOptions = { global: null, nextSet: new Set(), pins: new Map(), warnings: [] };
1477
+ const channelOptions = { global: null, nextSet: new Set(), stableSet: new Set(), pins: new Map(), warnings: [] };
1478
1478
  if (manifestData?.modulesDetailed) {
1479
1479
  const { fetchStableTags, classifyUpgrade, parseGitHubRepo } = require('../modules/channel-resolver');
1480
1480
  for (const entry of manifestData.modulesDetailed) {
@@ -10,7 +10,8 @@
10
10
  * 3. Registry defaults (default_channel from bmad-modules.yaml)
11
11
  * 4. Hardcoded fallback 'stable'
12
12
  *
13
- * Precedence: --pin > --next=CODE > --channel (global) > registry default > 'stable'.
13
+ * Precedence: --pin > explicit per-module choice (--next=CODE, picker next/stable) > registry pin
14
+ * > --channel (global) > registry default > 'stable'.
14
15
  *
15
16
  * This module is pure. No prompts, no git, no filesystem.
16
17
  */
@@ -24,6 +25,7 @@ const VALID_CHANNELS = new Set(['stable', 'next']);
24
25
  * @returns {{
25
26
  * global: 'stable'|'next'|null,
26
27
  * nextSet: Set<string>,
28
+ * stableSet: Set<string>,
27
29
  * pins: Map<string, string>,
28
30
  * warnings: string[]
29
31
  * }}
@@ -58,6 +60,9 @@ function parseChannelOptions(options = {}) {
58
60
  nextSet.add(trimmed);
59
61
  }
60
62
 
63
+ // Filled only by the interactive per-module picker (no CLI flag).
64
+ const stableSet = new Set();
65
+
61
66
  // --pin CODE=TAG (repeatable)
62
67
  const pins = new Map();
63
68
  for (const spec of options.pin || []) {
@@ -76,7 +81,7 @@ function parseChannelOptions(options = {}) {
76
81
  // headless installs with --next=/--pin for a community module don't hang.
77
82
  const acceptBypass = options.yes === true || options.acceptBypass === true;
78
83
 
79
- return { global, nextSet, pins, warnings, acceptBypass };
84
+ return { global, nextSet, stableSet, pins, warnings, acceptBypass };
80
85
  }
81
86
 
82
87
  function normalizeChannel(raw, warnings, flagName) {
@@ -109,17 +114,20 @@ function parsePinSpec(spec) {
109
114
  * source describes where the decision came from, for logging / debugging.
110
115
  */
111
116
  function decideChannelForModule({ code, channelOptions, registryDefault, registryPin }) {
112
- const { global, nextSet, pins } = channelOptions || { nextSet: new Set(), pins: new Map() };
117
+ const { global, nextSet, stableSet, pins } = channelOptions || { nextSet: new Set(), stableSet: new Set(), pins: new Map() };
113
118
 
114
119
  if (pins && pins.has(code)) {
115
120
  return { channel: 'pinned', pin: pins.get(code), source: 'flag:--pin' };
116
121
  }
117
- if (registryPin) {
118
- return { channel: 'pinned', pin: registryPin, source: 'registry:pin' };
119
- }
120
122
  if (nextSet && nextSet.has(code)) {
121
123
  return { channel: 'next', source: 'flag:--next' };
122
124
  }
125
+ if (stableSet && stableSet.has(code)) {
126
+ return { channel: 'stable', source: 'picker:stable' };
127
+ }
128
+ if (registryPin) {
129
+ return { channel: 'pinned', pin: registryPin, source: 'registry:pin' };
130
+ }
123
131
  if (global) {
124
132
  return { channel: global, source: 'flag:--channel' };
125
133
  }
@@ -1980,15 +1980,12 @@ class UI {
1980
1980
  message: `${code}: choose a channel`,
1981
1981
  choices: [
1982
1982
  { name: stableLabel, value: 'stable' },
1983
- { name: 'next (main HEAD \u2014 current development)', value: 'next' },
1984
1983
  { name: 'pin (specific version)', value: 'pin' },
1985
1984
  ],
1986
1985
  default: 'stable',
1987
1986
  });
1988
1987
 
1989
- if (choice === 'next') {
1990
- channelOptions.nextSet.add(code);
1991
- } else if (choice === 'pin') {
1988
+ if (choice === 'pin') {
1992
1989
  const pinValue = await prompts.text({
1993
1990
  message: `Enter a version tag for '${code}' (e.g. v1.6.0):`,
1994
1991
  validate: (value) => {
@@ -1998,8 +1995,9 @@ class UI {
1998
1995
  },
1999
1996
  });
2000
1997
  channelOptions.pins.set(code, String(pinValue).trim());
1998
+ } else {
1999
+ channelOptions.stableSet.add(code);
2001
2000
  }
2002
- // 'stable' is the default; nothing to record.
2003
2001
  }
2004
2002
  }
2005
2003
 
@@ -2014,8 +2012,7 @@ class UI {
2014
2012
  * frozen unless the user also passes `--pin CODE=NEW_TAG`.
2015
2013
  * - On `next`: no prompt (pull HEAD).
2016
2014
  * - On `pinned`: no prompt (stays pinned).
2017
- * - No channel recorded and `version: null`: one-time migration prompt
2018
- * ("Switch to stable / Keep on next").
2015
+ * - No channel recorded and `version: null`: moves to stable, no prompt.
2019
2016
  *
2020
2017
  * Decisions that freeze the current version are applied by adding a pin to
2021
2018
  * `channelOptions.pins` so downstream clone logic honors them.
@@ -2050,7 +2047,7 @@ class UI {
2050
2047
  });
2051
2048
  if (existingWithChannel.length > 0) {
2052
2049
  reviewChannels = await prompts.confirm({
2053
- message: 'Review channel assignments (stable / next / pin) for your existing modules?',
2050
+ message: 'Review channel assignments (stable / pin) for your existing modules?',
2054
2051
  default: false,
2055
2052
  });
2056
2053
  }
@@ -2071,7 +2068,7 @@ class UI {
2071
2068
  const parsed = repoUrl ? parseGitHubRepo(repoUrl) : null;
2072
2069
 
2073
2070
  // Legacy migration: manifest carries no channel and a null/empty
2074
- // version. Offer the one-time pick between stable and next.
2071
+ // version. The next channel is gone from the picker, so move it to stable.
2075
2072
  const recordedChannel = prev.channel || null;
2076
2073
  const needsMigration = !recordedChannel && (prev.version == null || prev.version === '');
2077
2074
  if (needsMigration) {
@@ -2079,15 +2076,7 @@ class UI {
2079
2076
  // Conservative headless default: stable.
2080
2077
  continue;
2081
2078
  }
2082
- const chosen = await prompts.select({
2083
- message: `${code}: your existing install tracks the main branch. Switch to stable releases (recommended for production), or keep on main?`,
2084
- choices: [
2085
- { name: 'Switch to stable', value: 'stable' },
2086
- { name: 'Keep on main (next)', value: 'next' },
2087
- ],
2088
- default: 'stable',
2089
- });
2090
- if (chosen === 'next') channelOptions.nextSet.add(code);
2079
+ channelOptions.stableSet.add(code);
2091
2080
  continue;
2092
2081
  }
2093
2082
 
@@ -2105,9 +2094,6 @@ class UI {
2105
2094
  if (recordedChannel !== 'stable') {
2106
2095
  switchChoices.push({ name: 'Switch to stable (released version)', value: 'stable' });
2107
2096
  }
2108
- if (recordedChannel !== 'next') {
2109
- switchChoices.push({ name: 'Switch to next (main HEAD)', value: 'next' });
2110
- }
2111
2097
  switchChoices.push({ name: 'Pin to a specific version tag', value: 'pin' });
2112
2098
 
2113
2099
  const choice = await prompts.select({
@@ -2116,10 +2102,6 @@ class UI {
2116
2102
  default: 'keep',
2117
2103
  });
2118
2104
 
2119
- if (choice === 'next') {
2120
- channelOptions.nextSet.add(code);
2121
- continue;
2122
- }
2123
2105
  if (choice === 'pin') {
2124
2106
  const pinValue = await prompts.text({
2125
2107
  message: `Enter a version tag for '${code}' (e.g. v1.6.0):`,
@@ -2135,6 +2117,7 @@ class UI {
2135
2117
  if (choice === 'stable') {
2136
2118
  // Switch to stable: install at the top stable tag without an
2137
2119
  // upgrade-classification prompt (the user explicitly opted in).
2120
+ channelOptions.stableSet.add(code);
2138
2121
  // Also warm the tag cache here so the actual clone step doesn't
2139
2122
  // need a second GitHub API call (can hit rate limits).
2140
2123
  if (parsed) {