From 3d8a89c7e1179ced2d946e4fbc26dbbca8c805dc Mon Sep 17 00:00:00 2001 From: Brian Date: Thu, 26 Mar 2026 19:12:32 -0500 Subject: [PATCH 1/3] feat: add .claude-plugin marketplace and plugin metadata (#2136) --- .claude-plugin/marketplace.json | 67 +++++++++++++++++++++++++++++++++ .claude-plugin/plugin.json | 12 ++++++ 2 files changed, 79 insertions(+) create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 000000000..3eebc7799 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,67 @@ +{ + "name": "bmad-method", + "owner": { + "name": "Brian (BMad) Madison" + }, + "plugins": [ + { + "name": "bmad-pro-skills", + "source": "./", + "description": "Next level skills for power users — advanced prompting techniques, agent management, and more.", + "version": "6.3.0", + "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", + "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" + ] + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 000000000..8c0adab25 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "bmad-method", + "version": "6.2.2", + "description": "Breakthrough Method of Agile AI-driven Development — a full-lifecycle framework with agents and workflows for analysis, planning, architecture, and implementation. The core BMad Method.", + "author": { + "name": "Brian (BMad) Madison" + }, + "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"] +} From ed9dea9058fd836cf152ed7d1fa1d0aaaf948f8f Mon Sep 17 00:00:00 2001 From: Brian Date: Thu, 26 Mar 2026 19:48:04 -0500 Subject: [PATCH 2/3] refactor: consolidate plugin.json metadata into marketplace.json (#2137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .claude-plugin/marketplace.json | 11 +++++++++++ .claude-plugin/plugin.json | 12 ------------ 2 files changed, 11 insertions(+), 12 deletions(-) delete mode 100644 .claude-plugin/plugin.json diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3eebc7799..6f4f0e0c0 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -3,12 +3,20 @@ "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", @@ -29,6 +37,9 @@ "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", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json deleted file mode 100644 index 8c0adab25..000000000 --- a/.claude-plugin/plugin.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "bmad-method", - "version": "6.2.2", - "description": "Breakthrough Method of Agile AI-driven Development — a full-lifecycle framework with agents and workflows for analysis, planning, architecture, and implementation. The core BMad Method.", - "author": { - "name": "Brian (BMad) Madison" - }, - "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"] -} From 1040c3c30638cac7f66b08e5fc7d96240d701829 Mon Sep 17 00:00:00 2001 From: Akhilesh Tyagi Date: Fri, 27 Mar 2026 08:16:14 +0530 Subject: [PATCH 3/3] fix: correctly resolve output_folder paths outside project root (#2132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: Brian --- docs/how-to/non-interactive-installation.md | 15 ++++- .../bmad-init/scripts/bmad_init.py | 39 +++++++++-- .../bmad-init/scripts/tests/test_bmad_init.py | 64 +++++++++++++++++++ 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/docs/how-to/non-interactive-installation.md b/docs/how-to/non-interactive-installation.md index 62b3090d8..eb72dfef4 100644 --- a/docs/how-to/non-interactive-installation.md +++ b/docs/how-to/non-interactive-installation.md @@ -37,7 +37,19 @@ Requires [Node.js](https://nodejs.org) v20+ and `npx` (included with npm). | `--user-name ` | Name for agents to use | System username | | `--communication-language ` | Agent communication language | English | | `--document-output-language ` | Document output language | English | -| `--output-folder ` | Output folder path | _bmad-output | +| `--output-folder ` | 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` | `/_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 diff --git a/src/core-skills/bmad-init/scripts/bmad_init.py b/src/core-skills/bmad-init/scripts/bmad_init.py index 0c80eaab8..7a561bd2b 100644 --- a/src/core-skills/bmad-init/scripts/bmad_init.py +++ b/src/core-skills/bmad-init/scripts/bmad_init.py @@ -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 # ============================================================================= diff --git a/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py b/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py index 32e07effe..45d1abc66 100644 --- a/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py +++ b/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py @@ -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):