Compare commits

..

7 Commits

Author SHA1 Message Date
Nikolas Hor 7af9f5ca82
Merge b696e9a246 into 1040c3c306 2026-03-27 03:05:58 -03:00
Akhilesh Tyagi 1040c3c306
fix: correctly resolve output_folder paths outside project root (#2132)
* fix(bmad-init): correctly resolve output_folder paths outside project root

  When output_folder was set to an absolute path (e.g. /Users/me/outputs),
  the {project-root}/{value} result template stored it as
  {project-root}//absolute/path. resolve_project_root_placeholder then did
  a naive string replace, producing /project//absolute/path — a broken path
  that workflows could not resolve.

  For relative paths outside the root (e.g. ../../sibling), the same naive
  replace left un-normalized paths like /project/../../sibling in the
  resolved config, which some tools mishandled.

  Fix resolve_project_root_placeholder to strip the {project-root} token,
  detect whether the remainder is absolute (returning it directly) or
  relative (joining with project root and normalizing via os.path.normpath).

  Fix apply_result_template to skip the template entirely when raw_value is
  already an absolute path, and to normalize the result for relative-but-
  outside paths. This covers the bmad-init SKILL write path, which bakes
  the resolved path directly into config.yaml.

  Add 7 tests covering all three path cases (absolute, relative-with-
  traversal, normal in-project) for both functions.

* Address review comments

---------

Co-authored-by: Akhilesh Tyagi <akhilesh.t@nextiva.com>
Co-authored-by: Brian <bmadcode@gmail.com>
2026-03-26 21:46:14 -05:00
Brian ed9dea9058
refactor: consolidate plugin.json metadata into marketplace.json (#2137)
Merge license, homepage, repository, keywords, and author from
plugin.json into marketplace.json and remove the redundant file.
The npx skills installer only reads marketplace.json for skill
discovery — plugin.json contributed no functional value.
2026-03-26 19:48:04 -05:00
Brian 3d8a89c7e1
feat: add .claude-plugin marketplace and plugin metadata (#2136) 2026-03-26 19:12:32 -05:00
github-actions[bot] 819d373e2e chore(release): v6.2.2 [skip ci] 2026-03-26 02:44:35 +00:00
Brian a5640c890d
chore: add v6.2.2 changelog and use CHANGELOG.md for GH release notes (#2127)
Update publish workflow to extract release notes from CHANGELOG.md
instead of using --generate-notes, with fallback if no entry found.
2026-03-25 21:43:25 -05:00
Brian 6dd0a97c1f
fix: update bmb module-definition path for skills/ restructure (#2126)
bmad-builder moved skills from src/skills/ to skills/ at repo root
for Claude Code plugin and Vercel Skills CLI compatibility.
2026-03-25 21:25:33 -05:00
9 changed files with 223 additions and 10 deletions

View File

@ -0,0 +1,78 @@
{
"name": "bmad-method",
"owner": {
"name": "Brian (BMad) Madison"
},
"description": "Breakthrough Method of Agile AI-driven Development — a full-lifecycle framework with agents and workflows for analysis, planning, architecture, and implementation.",
"license": "MIT",
"homepage": "https://github.com/bmad-code-org/BMAD-METHOD",
"repository": "https://github.com/bmad-code-org/BMAD-METHOD",
"keywords": ["bmad", "agile", "ai", "orchestrator", "development", "methodology", "agents"],
"plugins": [
{
"name": "bmad-pro-skills",
"source": "./",
"description": "Next level skills for power users — advanced prompting techniques, agent management, and more.",
"version": "6.3.0",
"author": {
"name": "Brian (BMad) Madison"
},
"skills": [
"./src/core-skills/bmad-help",
"./src/core-skills/bmad-init",
"./src/core-skills/bmad-brainstorming",
"./src/core-skills/bmad-distillator",
"./src/core-skills/bmad-party-mode",
"./src/core-skills/bmad-shard-doc",
"./src/core-skills/bmad-advanced-elicitation",
"./src/core-skills/bmad-editorial-review-prose",
"./src/core-skills/bmad-editorial-review-structure",
"./src/core-skills/bmad-index-docs",
"./src/core-skills/bmad-review-adversarial-general",
"./src/core-skills/bmad-review-edge-case-hunter"
]
},
{
"name": "bmad-method-lifecycle",
"source": "./",
"description": "Full-lifecycle AI development framework — agents and workflows for product analysis, planning, architecture, and implementation.",
"version": "6.3.0",
"author": {
"name": "Brian (BMad) Madison"
},
"skills": [
"./src/bmm-skills/1-analysis/bmad-product-brief",
"./src/bmm-skills/1-analysis/bmad-agent-analyst",
"./src/bmm-skills/1-analysis/bmad-agent-tech-writer",
"./src/bmm-skills/1-analysis/bmad-document-project",
"./src/bmm-skills/1-analysis/research/bmad-domain-research",
"./src/bmm-skills/1-analysis/research/bmad-market-research",
"./src/bmm-skills/1-analysis/research/bmad-technical-research",
"./src/bmm-skills/2-plan-workflows/bmad-agent-pm",
"./src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer",
"./src/bmm-skills/2-plan-workflows/bmad-create-prd",
"./src/bmm-skills/2-plan-workflows/bmad-edit-prd",
"./src/bmm-skills/2-plan-workflows/bmad-validate-prd",
"./src/bmm-skills/2-plan-workflows/bmad-create-ux-design",
"./src/bmm-skills/3-solutioning/bmad-agent-architect",
"./src/bmm-skills/3-solutioning/bmad-create-architecture",
"./src/bmm-skills/3-solutioning/bmad-check-implementation-readiness",
"./src/bmm-skills/3-solutioning/bmad-create-epics-and-stories",
"./src/bmm-skills/3-solutioning/bmad-generate-project-context",
"./src/bmm-skills/4-implementation/bmad-agent-dev",
"./src/bmm-skills/4-implementation/bmad-agent-sm",
"./src/bmm-skills/4-implementation/bmad-agent-qa",
"./src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev",
"./src/bmm-skills/4-implementation/bmad-dev-story",
"./src/bmm-skills/4-implementation/bmad-quick-dev",
"./src/bmm-skills/4-implementation/bmad-sprint-planning",
"./src/bmm-skills/4-implementation/bmad-sprint-status",
"./src/bmm-skills/4-implementation/bmad-code-review",
"./src/bmm-skills/4-implementation/bmad-create-story",
"./src/bmm-skills/4-implementation/bmad-correct-course",
"./src/bmm-skills/4-implementation/bmad-retrospective",
"./src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests"
]
}
]
}

View File

@ -120,7 +120,18 @@ jobs:
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
run: |
TAG="v$(node -p 'require("./package.json").version')"
gh release create "$TAG" --generate-notes
VERSION="${TAG#v}"
# Extract the current version's section from CHANGELOG.md
BODY=$(awk -v ver="$VERSION" '
/^## v/ { if (found) exit; if (index($0, "## v" ver)) found=1; next }
found { print }
' CHANGELOG.md)
if [ -z "$BODY" ]; then
echo "::warning::No CHANGELOG.md entry for $TAG — falling back to auto-generated notes"
gh release create "$TAG" --generate-notes
else
gh release create "$TAG" --notes "$BODY"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,5 +1,21 @@
# Changelog
## v6.2.2 - 2026-03-25
### ♻️ Refactoring
* Modernize module-help CSV to 13-column format with `after`/`before` dependency graph replacing sequence numbers (#2120)
* Rewrite bmad-help from procedural 8-step execution to outcome-based skill design (~50% shorter) (#2120)
### 🐛 Bug Fixes
* Update bmad-builder module-definition path from `src/module.yaml` to `skills/module.yaml` for bmad-builder v1.2.0 compatibility (#2126)
* Fix eslint config to ignore gitignored lock files (#2120)
### 📚 Documentation
* Close Epic 4.5 explanation gaps in Chinese (zh-CN): normalize command naming to current `bmad-*` convention and add cross-links across 9 explanation pages (#2102)
## v6.2.1 - 2026-03-24
### 🎁 Highlights

View File

@ -37,7 +37,19 @@ Requires [Node.js](https://nodejs.org) v20+ and `npx` (included with npm).
| `--user-name <name>` | Name for agents to use | System username |
| `--communication-language <lang>` | Agent communication language | English |
| `--document-output-language <lang>` | Document output language | English |
| `--output-folder <path>` | Output folder path | _bmad-output |
| `--output-folder <path>` | Output folder path (see resolution rules below) | `_bmad-output` |
#### Output Folder Path Resolution
The value passed to `--output-folder` (or entered interactively) is resolved according to these rules:
| Input type | Example | Resolved as |
|------------|---------|-------------|
| Relative path (default) | `_bmad-output` | `<project-root>/_bmad-output` |
| Relative path with traversal | `../../shared-outputs` | Normalized absolute path — e.g. `/Users/me/shared-outputs` |
| Absolute path | `/Users/me/shared-outputs` | Used as-is — project root is **not** prepended |
The resolved path is what agents and workflows use at runtime when writing output files. Using an absolute path or a traversal-based relative path lets you direct all generated artifacts to a directory outside your project tree — useful for shared or monorepo setups.
### Other Options
@ -141,6 +153,7 @@ Invalid values will either:
:::tip[Best Practices]
- Use absolute paths for `--directory` to avoid ambiguity
- Use an absolute path for `--output-folder` when you want artifacts written outside the project tree (e.g. a shared monorepo outputs directory)
- Test flags locally before using in CI/CD pipelines
- Combine with `-y` for truly unattended installations
- Use `--debug` if you encounter issues during installation

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "bmad-method",
"version": "6.2.1",
"version": "6.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bmad-method",
"version": "6.2.1",
"version": "6.2.2",
"license": "MIT",
"dependencies": {
"@clack/core": "^1.0.0",

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "bmad-method",
"version": "6.2.1",
"version": "6.2.2",
"description": "Breakthrough Method of Agile AI-driven Development",
"keywords": [
"agile",

View File

@ -166,9 +166,27 @@ def resolve_project_root_placeholder(value, project_root):
"""Replace {project-root} placeholder with actual path."""
if not value or not isinstance(value, str):
return value
if '{project-root}' in value:
return value.replace('{project-root}', str(project_root))
return value
if '{project-root}' not in value:
return value
# Strip the {project-root} token to inspect what remains, so we can
# correctly handle absolute paths stored as "{project-root}//absolute/path"
# (produced by the "{project-root}/{value}" template applied to an absolute value).
suffix = value.replace('{project-root}', '', 1)
# Strip the one path separator that follows the token (if any)
if suffix.startswith('/') or suffix.startswith('\\'):
remainder = suffix[1:]
else:
remainder = suffix
if os.path.isabs(remainder):
# The original value was an absolute path stored with a {project-root}/ prefix.
# Return the absolute path directly — no joining needed.
return remainder
# Relative path: join with project root and normalize to resolve any .. segments.
return os.path.normpath(os.path.join(str(project_root), remainder))
def parse_var_specs(vars_string):
@ -222,9 +240,22 @@ def apply_result_template(var_def, raw_value, context):
if not result_template:
return raw_value
# If the user supplied an absolute path and the template would prefix it with
# "{project-root}/", skip the template entirely to avoid producing a broken path
# like "/my/project//absolute/path".
if isinstance(raw_value, str) and os.path.isabs(raw_value):
return raw_value
ctx = dict(context)
ctx['value'] = raw_value
return expand_template(result_template, ctx)
result = expand_template(result_template, ctx)
# Normalize the resulting path to resolve any ".." segments (e.g. when the user
# entered a relative path such as "../../outside-dir").
if isinstance(result, str) and '{' not in result and os.path.isabs(result):
result = os.path.normpath(result)
return result
# =============================================================================

View File

@ -110,6 +110,37 @@ class TestResolveProjectRootPlaceholder(unittest.TestCase):
def test_non_string(self):
self.assertEqual(resolve_project_root_placeholder(42, Path('/test')), 42)
def test_absolute_path_stored_with_prefix(self):
"""Absolute output_folder entered by user is stored as '{project-root}//abs/path'
by the '{project-root}/{value}' template. It must resolve to '/abs/path', not
'/project//abs/path'."""
result = resolve_project_root_placeholder(
'{project-root}//Users/me/outside', Path('/Users/me/myproject')
)
self.assertEqual(result, '/Users/me/outside')
def test_relative_path_with_traversal_is_normalized(self):
"""A relative path like '../../sibling' produces '{project-root}/../../sibling'
after the template. It must resolve to the normalized absolute path, not the
un-normalized string '/project/../../sibling'."""
result = resolve_project_root_placeholder(
'{project-root}/../../sibling', Path('/Users/me/myproject')
)
self.assertEqual(result, '/Users/sibling')
def test_relative_path_one_level_up(self):
result = resolve_project_root_placeholder(
'{project-root}/../outside-outputs', Path('/project/root')
)
self.assertEqual(result, '/project/outside-outputs')
def test_standard_relative_path_unchanged(self):
"""Normal in-project relative paths continue to work correctly."""
result = resolve_project_root_placeholder(
'{project-root}/_bmad-output', Path('/project/root')
)
self.assertEqual(result, '/project/root/_bmad-output')
class TestExpandTemplate(unittest.TestCase):
@ -147,6 +178,39 @@ class TestApplyResultTemplate(unittest.TestCase):
result = apply_result_template(var_def, 'English', {})
self.assertEqual(result, 'English')
def test_absolute_value_skips_project_root_template(self):
"""When the user enters an absolute path, the '{project-root}/{value}' template
must not be applied doing so would produce '/project//absolute/path'."""
var_def = {'result': '{project-root}/{value}'}
result = apply_result_template(
var_def, '/Users/me/shared-outputs', {'project-root': '/Users/me/myproject'}
)
self.assertEqual(result, '/Users/me/shared-outputs')
def test_relative_traversal_value_is_normalized(self):
"""A relative path like '../../outside' combined with the project-root template
must produce a clean normalized absolute path, not '/project/../../outside'."""
var_def = {'result': '{project-root}/{value}'}
result = apply_result_template(
var_def, '../../outside-dir', {'project-root': '/Users/me/myproject'}
)
self.assertEqual(result, '/Users/outside-dir')
def test_relative_one_level_up_is_normalized(self):
var_def = {'result': '{project-root}/{value}'}
result = apply_result_template(
var_def, '../sibling-outputs', {'project-root': '/project/root'}
)
self.assertEqual(result, '/project/sibling-outputs')
def test_normal_relative_value_unchanged(self):
"""Standard in-project relative paths still produce the expected joined path."""
var_def = {'result': '{project-root}/{value}'}
result = apply_result_template(
var_def, '_bmad-output', {'project-root': '/project/root'}
)
self.assertEqual(result, '/project/root/_bmad-output')
class TestLoadModuleYaml(unittest.TestCase):

View File

@ -4,7 +4,7 @@
modules:
bmad-builder:
url: https://github.com/bmad-code-org/bmad-builder
module-definition: src/module.yaml
module-definition: skills/module.yaml
code: bmb
name: "BMad Builder"
description: "Agent and Builder"