bmad-method 6.8.1-next.19 → 6.8.1-next.20

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.
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.8.1-next.19",
4
+ "version": "6.8.1-next.20",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -207,11 +207,11 @@ def test_unknown_category_style_uses_fallback_glyph():
207
207
 
208
208
  def test_shipped_selector_is_in_sync_with_catalog():
209
209
  # foolproofing: if someone edits brain-methods.csv they must regenerate the page.
210
- # Regenerate with: python3 brain.py html --out assets/brain-selector.html
210
+ # Regenerate with: uv run brain.py html --out assets/brain-selector.html
211
211
  asset = brain.DEFAULT_FILE.parent / "brain-selector.html"
212
212
  assert asset.is_file(), "missing assets/brain-selector.html — generate it"
213
213
  expected = brain.html_doc(brain.load(brain.DEFAULT_FILE))
214
214
  assert asset.read_text(encoding="utf-8") == expected, (
215
215
  "assets/brain-selector.html is stale; regenerate: "
216
- "python3 brain.py html --out assets/brain-selector.html"
216
+ "uv run brain.py html --out assets/brain-selector.html"
217
217
  )
@@ -13,7 +13,7 @@ Exercises the scanner against a synthesized install tree:
13
13
  - malformed TOML (surfaces as an error without aborting)
14
14
  - multiple skills roots (e.g. project-local + user-global mix)
15
15
 
16
- Run: python3 scripts/tests/test_list_customizable_skills.py
16
+ Run: uv run scripts/tests/test_list_customizable_skills.py
17
17
  """
18
18
 
19
19
  from __future__ import annotations
@@ -10,12 +10,14 @@ Reads from four layers (highest priority last):
10
10
 
11
11
  Outputs merged JSON to stdout. Errors go to stderr.
12
12
 
13
- Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
14
- no virtualenv plain `python3` is sufficient.
15
-
16
- python3 resolve_config.py --project-root /abs/path/to/project
17
- python3 resolve_config.py --project-root ... --key core
18
- python3 resolve_config.py --project-root ... --key agents
13
+ Uses only the Python stdlib (`tomllib`) no third-party dependencies.
14
+ BMad is standardizing on `uv run` to invoke scripts (uv provisions a suitable
15
+ interpreter for you); a plain `python3` on PATH still works during the
16
+ transition. Either runner needs Python 3.11+ for `tomllib`.
17
+
18
+ uv run resolve_config.py --project-root /abs/path/to/project
19
+ uv run resolve_config.py --project-root ... --key core
20
+ uv run resolve_config.py --project-root ... --key agents
19
21
 
20
22
  Merge rules (same as resolve_customization.py):
21
23
  - Scalars: override wins
@@ -11,12 +11,14 @@ Skill name is derived from the basename of the skill directory.
11
11
 
12
12
  Outputs merged JSON to stdout. Errors go to stderr.
13
13
 
14
- Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
15
- no virtualenv plain `python3` is sufficient.
16
-
17
- python3 resolve_customization.py --skill /abs/path/to/skill-dir
18
- python3 resolve_customization.py --skill ... --key agent
19
- python3 resolve_customization.py --skill ... --key agent.menu
14
+ Uses only the Python stdlib (`tomllib`) no third-party dependencies.
15
+ BMad is standardizing on `uv run` to invoke scripts (uv provisions a suitable
16
+ interpreter for you); a plain `python3` on PATH still works during the
17
+ transition. Either runner needs Python 3.11+ for `tomllib`.
18
+
19
+ uv run resolve_customization.py --skill /abs/path/to/skill-dir
20
+ uv run resolve_customization.py --skill ... --key agent
21
+ uv run resolve_customization.py --skill ... --key agent.menu
20
22
 
21
23
  Merge rules (purely structural — no field-name special-casing):
22
24
  - Scalars (string, int, bool, float): override wins
@@ -1233,6 +1233,9 @@ class Installer {
1233
1233
  ` 1. Launch your AI agent from your project folder`,
1234
1234
  ` 2. Not sure what to do? Invoke the ${color.cyan('bmad-help')} skill and ask it what to do!`,
1235
1235
  '',
1236
+ ` ${color.cyan('Tip:')} BMAD workflows increasingly run Python scripts via ${color.cyan('uv run')} — uv is`,
1237
+ ` becoming the de facto standard. If you don't have it yet, ask your agent to set it up.`,
1238
+ '',
1236
1239
  ` Blog, Docs and Guides: ${color.blue('https://bmadcode.com/')}`,
1237
1240
  ` Community: ${color.blue('https://discord.gg/gk8jAdXWmj')}`,
1238
1241
  );
@@ -0,0 +1,97 @@
1
+ const { spawnSync } = require('node:child_process');
2
+ const prompts = require('../prompts');
3
+
4
+ // `uv` (https://docs.astral.sh/uv/) is becoming the de facto standard for
5
+ // running the Python scripts BMAD workflows shell out to: `uv run <script>`
6
+ // resolves the interpreter and any dependencies on demand, so skills don't
7
+ // have to assume a particular `python3` is on PATH. The ecosystem is mid-
8
+ // migration — some skills still call `python3` directly — so a missing `uv`
9
+ // is a warning, not a blocker: BMAD installs and runs either way.
10
+ const RUNTIME_COMMAND = 'uv';
11
+
12
+ /**
13
+ * Parse `uv --version` output into version parts.
14
+ * Example outputs: "uv 0.5.31", "uv 0.5.31 (Homebrew 2025-02-12)".
15
+ * @param {string} output - stdout/stderr from `uv --version`
16
+ * @returns {{major: number, minor: number, patch: number, raw: string}|null}
17
+ */
18
+ function parseUvVersion(output) {
19
+ if (!output) return null;
20
+ const match = output.match(/uv\s+(\d+)\.(\d+)(?:\.(\d+))?/i);
21
+ if (!match) return null;
22
+ return {
23
+ major: Number(match[1]),
24
+ minor: Number(match[2]),
25
+ patch: Number(match[3] || 0),
26
+ raw: `${match[1]}.${match[2]}.${match[3] || 0}`,
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Probe the local environment for `uv`.
32
+ * @returns {{version: {major: number, minor: number, patch: number, raw: string}}|null}
33
+ */
34
+ function detectUv() {
35
+ let result;
36
+ try {
37
+ result = spawnSync(RUNTIME_COMMAND, ['--version'], {
38
+ encoding: 'utf8',
39
+ timeout: 5000,
40
+ windowsHide: true,
41
+ });
42
+ } catch {
43
+ return null;
44
+ }
45
+ if (!result || result.error) return null;
46
+ const version = parseUvVersion(`${result.stdout || ''}\n${result.stderr || ''}`);
47
+ return version ? { version } : null;
48
+ }
49
+
50
+ function setupHints() {
51
+ return [
52
+ 'BMAD workflows increasingly run Python scripts via `uv run`, which manages',
53
+ 'the interpreter and dependencies for you — no manual venv or pip needed.',
54
+ '',
55
+ 'Easiest path: ask your AI agent to "install and set up uv for me".',
56
+ '',
57
+ 'Or install it yourself:',
58
+ ' macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh',
59
+ ' Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"',
60
+ ' Homebrew: brew install uv',
61
+ ' Docs: https://docs.astral.sh/uv/getting-started/installation/',
62
+ ].join('\n');
63
+ }
64
+
65
+ /**
66
+ * Check whether `uv` is available and inform the user.
67
+ *
68
+ * Warn-don't-block, and no acknowledgement prompt: `uv` is on its way to being
69
+ * the standard runner for BMAD's Python scripts, but the migration is still in
70
+ * progress, so the install never stops on its account. The note tells the user
71
+ * how to set it up (preferably by asking their agent).
72
+ *
73
+ * @returns {Promise<{status: 'found'|'missing', detected: Object|null}>}
74
+ */
75
+ async function checkUvEnvironment() {
76
+ // Called via module.exports so tests can stub detection.
77
+ const detected = module.exports.detectUv();
78
+
79
+ if (detected) {
80
+ await prompts.log.success(`uv ${detected.version.raw} detected — ready to run BMAD's Python-powered scripts via \`uv run\`.`);
81
+ return { status: 'found', detected };
82
+ }
83
+
84
+ await prompts.log.warn(
85
+ "uv not found on PATH. uv is becoming the de facto standard for running BMAD's Python\n" +
86
+ 'scripts (`uv run <script>`), and it provisions the interpreter for you. BMAD installs\n' +
87
+ 'fine without it, but setting up uv now keeps you ahead as workflows adopt it.',
88
+ );
89
+ await prompts.note(setupHints(), 'uv recommended');
90
+ return { status: 'missing', detected: null };
91
+ }
92
+
93
+ module.exports = {
94
+ checkUvEnvironment,
95
+ detectUv,
96
+ parseUvVersion,
97
+ };
@@ -12,6 +12,10 @@ startMessage: |
12
12
  🌟 100% free. 100% open source. Always.
13
13
  No paywalls. No gated content. Knowledge shared, not sold.
14
14
 
15
+ 🐍 HEADS UP: uv (https://docs.astral.sh/uv/) is becoming the de facto standard
16
+ for running the Python scripts BMAD workflows rely on (`uv run <script>`).
17
+ If it's not set up yet, ask your AI agent to "install and set up uv for me".
18
+
15
19
  🌐 CONNECT:
16
20
  Website: https://bmadcode.com/
17
21
  Discord: https://discord.gg/gk8jAdXWmj
@@ -161,15 +161,16 @@ class UI {
161
161
  const messageLoader = new MessageLoader();
162
162
  await messageLoader.displayStartMessage();
163
163
 
164
- // Probe the local Python before any other prompts: several BMAD features
165
- // (memlog session memory, TOML config resolution) need Python 3.11+ at
166
- // runtime. Warn-don't-block, but require an explicit ack so the warning
167
- // can't scroll past unseen. The installer runs in the destination
168
- // environment, so probing PATH here tests the right machine.
169
- // Skip the ack when stdin isn't a TTY (CI/Docker/piped): clack's select
170
- // on closed stdin resolves to cancel, which would silently exit 0.
171
- const { checkPythonEnvironment } = require('./core/python-check');
172
- await checkPythonEnvironment({ nonInteractive: !!options.yes || !process.stdin.isTTY });
164
+ // Probe for `uv` before any other prompts: it's becoming the de facto
165
+ // runner for the Python scripts BMAD workflows shell out to
166
+ // (`uv run <script>`), and uv provisions the interpreter itself, so it's
167
+ // the single thing worth checking for. The migration is still in progress
168
+ // (some skills still call `python3` directly), so this is informational —
169
+ // warn-don't-block, no ack prompt and just points the user at setup
170
+ // (ideally "ask your agent to set up uv"). The installer runs in the
171
+ // destination environment, so probing PATH here tests the right machine.
172
+ const { checkUvEnvironment } = require('./core/uv-check');
173
+ await checkUvEnvironment();
173
174
 
174
175
  // Parse channel flags (--channel/--all-*/--next=/--pin) once. Warnings
175
176
  // are surfaced immediately so the user sees them before any git ops run.
@@ -1,199 +0,0 @@
1
- const { spawnSync } = require('node:child_process');
2
- const prompts = require('../prompts');
3
-
4
- // Python 3.11 added stdlib `tomllib` (PEP 680), which the shared scripts in
5
- // src/scripts/ (resolve_config.py, resolve_customization.py) require to read
6
- // BMAD's TOML config files. memlog.py is more lenient and runs on 3.8+.
7
- const PYTHON_FULL_SUPPORT = { major: 3, minor: 11 };
8
- const PYTHON_PARTIAL_SUPPORT = { major: 3, minor: 8 };
9
-
10
- // Every runtime call site (skill steps, on_complete hooks) invokes a literal
11
- // `python3`, so only that command's version vouches for BMAD features. The
12
- // fallback probes exist to tell the user "Python is installed, but not under
13
- // the name BMAD uses" instead of a misleading "No Python found".
14
- const RUNTIME_COMMAND = 'python3';
15
- const PROBE_CANDIDATES =
16
- process.platform === 'win32'
17
- ? [
18
- { command: 'python3', args: ['--version'] },
19
- { command: 'py', args: ['-3', '--version'] },
20
- { command: 'python', args: ['--version'] },
21
- ]
22
- : [
23
- { command: 'python3', args: ['--version'] },
24
- { command: 'python', args: ['--version'] },
25
- ];
26
-
27
- /**
28
- * Parse a `python --version` output line into version parts.
29
- * Python 3 prints to stdout; Python 2 printed to stderr — callers pass both.
30
- * @param {string} output - Combined stdout/stderr from `python --version`
31
- * @returns {{major: number, minor: number, patch: number, raw: string}|null}
32
- */
33
- function parsePythonVersion(output) {
34
- if (!output) return null;
35
- const match = output.match(/Python\s+(\d+)\.(\d+)(?:\.(\d+))?/);
36
- if (!match) return null;
37
- return {
38
- major: Number(match[1]),
39
- minor: Number(match[2]),
40
- patch: Number(match[3] || 0),
41
- raw: `${match[1]}.${match[2]}.${match[3] || 0}`,
42
- };
43
- }
44
-
45
- /**
46
- * Classify a detected Python version against BMAD's feature requirements.
47
- * @param {{major: number, minor: number}|null} version
48
- * @returns {'full'|'partial'|'unsupported'|'none'}
49
- */
50
- function classifyPython(version) {
51
- if (!version) return 'none';
52
- const { major, minor } = version;
53
- if (major > PYTHON_FULL_SUPPORT.major || (major === PYTHON_FULL_SUPPORT.major && minor >= PYTHON_FULL_SUPPORT.minor)) {
54
- return 'full';
55
- }
56
- if (major === PYTHON_PARTIAL_SUPPORT.major && minor >= PYTHON_PARTIAL_SUPPORT.minor) {
57
- return 'partial';
58
- }
59
- return 'unsupported';
60
- }
61
-
62
- /**
63
- * Run one probe candidate and return its parsed version, or null.
64
- * @param {{command: string, args: string[]}} candidate
65
- * @returns {{major: number, minor: number, patch: number, raw: string}|null}
66
- */
67
- function probeVersion(candidate) {
68
- const run = (extra = {}) =>
69
- spawnSync(candidate.command, candidate.args, {
70
- encoding: 'utf8',
71
- timeout: 5000,
72
- windowsHide: true,
73
- ...extra,
74
- });
75
- let result = run();
76
- // Node >=18.20/20.12 refuses to spawn .bat/.cmd without a shell
77
- // (CVE-2024-27980 hardening) and reports EINVAL — pyenv-win ships its
78
- // python shims as .bat. Args here are static literals, so a shell retry
79
- // is injection-safe.
80
- if (result.error && result.error.code === 'EINVAL' && process.platform === 'win32') {
81
- result = run({ shell: true });
82
- }
83
- if (result.error) return null;
84
- return parsePythonVersion(`${result.stdout || ''}\n${result.stderr || ''}`);
85
- }
86
-
87
- /**
88
- * Probe the local environment for a Python interpreter.
89
- * Tries each candidate command and returns the first that reports a version.
90
- * `isRuntimeCommand` is true only when the match is `python3` — the command
91
- * BMAD scripts actually invoke.
92
- * @returns {{command: string, version: {major: number, minor: number, patch: number, raw: string}, isRuntimeCommand: boolean}|null}
93
- */
94
- function detectPython() {
95
- for (const candidate of PROBE_CANDIDATES) {
96
- try {
97
- const version = probeVersion(candidate);
98
- if (version) {
99
- const display = candidate.args.length > 1 ? `${candidate.command} ${candidate.args.slice(0, -1).join(' ')}` : candidate.command;
100
- return { command: display, version, isRuntimeCommand: candidate.command === RUNTIME_COMMAND };
101
- }
102
- } catch {
103
- // Candidate not runnable — try the next one.
104
- }
105
- }
106
- return null;
107
- }
108
-
109
- function upgradeHints() {
110
- return [
111
- 'How to get Python 3.11+ (as `python3`):',
112
- ' macOS: brew install python3',
113
- ' Windows: winget install Python.Python.3.12 (then ensure `python3` resolves, e.g. enable the python3 alias)',
114
- ' Linux/WSL: sudo apt install python3 (Ubuntu 24.04+ ships 3.12; older distros: use pyenv or deadsnakes)',
115
- ' Docker: add python3 to your image (e.g. apk add python3 / apt-get install -y python3)',
116
- ].join('\n');
117
- }
118
-
119
- /**
120
- * Check the local Python environment and warn about degraded BMAD features.
121
- *
122
- * Warn-don't-block: most of BMAD works without Python, so the install always
123
- * may proceed — but the user must explicitly acknowledge the warning so it
124
- * can't scroll past unseen. In non-interactive runs (--yes, or stdin is not
125
- * a TTY) the warning is logged and the install continues without a prompt.
126
- *
127
- * @param {Object} [options]
128
- * @param {boolean} [options.nonInteractive=false] - Skip the ack prompt (--yes, or no TTY)
129
- * @returns {Promise<{status: string, detected: Object|null}>}
130
- */
131
- async function checkPythonEnvironment({ nonInteractive = false } = {}) {
132
- // Called via module.exports so tests can stub detection.
133
- const detected = module.exports.detectPython();
134
- const status = classifyPython(detected ? detected.version : null);
135
-
136
- if (status === 'full' && detected.isRuntimeCommand) {
137
- await prompts.log.success(`Python ${detected.version.raw} detected (${detected.command}) — all BMAD features supported.`);
138
- return { status, detected };
139
- }
140
-
141
- if (detected && !detected.isRuntimeCommand) {
142
- await prompts.log.warn(
143
- `Python ${detected.version.raw} found via \`${detected.command}\`, but BMAD scripts invoke \`python3\`, which is not on PATH.\n` +
144
- `Python-powered features (memlog session memory, TOML config resolution) won't run until \`python3\` resolves —\n` +
145
- `add a python3 alias/shim, or reinstall Python with the python3 launcher enabled.`,
146
- );
147
- } else if (status === 'partial') {
148
- await prompts.log.warn(
149
- `Python ${detected.version.raw} detected (${detected.command}) — BMAD's TOML config tools need Python 3.11+ (stdlib tomllib).\n` +
150
- `Works: memlog session memory. Won't work: config/customization resolution scripts.`,
151
- );
152
- } else {
153
- const found =
154
- status === 'unsupported' ? `Python ${detected.version.raw} detected (${detected.command}) — too old.` : 'No Python found on PATH.';
155
- await prompts.log.warn(
156
- `${found} BMAD installs fine without it, but Python-powered features\n` +
157
- `(memlog session memory, TOML config resolution) won't run until Python 3.11+ is available.`,
158
- );
159
- }
160
- await prompts.note(upgradeHints(), 'Python 3.11+ recommended');
161
-
162
- if (nonInteractive) {
163
- await prompts.log.info('Continuing anyway (non-interactive run). You can fix Python later — no reinstall needed.');
164
- return { status, detected };
165
- }
166
-
167
- const choice = await prompts.select({
168
- message: "BMAD's Python-powered features won't work yet. How do you want to proceed?",
169
- choices: [
170
- {
171
- name: 'Continue install',
172
- value: 'continue',
173
- hint: 'BMAD works without Python — you can fix Python later, no reinstall needed',
174
- },
175
- {
176
- name: 'Quit and fix Python first',
177
- value: 'quit',
178
- hint: 'make Python 3.11+ available as python3, then re-run the installer',
179
- },
180
- ],
181
- default: 'continue',
182
- });
183
-
184
- if (choice === 'quit') {
185
- await prompts.cancel('Make Python 3.11+ available as `python3` (see hints above), then re-run the installer.');
186
- process.exit(0);
187
- }
188
-
189
- return { status, detected };
190
- }
191
-
192
- module.exports = {
193
- checkPythonEnvironment,
194
- detectPython,
195
- parsePythonVersion,
196
- classifyPython,
197
- PYTHON_FULL_SUPPORT,
198
- PYTHON_PARTIAL_SUPPORT,
199
- };